diff --git a/.changeset/api-override-conflict-error-defaults.md b/.changeset/api-override-conflict-error-defaults.md new file mode 100644 index 0000000000..703f76fb6e --- /dev/null +++ b/.changeset/api-override-conflict-error-defaults.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': minor +--- + +**BREAKING**: The `API_FACTORY_CONFLICT` warning is now treated as an error and will prevent the app from starting. diff --git a/.changeset/api-override-conflict-error.md b/.changeset/api-override-conflict-error.md new file mode 100644 index 0000000000..ed88452ccf --- /dev/null +++ b/.changeset/api-override-conflict-error.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: Updated the behavior of the new API override logic to reject the override and block app startup instead of just logging a deprecation warning. diff --git a/.changeset/dull-ants-pull.md b/.changeset/dull-ants-pull.md new file mode 100644 index 0000000000..66adf0898f --- /dev/null +++ b/.changeset/dull-ants-pull.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Migrated `UnregisterEntityDialog` from Material UI to Backstage UI components. diff --git a/.changeset/elasticsearch-auth-extension-point.md b/.changeset/elasticsearch-auth-extension-point.md new file mode 100644 index 0000000000..e2e1152e65 --- /dev/null +++ b/.changeset/elasticsearch-auth-extension-point.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': minor +--- + +Added `elasticsearchAuthExtensionPoint` to enable dynamic authentication mechanisms such as bearer tokens with automatic rotation. diff --git a/.changeset/fix-catalog-tags-spacing.md b/.changeset/fix-catalog-tags-spacing.md new file mode 100644 index 0000000000..e611273a04 --- /dev/null +++ b/.changeset/fix-catalog-tags-spacing.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fixed vertical spacing between tags in the catalog table. diff --git a/.changeset/fix-nextjs16-css-modules.md b/.changeset/fix-nextjs16-css-modules.md new file mode 100644 index 0000000000..faa60fc06a --- /dev/null +++ b/.changeset/fix-nextjs16-css-modules.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors. + +**Affected components:** Popover, Tooltip diff --git a/.changeset/fix-opensearch-bulk-refresh.md b/.changeset/fix-opensearch-bulk-refresh.md new file mode 100644 index 0000000000..17a791519d --- /dev/null +++ b/.changeset/fix-opensearch-bulk-refresh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fixed bulk indexing to refresh only the target index instead of all indexes, improving performance in multi-index deployments. diff --git a/.changeset/lemon-lines-give.md b/.changeset/lemon-lines-give.md new file mode 100644 index 0000000000..215f9f1a0e --- /dev/null +++ b/.changeset/lemon-lines-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +The AzureUrl class in the @backstage/integration package is now able to process BOTH git branches and git tags. Initially this class only processed git branches and threw an error when non-branch Azure URLs were passed in. diff --git a/.changeset/lucky-masks-doubt.md b/.changeset/lucky-masks-doubt.md new file mode 100644 index 0000000000..b20633308f --- /dev/null +++ b/.changeset/lucky-masks-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Return `numberOfResults` count with search query responses diff --git a/.changeset/odd-lemons-occur.md b/.changeset/odd-lemons-occur.md new file mode 100644 index 0000000000..695ed3c80c --- /dev/null +++ b/.changeset/odd-lemons-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. diff --git a/.changeset/sharp-doodles-retire.md b/.changeset/sharp-doodles-retire.md new file mode 100644 index 0000000000..2062b7ec34 --- /dev/null +++ b/.changeset/sharp-doodles-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Some security fixes diff --git a/.changeset/slow-numbers-study.md b/.changeset/slow-numbers-study.md new file mode 100644 index 0000000000..fe7203e169 --- /dev/null +++ b/.changeset/slow-numbers-study.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Plugin IDs that do not match the standard format are deprecated (letters, digits, and dashes only, starting with a letter). Plugin IDs that do no match this format will be rejected in a future release. + +In addition, plugin IDs that don't match the legacy pattern that also allows underscores, with be rejected. diff --git a/.changeset/small-rice-arrive.md b/.changeset/small-rice-arrive.md new file mode 100644 index 0000000000..a30bec6a82 --- /dev/null +++ b/.changeset/small-rice-arrive.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-mui-to-bui': patch +--- + +Updated MUI to BUI theme converter to align with latest token changes + +**Changes:** + +- Removed generation of deprecated tokens: `--bui-fg-link`, `--bui-fg-link-hover`, `--bui-fg-tint`, `--bui-fg-tint-disabled`, `--bui-bg-tint` and all its variants +- Added generation of new `info` status tokens: `--bui-fg-info`, `--bui-fg-info-on-bg`, `--bui-bg-info`, `--bui-border-info` +- Updated status color mapping to generate both standalone and `-on-bg` variants for danger, warning, success, and info +- Status colors now use `.main` for standalone variants and `.dark` for `-on-bg` variants, providing better visual hierarchy + +The converter now generates tokens that match the updated BUI design system structure, with clear distinction between status colors for standalone use vs. use on colored backgrounds. diff --git a/.changeset/smart-spoons-scream.md b/.changeset/smart-spoons-scream.md new file mode 100644 index 0000000000..5abebc3ec5 --- /dev/null +++ b/.changeset/smart-spoons-scream.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Migrated `DeleteEntityDialog` and `EntityOrphanWarning` components to Backstage UI. + +The `deleteEntity.description` translation key no longer includes "Click here to delete" text. A new `deleteEntity.actionButtonTitle` key was added for the action button. diff --git a/.changeset/solid-dryers-leave.md b/.changeset/solid-dryers-leave.md new file mode 100644 index 0000000000..db1eb86c6c --- /dev/null +++ b/.changeset/solid-dryers-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Support `connection.type: azure` in database client to use Microsoft Entra authentication with Azure database for PostgreSQL diff --git a/.changeset/three-rooms-enjoy.md b/.changeset/three-rooms-enjoy.md new file mode 100644 index 0000000000..9856aba0e8 --- /dev/null +++ b/.changeset/three-rooms-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': minor +--- + +Added support for configuring the default search type in the search page via the `search.defaultType` option in `app-config.yaml`. This applies to both the legacy and new frontend systems. If not set, the default is empty, which means searching for "all" types. diff --git a/.changeset/wicked-walls-accept.md b/.changeset/wicked-walls-accept.md new file mode 100644 index 0000000000..dc5e86a0d4 --- /dev/null +++ b/.changeset/wicked-walls-accept.md @@ -0,0 +1,59 @@ +--- +'@backstage/ui': minor +--- + +**BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling + +The following color tokens have been removed: + +- `--bui-fg-link` (and all related tokens: `-hover`, `-pressed`, `-disabled`) +- `--bui-fg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) +- `--bui-bg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`) +- `--bui-border-tint` (and all related tokens) + +**New Status Tokens:** + +Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage: + +- `--bui-fg-danger-on-bg` / `--bui-fg-danger` +- `--bui-fg-warning-on-bg` / `--bui-fg-warning` +- `--bui-fg-success-on-bg` / `--bui-fg-success` +- `--bui-fg-info-on-bg` / `--bui-fg-info` + +The `-on-bg` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast. + +**Migration:** + +For link colors, migrate to one of the following alternatives: + +```diff +.custom-link { +- color: var(--bui-fg-link); ++ color: var(--bui-fg-info); /* For informational links */ ++ /* or */ ++ color: var(--bui-fg-primary); /* For standard text links */ +} +``` + +For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors: + +```diff +.info-section { +- background: var(--bui-bg-tint); ++ background: var(--bui-bg-info); /* For informational sections */ ++ /* or */ ++ background: var(--bui-bg-neutral-on-surface-0); /* For neutral emphasis */ +} +``` + +If you're using status foreground colors on colored backgrounds, update to the new `-on-bg` tokens: + +```diff +.error-badge { +- color: var(--bui-fg-danger); ++ color: var(--bui-fg-danger-on-bg); + background: var(--bui-bg-danger); +} +``` + +**Affected components:** Link diff --git a/.changeset/yellow-years-run.md b/.changeset/yellow-years-run.md new file mode 100644 index 0000000000..fc57664911 --- /dev/null +++ b/.changeset/yellow-years-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Updated README for backend-dynamic-feature-service diff --git a/contrib/.devcontainer/Dockerfile b/.devcontainer/Dockerfile similarity index 78% rename from contrib/.devcontainer/Dockerfile rename to .devcontainer/Dockerfile index 6407aa26dd..d29ef36dc6 100644 --- a/contrib/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/typescript-node:20 +FROM mcr.microsoft.com/devcontainers/typescript-node:24 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install chromium \ diff --git a/.devcontainer/devcontainer.env b/.devcontainer/devcontainer.env new file mode 100644 index 0000000000..65350b61d4 --- /dev/null +++ b/.devcontainer/devcontainer.env @@ -0,0 +1 @@ +NODE_OPTIONS=--no-node-snapshot diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..08d02732be --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,61 @@ +{ + "name": "Backstage", + "forwardPorts": [3000, 7007], + "build": { "dockerfile": "Dockerfile" }, + "features": { + "ghcr.io/devcontainers/features/common-utils:2.5.5": {}, + "ghcr.io/devcontainers-contrib/features/mkdocs:2": {} + }, + "postCreateCommand": "bash .devcontainer/setup.sh", + "hostRequirements": { + "cpus": 2, + "memory": "4gb", + "storage": "32gb" + }, + "runArgs": [ + "--env-file", + ".devcontainer/devcontainer.env", + "--sysctl", + "net.ipv6.conf.all.disable_ipv6=1" + ], + "portsAttributes": { + "3000": { + "label": "Frontend port", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "7007": { + "label": "Backend port", + "onAutoForward": "silent", + "requireLocalPort": true + }, + "9464": { + "onAutoForward": "silent" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "Intility.vscode-backstage" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "files.eol": "\n", + "prettier.endOfLine": "lf", + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.sortMembers": "explicit" + } + } + } + } +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000000..7615e2b3cc --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,28 @@ +#!/bin/bash +echo "Installing Backstage dependencies..." + +yarn install +export VIRTUAL_ENV=$HOME/venv +python3 -m venv $VIRTUAL_ENV +export PATH="$VIRTUAL_ENV/bin:$PATH" +python3 -m pip install mkdocs-techdocs-core + +echo "" +echo "╔════════════════════════════════════════════════════════╗ " +echo "║ 🚀 Setup Complete! Ready to launch Backstage! ║ " +echo "╠════════════════════════════════════════════════════════╣ " +echo "║ ║ " +echo "║ Open a new terminal and run: ║ " +echo "║ ║ " +echo "║ yarn start ║ " +echo "║ ║ " +echo "║ Then access Backstage at: ║ " +echo "║ ║ " +echo "║ http://localhost:3000 ║ " +echo "║ ║ " +echo "║ You might need to refresh the page once backend ║ " +echo "║ is ready. ║ " +echo "║ ║ " +echo "║ Happy coding! 🎉 ║ " +echo "╚════════════════════════════════════════════════════════╝ " +echo "" \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ee3fe6fda0..6a5c5beb81 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -42,3 +42,15 @@ Never update ESLint, Prettier, or TypeScript configuration files unless specific ## Repository Structure See `/docs/contribute/project-structure.md` for a detailed description of the repository structure. + +## Pull Request Reviews + +When reviewing a pull request you MUST calculate a priority score between 0 and 100, where a higher score indicates higher priority. You MUST include the score as an HTML comment at the very beginning of the "PR Overview" section, using the following format: ``. + +The score MUST be calculated as a sum of the following: + +- **60% Guideline Adherence**: How well does the pull request follow the project guidelines in `/STYLE.md`, `/REVIEWING.md`, `/CONTRIBUTING.md`, and `/SECURITY.md`? A pull request that fully adheres to all guidelines scores 60 points, while one that violates multiple guidelines scores closer to 0. + +- **40% Complexity**: How straightforward is the pull request? A simple bug fix or documentation update scores the full 40 points. A complex API change, new feature, or architectural modification scores closer to 0. + +Higher priority pull requests (higher scores) are those that are both well-crafted according to project standards AND easy to review and merge quickly. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 62277b26d6..b8132eb0e6 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -345,6 +345,7 @@ parallelization param params parseable +passwordless Patrik pattison Peloton @@ -527,6 +528,7 @@ transpilers trixie truthy tsconfig +Turbopack TSDoc typeahead ui @@ -539,6 +541,7 @@ unmount unregister unregistering unregistration +unreviewed unstarred untemplated untracked diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 484d102b7a..75e0a759df 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -59,7 +59,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Build and push uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 08f6198757..909678d842 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/upload-sarif@439137e1b50c27ba9e2f9befc93e43091b449c34 # v3.32.0 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_pull-requests-trigger.yml b/.github/workflows/sync_pull-requests-trigger.yml new file mode 100644 index 0000000000..499e7a1e1d --- /dev/null +++ b/.github/workflows/sync_pull-requests-trigger.yml @@ -0,0 +1,65 @@ +name: Sync Pull Requests Trigger + +on: + pull_request_target: + types: [opened, synchronize, reopened, labeled, unlabeled] + pull_request_review: + types: [submitted, dismissed] + issue_comment: + types: [created] + +concurrency: + group: sync-pr-trigger-${{ github.event.pull_request.number || github.event.issue.number }} + cancel-in-progress: true + +jobs: + # Run directly for pull_request_target since it has secrets access + label: + if: > + github.repository == 'backstage/backstage' && + github.event.sender.type != 'Bot' && + github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + with: + egress-policy: audit + + - name: Backstage PR automation + uses: backstage/actions/pr-automation@c0110c14def021f64b4774335b0d55ac705b9318 # v0.7.1 + with: + app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} + private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} + installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} + project-owner: backstage + project-number: '14' + + # Dispatch event for other triggers to be processed with secrets access + trigger: + if: > + github.repository == 'backstage/backstage' && + github.event.sender.type != 'Bot' && + github.event_name != 'pull_request_target' && + (github.event.pull_request || github.event.issue.pull_request) + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + with: + egress-policy: audit + + - name: Dispatch sync event + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'sync-pull-requests', + client_payload: { + 'pr-number': String(context.payload.pull_request?.number || context.payload.issue?.number), + 'label-added': context.payload.action === 'labeled' ? context.payload.label?.name : '', + 'review-state': context.payload.review?.state || '', + }, + }); diff --git a/.github/workflows/sync_pull-requests.yml b/.github/workflows/sync_pull-requests.yml new file mode 100644 index 0000000000..af75906ac2 --- /dev/null +++ b/.github/workflows/sync_pull-requests.yml @@ -0,0 +1,30 @@ +name: Sync Pull Requests + +on: + repository_dispatch: + types: [sync-pull-requests] + +concurrency: + group: sync-pr-${{ github.event.client_payload.pr-number }} + cancel-in-progress: true + +jobs: + run: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 + with: + egress-policy: audit + + - name: Backstage PR automation + uses: backstage/actions/pr-automation@c0110c14def021f64b4774335b0d55ac705b9318 # v0.7.1 + with: + app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} + private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} + installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} + project-owner: backstage + project-number: '14' + pr-number: ${{ github.event.client_payload.pr-number }} + label-added: ${{ github.event.client_payload.label-added }} + review-state: ${{ github.event.client_payload.review-state }} diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 207ad64b49..f3e60a3b79 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/upload-sarif@439137e1b50c27ba9e2f9befc93e43091b449c34 # v3.32.0 with: sarif_file: snyk.sarif diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 20374b24cf..18f374e124 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -13,6 +13,10 @@ jobs: name: Create Changeset PR runs-on: ubuntu-latest + env: + CI: true + NODE_OPTIONS: --max-old-space-size=8192 + steps: - name: Harden Runner uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 8999824dd6..76a91d0ff3 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/init@439137e1b50c27ba9e2f9befc93e43091b449c34 # v3.32.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/autobuild@439137e1b50c27ba9e2f9befc93e43091b449c34 # v3.32.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4bdb89f48054571735e3792627da6195c57459e2 # v3.31.10 + uses: github/codeql-action/analyze@439137e1b50c27ba9e2f9befc93e43091b449c34 # v3.32.0 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 41ae7c06d0..1b98f8ec9a 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -66,6 +66,8 @@ jobs: - run: yarn tsc - run: yarn backstage-cli repo build + # BUI has a custom build script and needs to be built separately + - run: yarn --cwd packages/ui build - name: run E2E test run: | sudo sysctl fs.inotify.max_user_watches=524288 diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 6a7a4b2af7..d01b4f9c3e 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -37,7 +37,7 @@ jobs: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index bd9b190a54..773d7d89f0 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -58,7 +58,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: setup python - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.10' @@ -86,6 +86,8 @@ jobs: - run: yarn tsc - run: yarn backstage-cli repo build + # BUI has a custom build script and needs to be built separately + - run: yarn --cwd packages/ui build - name: run E2E test run: yarn e2e-test run env: diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 19e575cf46..a9883c6480 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -222,7 +222,7 @@ jobs: uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22.x - - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: '3.9' diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index 352c633a15..4f8b1b137d 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -259,13 +259,9 @@ --bui-fg-primary: var(--bui-white); --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: var(--bui-white); - --bui-fg-link-hover: var(--bui-white); --bui-fg-disabled: var(--bui-gray-5); --bui-fg-solid: var(--bui-black); --bui-fg-solid-disabled: #072f15; - --bui-fg-tint: var(--bui-white); - --bui-fg-tint-disabled: var(--bui-gray-5); --bui-fg-danger: #e22b2b; --bui-fg-warning: #e36d05; --bui-fg-success: #1db954; diff --git a/ADOPTERS.md b/ADOPTERS.md index 2b3cad0b25..b1b36bc155 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -91,7 +91,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | | [VMware](https://www.vmware.com) | [Waldir Montoya](https://github.com/waldirmontoya25), [Kris Applegate](https://github.com/krisapplegate), [Jamie Klassen](https://github.com/jamieklassen) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | | [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | -| [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | +| [IKEA Retail](https://www.ingka.com) | [Björn Ramberg](https://github.com/bjornramberg), [Martin Norin](https://github.com/martin-norin) | Centralized developer portal, supporting the internal engineering community with self serve access, documentation, templates, catalog and more. | | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | | [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | | [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@kurtaking](https://github.com/kurtaking), [@pc-bob](https://github.com/pc-bob) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80381417a0..46b11576eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -432,7 +432,7 @@ Once you've submitted a Pull Request (PR) the various bots will come out and do - checking for commits for their DCO (Developer Certificate of Origin) - kick off the various CI builds -Once these steps are completed, it's just a matter of being patient. As the reviewers have time, they will begin reviewing your PR. When the review process begins, there may be a few layers to this, but the general rule is that you need approval from one of the core maintainers and one from the specific area impacted by your PR. You may also have someone from the community review your changes. This can really help speed things up as they may catch some early items making the review for the maintainers simpler. Once you have the two (2) approvals, it's ready to be merged, a task that is also performed by the maintainers. +Once these steps are completed, it's just a matter of being patient. Reviews are coordinated by maintainers and reviewers based on project area ownership. Prioritization is driven by a number of different factors, but some important ones are size (smaller get higher priority) and alignment with current roadmap priorities. Timely responses to review comments also help keep the pull request moving faster. You may also have someone from the reviewers group review your pull request and request changes. Approval from a member of the reviewers group will greatly increase the priority of your pull request. ### Review Tips diff --git a/LABELS.md b/LABELS.md index f1af06d4fa..8e9a55feef 100644 --- a/LABELS.md +++ b/LABELS.md @@ -97,6 +97,27 @@ These labels indicate a workflow status for the owners. - `stale` - The issue or pull request has not seen any activity for a while and will be closed if no further activity is seen. - `no stale` - The issue or pull request should not be closed due to inactivity. +## Pull Request Labels + +The following labels indicate the status of a pull request: + +- `waiting-for:review` - The pull request needs a review and will be visible in the review queue unless already assigned an owner. +- `waiting-for:author` - Changes have been requested by a reviewer and the pull request will not receive a review until the changes are made. A comment made on the pull request by the author will also push it back into the review queue. +- `waiting-for:decision` - The pull request has been marked as more complex and needs a decision from the owners. Progress can still be made and discussion can continue, but expect the review to take longer. These pull requests are often good candidates to bring to a [SIG](https://github.com/backstage/community/tree/main/sigs) meeting. +- `waiting-for:merge` - The pull request has been approved and is awaiting merge. If you have write access and authored the pull request you can merge it yourself. If you do not have access to merge and the pull request has not been merged within a day of approval, please notify the assigned reviewer. + +The following labels indicate the size of a pull request: + +- `size:tiny` - Tiny pull requests receive a higher priority for reviews. +- `size:small` - Small pull requests receive a slightly higher priority for reviews. +- `size:medium` - Medium-sized pull requests receive no change in priority for reviews. +- `size:large` - Large pull requests receive a slightly lower priority for reviews. +- `size:huge` - Huge pull requests receive a lower priority for reviews. + +The following additional labels also apply to pull requests: + +- `reviewer-approved` - The pull request has been approved by a member of the reviewers group. This pull request receives a much higher priority for reviews. + ## Common Issue Filters This is a collection of common issue filters that can help you find issues that you are looking for or that match your interests and skills. diff --git a/OWNERS.md b/OWNERS.md index 214b76a40e..91bbebcd72 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -19,7 +19,7 @@ These are the separate project areas of Backstage, each with their own project a ### Auth -Team: @backstage/auth-maintainers +Team: @backstage/auth-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Aauth-maintainers) Scope: The Backstage auth plugin and modules, as well as client-side implementations. @@ -34,7 +34,7 @@ Scope: The Backstage auth plugin and modules, as well as client-side implementat ### Catalog -Team: @backstage/catalog-maintainers +Team: @backstage/catalog-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Acatalog-maintainers) Scope: The catalog plugin and catalog model @@ -63,7 +63,7 @@ Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugi ### Design System -Team: @backstage/design-system-maintainers +Team: @backstage/design-system-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Adesign-system-maintainers) Scope: The Backstage design system, component library, as well as surrounding tooling such as Storybook. @@ -75,7 +75,7 @@ Scope: The Backstage design system, component library, as well as surrounding to ### Documentation -Team: @backstage/documentation-maintainers +Team: @backstage/documentation-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Adocumentation-maintainers) Scope: The Backstage Documentation and Microsite, excluding the plugins listing @@ -87,7 +87,7 @@ Scope: The Backstage Documentation and Microsite, excluding the plugins listing ### Framework -Team: @backstage/framework-maintainers +Team: @backstage/framework-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Aframework-maintainers) Scope: The Backstage core framework, including all revisions of the frontend and backend systems, as well as the App plugin. @@ -114,7 +114,7 @@ Scope: The Backstage [Helm Chart(s)](https://github.com/backstage/charts). ### Home -Team: @backstage/home-maintainers +Team: @backstage/home-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Ahome-maintainers) Scope: The Backstage home page and information architecture @@ -129,7 +129,7 @@ Scope: The Backstage home page and information architecture ### Kubernetes -Team: @backstage/kubernetes-maintainers +Team: @backstage/kubernetes-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Akubernetes-maintainers) Scope: The Kubernetes plugin and the base it provides for other plugins to build upon. @@ -139,7 +139,7 @@ Scope: The Kubernetes plugin and the base it provides for other plugins to build ### Operations -Team: @backstage/operations-maintainers +Team: @backstage/operations-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Aoperations-maintainers) Scope: The management and operation of the main Backstage repository and release process, along with the surrounding tooling. @@ -154,7 +154,7 @@ Scope: The management and operation of the main Backstage repository and release ### Permission Framework -Team: @backstage/permission-maintainers +Team: @backstage/permission-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Apermission-maintainers) Scope: The Permission Framework and plugins integrating with the permission framework @@ -169,7 +169,7 @@ Scope: The Permission Framework and plugins integrating with the permission fram ### Search -Team: @backstage/search-maintainers +Team: @backstage/search-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Asearch-maintainers) Scope: The Backstage Search plugin @@ -181,7 +181,7 @@ Scope: The Backstage Search plugin ### TechDocs -Team: @backstage/techdocs-maintainers +Team: @backstage/techdocs-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Atechdocs-maintainers) Scope: The TechDocs plugin and related tooling @@ -195,7 +195,7 @@ Scope: The TechDocs plugin and related tooling ### Tooling -Team: @backstage/tooling-maintainers +Team: @backstage/tooling-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Atooling-maintainers) Scope: All published Backstage CLI tools in the main `backstage` repository that do not belong to other areas, including `@backstage/cli` and `@backstage/repo-tools`. @@ -215,7 +215,7 @@ These incubating project areas have shared ownership with @backstage/maintainers ### Auditor -Team: @backstage/auditor-maintainers +Team: @backstage/auditor-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Aauditor-maintainers) Scope: The auditor core service, along with auditor usage in the main repository @@ -225,7 +225,7 @@ Scope: The auditor core service, along with auditor usage in the main repository ### Events -Team: @backstage/events-maintainers +Team: @backstage/events-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Aevents-maintainers) Scope: The Events plugin and library, along with events modules in the main repository @@ -235,7 +235,7 @@ Scope: The Events plugin and library, along with events modules in the main repo ### Notifications -Team: @backstage/notifications-maintainers +Team: @backstage/notifications-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Anotifications-maintainers) Scope: The Notifications and Signals plugins and libraries @@ -245,7 +245,7 @@ Scope: The Notifications and Signals plugins and libraries ### OpenAPI Tooling -Team: @backstage/openapi-tooling-maintainers +Team: @backstage/openapi-tooling-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Aopenapi-tooling-maintainers) Scope: Tooling for frontend and backend schema-first OpenAPI development. @@ -255,7 +255,7 @@ Scope: Tooling for frontend and backend schema-first OpenAPI development. ### Scaffolder -Team: @backstage/scaffolder-maintainers +Team: @backstage/scaffolder-maintainers — [Review board](https://github.com/orgs/backstage/projects/14/views/1?filterQuery=no%3Aassignee+label%3Astatus%3Aneeds-review+reviewers%3Ascaffolder-maintainers) Scope: The Scaffolder frontend and backend plugins, and related tooling. diff --git a/REVIEWING.md b/REVIEWING.md index 27da917805..35638797e6 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -2,6 +2,38 @@ This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to this repository. +## Quick links + +- [Incoming Reviews](https://github.com/orgs/backstage/projects/14/views/1) +- [Personal Reviews](https://github.com/orgs/backstage/projects/14/views/2) +- Project area boards in [OWNERS.md](./OWNERS.md) + +## Review Workflow + +Pull request reviews are coordinated manually by maintainers and reviewers based on project area ownership. Reviews are prioritized and tracked using labels and GitHub Project boards. This review process applies to all pull requests in the [main Backstage repository](https://github.com/backstage/backstage), other repositories may have their own review processes. + +All incoming pull request reviews are tracked on the [Incoming Reviews board](https://github.com/orgs/backstage/projects/14/views/1). This board can be used by members of the `@backstage/reviewers` group to find pull requests to review, as well as maintainers that want an overview of all incoming pull requests. + +Project area maintainers can use the same board but with additional filters applied to only show incoming pull requests for their project area. These filtered boards are linked to for each project area in [OWNERS.md](./OWNERS.md). There is also a [personal review board](https://github.com/orgs/backstage/projects/14/views/2) that can be used to track reviews that you have been assigned to. + +There are several labels that help track the status and prioritize pull requests. You can find more information about these labels in [LABELS.md](./LABELS.md#pull-request-labels). + +Reviewers should use the "request changes" option when they believe changes are necessary before the pull request can be merged. This is both to clarify to the author that the pull request is not ready to be merged, but also to update the status of the pull request in the review queue. If you simply want to contribute to the discussion in a pull request, or have minor or optional suggestions, you can just leave a regular comment. + +### Review process for @backstage/reviewers + +Members of the `@backstage/reviewers` do not have any specific areas that they should focus on, they can choose to filter and focus on reviews from any part of the project. They do not assign themselves specific pull requests, but instead leave reviews directly on pull requests without any further process. + +Reviews from this group still have meaningful impact on the review process, as they are picked up by project automation. Approving reviews will add the `reviewer-approved` label to the pull request, which greatly increases its priority and visibility for owners in the area. Likewise, requesting changes will add the `waiting-for:author` label to the pull request, which will remove it from the review queue until the author has commented or made changes. + +### Review process for Project Area Maintainers + +Project area maintainers are responsible for reviewing and ultimately merging or closing pull requests towards their project area. They should use the global or filtered board for their project area to find pull requests to review. When they find a pull request that they want to review, they should assign themselves to the pull request, removing it from the review queue and placing it on their personal review board. + +Once a pull request has been assigned a single owner, it is their responsibility to review and eventually merge or close the pull request. They manage all ongoing requests on their personal review board, typically prioritizing the ones at the top of the board marked with `waiting-for:review`. If a pull request is left unreviewed for too long, it will automatically be unassigned and returned to the review queue. Once a pull request has been approved by the assigned owner, the owner should merge the pull request themselves if it is an outside contribution, but otherwise generally leave that to the author of the PR. + +Some pull requests may require review from multiple project areas. In these cases the most relevant owner should assign themselves and coordinate with the other owners for additional reviews. If the most relevant owner is not clear, this is preferably solved in a discussion among the owners. Frequent conflicts should lead to a discussion whether `CODEOWNERS` should be updated to simplify the review process. If some owners are currently unavailable, other owners can assign themselves to the pull request and bring it to the point where they approve the changes for their area, and then send the pull request back to the review queue. + ## Code Style See our code style documented at [STYLE.md](./STYLE.md). diff --git a/contrib/.devcontainer/README.md b/contrib/.devcontainer/README.md deleted file mode 100644 index 4fca39b3ad..0000000000 --- a/contrib/.devcontainer/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Devcontainer Configuration - -[Devcontainer](https://code.visualstudio.com/docs/devcontainers/containers) is a VSCode feature that allows developers -to share a common workspace definition, by leveraging Docker to run the workspace, and using VSCode Remote Development extension, you can make sure the developer experience is the same across different computers - -## Benefits - -- Developers only need to have VSCode and Docker installed -- All dependencies necessary to work in the repository are contained in the docker image -- Makes it easier for new developers to start contributing to the project -- No extra setup required - -## Known Issues - -- Performance is a known issue. Working inside devcontainers require developers to have a powerful computer. See [System Requirements](https://code.visualstudio.com/docs/devcontainers/containers#_system-requirements) - -## Usage - -Just copy the .devcontainer folder to the root of the directory, and VSCode will automatically detect the configuration and suggest that you re-open the repository using Devcontainer diff --git a/contrib/.devcontainer/devcontainer.json b/contrib/.devcontainer/devcontainer.json deleted file mode 100644 index 14e7d7f0ae..0000000000 --- a/contrib/.devcontainer/devcontainer.json +++ /dev/null @@ -1,27 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node -{ - "name": "Base Backstage Workspace", - "build": { "dockerfile": "Dockerfile" }, - "features": { - "ghcr.io/devcontainers/features/common-utils:1": {}, - "ghcr.io/devcontainers/features/docker-from-docker:1": {}, - "ghcr.io/devcontainers-contrib/features/mkdocs:2": {} - }, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [3000, 7007], - - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bash .devcontainer/postCreate.sh", - - // Configure tool-specific properties. - "customizations": { - "vscode": { - "extensions": ["Intility.vscode-backstage"] - } - } - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/contrib/.devcontainer/postCreate.sh b/contrib/.devcontainer/postCreate.sh deleted file mode 100755 index 88f403de5e..0000000000 --- a/contrib/.devcontainer/postCreate.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -yarn install -export VIRTUAL_ENV=$HOME/venv -python3 -m venv $VIRTUAL_ENV -export PATH="$VIRTUAL_ENV/bin:$PATH" -python3 -m pip install mkdocs-techdocs-core \ No newline at end of file diff --git a/docs-ui/.eslintrc.json b/docs-ui/.eslintrc.json deleted file mode 100644 index 5c40e04dde..0000000000 --- a/docs-ui/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "rules": { - "notice/notice": "off", - "react/forbid-elements": "off", - "jsx-a11y/alt-text": "off" - } -} diff --git a/docs-ui/eslint.config.mjs b/docs-ui/eslint.config.mjs new file mode 100644 index 0000000000..2cb66fd509 --- /dev/null +++ b/docs-ui/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'; + +const eslintConfig = defineConfig([ + ...nextCoreWebVitals, + globalIgnores(['.next/**', 'dist/**', 'node_modules/**']), + { + rules: { + 'notice/notice': 'off', + 'react/forbid-elements': 'off', + 'jsx-a11y/alt-text': 'off', + }, + }, +]); + +export default eslintConfig; diff --git a/docs-ui/package.json b/docs-ui/package.json index 861ba500bf..bb0b1a393d 100644 --- a/docs-ui/package.json +++ b/docs-ui/package.json @@ -5,7 +5,7 @@ "scripts": { "prebuild": "yarn sync:css", "build": "next build", - "lint": "next lint", + "lint": "eslint .", "prestart": "yarn sync:css", "start": "next dev", "sync:changelog": "node scripts/sync-changelog.mjs", @@ -47,7 +47,7 @@ "@types/react": "19.2.10", "@types/react-dom": "19.2.3", "chokidar": "^3.6.0", - "eslint": "^8", + "eslint": "^9", "eslint-config-next": "16.1.6", "lightningcss": "^1.28.2", "typescript": "^5", diff --git a/docs-ui/public/theme-backstage.css b/docs-ui/public/theme-backstage.css deleted file mode 100644 index 2f444f2257..0000000000 --- a/docs-ui/public/theme-backstage.css +++ /dev/null @@ -1 +0,0 @@ -@layer tokens{:root{--bui-font-regular:system-ui;--bui-font-monospace:ui-monospace,"Menlo","Monaco","Consolas","Liberation Mono","Courier New",monospace;--bui-font-weight-regular:400;--bui-font-weight-bold:600;--bui-font-size-1:.625rem;--bui-font-size-2:.75rem;--bui-font-size-3:.875rem;--bui-font-size-4:1rem;--bui-font-size-5:1.25rem;--bui-font-size-6:1.5rem;--bui-font-size-7:2rem;--bui-font-size-8:3rem;--bui-font-size-9:4rem;--bui-font-size-10:5.75rem;--bui-space:.25rem;--bui-space-0_5:calc(var(--bui-space)*.5);--bui-space-1:var(--bui-space);--bui-space-1_5:calc(var(--bui-space)*1.5);--bui-space-2:calc(var(--bui-space)*2);--bui-space-3:calc(var(--bui-space)*3);--bui-space-4:calc(var(--bui-space)*4);--bui-space-5:calc(var(--bui-space)*5);--bui-space-6:calc(var(--bui-space)*6);--bui-space-7:calc(var(--bui-space)*7);--bui-space-8:calc(var(--bui-space)*8);--bui-space-9:calc(var(--bui-space)*9);--bui-space-10:calc(var(--bui-space)*10);--bui-space-11:calc(var(--bui-space)*11);--bui-space-12:calc(var(--bui-space)*12);--bui-space-13:calc(var(--bui-space)*13);--bui-space-14:calc(var(--bui-space)*14);--bui-radius-1:calc(.125rem);--bui-radius-2:calc(.25rem);--bui-radius-3:calc(.5rem);--bui-radius-4:calc(.75rem);--bui-radius-5:calc(1rem);--bui-radius-6:calc(1.25rem);--bui-radius-full:9999px;--bui-black:#000;--bui-white:#fff;--bui-gray-1:#f8f8f8;--bui-gray-2:#ececec;--bui-gray-3:#d9d9d9;--bui-gray-4:#c1c1c1;--bui-gray-5:#9e9e9e;--bui-gray-6:#8c8c8c;--bui-gray-7:#757575;--bui-gray-8:#595959;--bui-bg-surface-0:var(--bui-gray-1);--bui-bg-surface-1:var(--bui-white);--bui-bg-surface-2:var(--bui-gray-1);--bui-bg-surface-3:var(--bui-gray-2);--bui-bg-solid:#1f5493;--bui-bg-solid-hover:#163a66;--bui-bg-solid-pressed:#0f2b4e;--bui-bg-solid-disabled:#163a66;--bui-bg-neutral-on-surface-0:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-0-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-0-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-0-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-1-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-2-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-2-disabled:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3:oklch(0% 0 0/.06);--bui-bg-neutral-on-surface-3-hover:oklch(0% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(0% 0 0/.16);--bui-bg-neutral-on-surface-3-disabled:oklch(0% 0 0/.06);--bui-bg-danger:#ffe2e2;--bui-bg-warning:#ffedd5;--bui-bg-success:#dcfce7;--bui-bg-info:#dbeafe;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#1f5493;--bui-fg-link-hover:#1f2d5c;--bui-fg-disabled:#9e9e9e;--bui-fg-solid:var(--bui-white);--bui-fg-solid-disabled:#98a8bc;--bui-fg-tint:#1f5493;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#991919;--bui-fg-warning:#92310a;--bui-fg-success:#116932;--bui-fg-info:#173da6;--bui-border:#0000001a;--bui-border-hover:#0003;--bui-border-pressed:#0006;--bui-border-disabled:#0000001a;--bui-border-info:#7ea9d6;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#a0a0a03b;--bui-scrollbar-thumb:#a0a0a0;--bui-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--bui-animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite}[data-theme-mode=dark]{--bui-gray-1:#191919;--bui-gray-2:#242424;--bui-gray-3:#373737;--bui-gray-4:#464646;--bui-gray-5:#575757;--bui-gray-6:#7b7b7b;--bui-gray-7:#9e9e9e;--bui-gray-8:#b4b4b4;--bui-bg-surface-0:#333;--bui-bg-surface-1:var(--bui-gray-1);--bui-bg-surface-2:var(--bui-gray-2);--bui-bg-surface-3:var(--bui-gray-3);--bui-bg-solid:#9cc9ff;--bui-bg-solid-hover:#83b9fd;--bui-bg-solid-pressed:#83b9fd;--bui-bg-solid-disabled:#1b3d68;--bui-bg-neutral-on-surface-0:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-0-hover:oklch(100% 0 0/.14);--bui-bg-neutral-on-surface-0-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-0-disabled:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-1-hover:oklch(100% 0 0/.1);--bui-bg-neutral-on-surface-1-pressed:oklch(100% 0 0/.16);--bui-bg-neutral-on-surface-1-disabled:oklch(100% 0 0/.06);--bui-bg-neutral-on-surface-2:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-2-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-2-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-2-disabled:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3:oklch(100% 0 0/.08);--bui-bg-neutral-on-surface-3-hover:oklch(100% 0 0/.12);--bui-bg-neutral-on-surface-3-pressed:oklch(100% 0 0/.2);--bui-bg-neutral-on-surface-3-disabled:oklch(100% 0 0/.08);--bui-bg-danger:#300c0c;--bui-bg-warning:#302008;--bui-bg-success:#042713;--bui-bg-info:#132049;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:#9cc9ff;--bui-fg-link-hover:#7eb5f7;--bui-fg-disabled:var(--bui-gray-7);--bui-fg-solid:#101821;--bui-fg-solid-disabled:#6191cc;--bui-fg-tint:#9cc9ff;--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#fca5a5;--bui-fg-warning:#fdba74;--bui-fg-success:#86efac;--bui-fg-info:#a3cfff;--bui-border:#ffffff1f;--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-info:#7ea9d6;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#1f5493;--bui-scrollbar:#3636363a;--bui-scrollbar-thumb:#575757;--bui-shadow:none}}@layer base{*,:before,:after{box-sizing:border-box}html{-webkit-text-size-adjust:100%;tab-size:4;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;line-height:1.15}body{margin:0}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{border-color:currentColor}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}:where(a){color:inherit;text-decoration:none}@keyframes pulse{50%{opacity:.5}}body{background-color:var(--bui-bg-surface-0);color:var(--bui-fg-primary);font-family:var(--bui-font-regular);font-weight:var(--bui-font-weight-regular);font-size:var(--bui-font-size-3);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-theme-mode=dark]{color-scheme:dark}[data-theme-mode=light]{color-scheme:light}}@layer components;@layer utilities{.bui-p{padding:var(--p)}.bui-p-0\.5{padding:var(--bui-space-0_5)}.bui-p-1{padding:var(--bui-space-1)}.bui-p-1\.5{padding:var(--bui-space-1_5)}.bui-p-2{padding:var(--bui-space-2)}.bui-p-3{padding:var(--bui-space-3)}.bui-p-4{padding:var(--bui-space-4)}.bui-p-5{padding:var(--bui-space-5)}.bui-p-6{padding:var(--bui-space-6)}.bui-p-7{padding:var(--bui-space-7)}.bui-p-8{padding:var(--bui-space-8)}.bui-p-9{padding:var(--bui-space-9)}.bui-p-10{padding:var(--bui-space-10)}.bui-p-11{padding:var(--bui-space-11)}.bui-p-12{padding:var(--bui-space-12)}.bui-p-13{padding:var(--bui-space-13)}.bui-p-14{padding:var(--bui-space-14)}@media (width>=640px){.xs\:bui-p{padding:var(--p-xs)}.xs\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xs\:bui-p-1{padding:var(--bui-space-1)}.xs\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xs\:bui-p-2{padding:var(--bui-space-2)}.xs\:bui-p-3{padding:var(--bui-space-3)}.xs\:bui-p-4{padding:var(--bui-space-4)}.xs\:bui-p-5{padding:var(--bui-space-5)}.xs\:bui-p-6{padding:var(--bui-space-6)}.xs\:bui-p-7{padding:var(--bui-space-7)}.xs\:bui-p-8{padding:var(--bui-space-8)}.xs\:bui-p-9{padding:var(--bui-space-9)}.xs\:bui-p-10{padding:var(--bui-space-10)}.xs\:bui-p-11{padding:var(--bui-space-11)}.xs\:bui-p-12{padding:var(--bui-space-12)}.xs\:bui-p-13{padding:var(--bui-space-13)}.xs\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-p{padding:var(--p-sm)}.sm\:bui-p-0\.5{padding:var(--bui-space-0_5)}.sm\:bui-p-1{padding:var(--bui-space-1)}.sm\:bui-p-1\.5{padding:var(--bui-space-1_5)}.sm\:bui-p-2{padding:var(--bui-space-2)}.sm\:bui-p-3{padding:var(--bui-space-3)}.sm\:bui-p-4{padding:var(--bui-space-4)}.sm\:bui-p-5{padding:var(--bui-space-5)}.sm\:bui-p-6{padding:var(--bui-space-6)}.sm\:bui-p-7{padding:var(--bui-space-7)}.sm\:bui-p-8{padding:var(--bui-space-8)}.sm\:bui-p-9{padding:var(--bui-space-9)}.sm\:bui-p-10{padding:var(--bui-space-10)}.sm\:bui-p-11{padding:var(--bui-space-11)}.sm\:bui-p-12{padding:var(--bui-space-12)}.sm\:bui-p-13{padding:var(--bui-space-13)}.sm\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-p{padding:var(--p-md)}.md\:bui-p-0\.5{padding:var(--bui-space-0_5)}.md\:bui-p-1{padding:var(--bui-space-1)}.md\:bui-p-1\.5{padding:var(--bui-space-1_5)}.md\:bui-p-2{padding:var(--bui-space-2)}.md\:bui-p-3{padding:var(--bui-space-3)}.md\:bui-p-4{padding:var(--bui-space-4)}.md\:bui-p-5{padding:var(--bui-space-5)}.md\:bui-p-6{padding:var(--bui-space-6)}.md\:bui-p-7{padding:var(--bui-space-7)}.md\:bui-p-8{padding:var(--bui-space-8)}.md\:bui-p-9{padding:var(--bui-space-9)}.md\:bui-p-10{padding:var(--bui-space-10)}.md\:bui-p-11{padding:var(--bui-space-11)}.md\:bui-p-12{padding:var(--bui-space-12)}.md\:bui-p-13{padding:var(--bui-space-13)}.md\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-p{padding:var(--p-lg)}.lg\:bui-p-0\.5{padding:var(--bui-space-0_5)}.lg\:bui-p-1{padding:var(--bui-space-1)}.lg\:bui-p-1\.5{padding:var(--bui-space-1_5)}.lg\:bui-p-2{padding:var(--bui-space-2)}.lg\:bui-p-3{padding:var(--bui-space-3)}.lg\:bui-p-4{padding:var(--bui-space-4)}.lg\:bui-p-5{padding:var(--bui-space-5)}.lg\:bui-p-6{padding:var(--bui-space-6)}.lg\:bui-p-7{padding:var(--bui-space-7)}.lg\:bui-p-8{padding:var(--bui-space-8)}.lg\:bui-p-9{padding:var(--bui-space-9)}.lg\:bui-p-10{padding:var(--bui-space-10)}.lg\:bui-p-11{padding:var(--bui-space-11)}.lg\:bui-p-12{padding:var(--bui-space-12)}.lg\:bui-p-13{padding:var(--bui-space-13)}.lg\:bui-p-14{padding:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-p{padding:var(--p-xl)}.xl\:bui-p-0\.5{padding:var(--bui-space-0_5)}.xl\:bui-p-1{padding:var(--bui-space-1)}.xl\:bui-p-1\.5{padding:var(--bui-space-1_5)}.xl\:bui-p-2{padding:var(--bui-space-2)}.xl\:bui-p-3{padding:var(--bui-space-3)}.xl\:bui-p-4{padding:var(--bui-space-4)}.xl\:bui-p-5{padding:var(--bui-space-5)}.xl\:bui-p-6{padding:var(--bui-space-6)}.xl\:bui-p-7{padding:var(--bui-space-7)}.xl\:bui-p-8{padding:var(--bui-space-8)}.xl\:bui-p-9{padding:var(--bui-space-9)}.xl\:bui-p-10{padding:var(--bui-space-10)}.xl\:bui-p-11{padding:var(--bui-space-11)}.xl\:bui-p-12{padding:var(--bui-space-12)}.xl\:bui-p-13{padding:var(--bui-space-13)}.xl\:bui-p-14{padding:var(--bui-space-14)}}.bui-pl{padding-left:var(--pl)}.bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.bui-pl-1{padding-left:var(--bui-space-1)}.bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.bui-pl-2{padding-left:var(--bui-space-2)}.bui-pl-3{padding-left:var(--bui-space-3)}.bui-pl-4{padding-left:var(--bui-space-4)}.bui-pl-5{padding-left:var(--bui-space-5)}.bui-pl-6{padding-left:var(--bui-space-6)}.bui-pl-7{padding-left:var(--bui-space-7)}.bui-pl-8{padding-left:var(--bui-space-8)}.bui-pl-9{padding-left:var(--bui-space-9)}.bui-pl-10{padding-left:var(--bui-space-10)}.bui-pl-11{padding-left:var(--bui-space-11)}.bui-pl-12{padding-left:var(--bui-space-12)}.bui-pl-13{padding-left:var(--bui-space-13)}.bui-pl-14{padding-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pl{padding-left:var(--pl-xs)}.xs\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xs\:bui-pl-1{padding-left:var(--bui-space-1)}.xs\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xs\:bui-pl-2{padding-left:var(--bui-space-2)}.xs\:bui-pl-3{padding-left:var(--bui-space-3)}.xs\:bui-pl-4{padding-left:var(--bui-space-4)}.xs\:bui-pl-5{padding-left:var(--bui-space-5)}.xs\:bui-pl-6{padding-left:var(--bui-space-6)}.xs\:bui-pl-7{padding-left:var(--bui-space-7)}.xs\:bui-pl-8{padding-left:var(--bui-space-8)}.xs\:bui-pl-9{padding-left:var(--bui-space-9)}.xs\:bui-pl-10{padding-left:var(--bui-space-10)}.xs\:bui-pl-11{padding-left:var(--bui-space-11)}.xs\:bui-pl-12{padding-left:var(--bui-space-12)}.xs\:bui-pl-13{padding-left:var(--bui-space-13)}.xs\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pl{padding-left:var(--pl-sm)}.sm\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.sm\:bui-pl-1{padding-left:var(--bui-space-1)}.sm\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.sm\:bui-pl-2{padding-left:var(--bui-space-2)}.sm\:bui-pl-3{padding-left:var(--bui-space-3)}.sm\:bui-pl-4{padding-left:var(--bui-space-4)}.sm\:bui-pl-5{padding-left:var(--bui-space-5)}.sm\:bui-pl-6{padding-left:var(--bui-space-6)}.sm\:bui-pl-7{padding-left:var(--bui-space-7)}.sm\:bui-pl-8{padding-left:var(--bui-space-8)}.sm\:bui-pl-9{padding-left:var(--bui-space-9)}.sm\:bui-pl-10{padding-left:var(--bui-space-10)}.sm\:bui-pl-11{padding-left:var(--bui-space-11)}.sm\:bui-pl-12{padding-left:var(--bui-space-12)}.sm\:bui-pl-13{padding-left:var(--bui-space-13)}.sm\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pl{padding-left:var(--pl-md)}.md\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.md\:bui-pl-1{padding-left:var(--bui-space-1)}.md\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.md\:bui-pl-2{padding-left:var(--bui-space-2)}.md\:bui-pl-3{padding-left:var(--bui-space-3)}.md\:bui-pl-4{padding-left:var(--bui-space-4)}.md\:bui-pl-5{padding-left:var(--bui-space-5)}.md\:bui-pl-6{padding-left:var(--bui-space-6)}.md\:bui-pl-7{padding-left:var(--bui-space-7)}.md\:bui-pl-8{padding-left:var(--bui-space-8)}.md\:bui-pl-9{padding-left:var(--bui-space-9)}.md\:bui-pl-10{padding-left:var(--bui-space-10)}.md\:bui-pl-11{padding-left:var(--bui-space-11)}.md\:bui-pl-12{padding-left:var(--bui-space-12)}.md\:bui-pl-13{padding-left:var(--bui-space-13)}.md\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pl{padding-left:var(--pl-lg)}.lg\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.lg\:bui-pl-1{padding-left:var(--bui-space-1)}.lg\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.lg\:bui-pl-2{padding-left:var(--bui-space-2)}.lg\:bui-pl-3{padding-left:var(--bui-space-3)}.lg\:bui-pl-4{padding-left:var(--bui-space-4)}.lg\:bui-pl-5{padding-left:var(--bui-space-5)}.lg\:bui-pl-6{padding-left:var(--bui-space-6)}.lg\:bui-pl-7{padding-left:var(--bui-space-7)}.lg\:bui-pl-8{padding-left:var(--bui-space-8)}.lg\:bui-pl-9{padding-left:var(--bui-space-9)}.lg\:bui-pl-10{padding-left:var(--bui-space-10)}.lg\:bui-pl-11{padding-left:var(--bui-space-11)}.lg\:bui-pl-12{padding-left:var(--bui-space-12)}.lg\:bui-pl-13{padding-left:var(--bui-space-13)}.lg\:bui-pl-14{padding-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pl{padding-left:var(--pl-xl)}.xl\:bui-pl-0\.5{padding-left:var(--bui-space-0_5)}.xl\:bui-pl-1{padding-left:var(--bui-space-1)}.xl\:bui-pl-1\.5{padding-left:var(--bui-space-1_5)}.xl\:bui-pl-2{padding-left:var(--bui-space-2)}.xl\:bui-pl-3{padding-left:var(--bui-space-3)}.xl\:bui-pl-4{padding-left:var(--bui-space-4)}.xl\:bui-pl-5{padding-left:var(--bui-space-5)}.xl\:bui-pl-6{padding-left:var(--bui-space-6)}.xl\:bui-pl-7{padding-left:var(--bui-space-7)}.xl\:bui-pl-8{padding-left:var(--bui-space-8)}.xl\:bui-pl-9{padding-left:var(--bui-space-9)}.xl\:bui-pl-10{padding-left:var(--bui-space-10)}.xl\:bui-pl-11{padding-left:var(--bui-space-11)}.xl\:bui-pl-12{padding-left:var(--bui-space-12)}.xl\:bui-pl-13{padding-left:var(--bui-space-13)}.xl\:bui-pl-14{padding-left:var(--bui-space-14)}}.bui-pr{padding-right:var(--pr)}.bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.bui-pr-1{padding-right:var(--bui-space-1)}.bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.bui-pr-2{padding-right:var(--bui-space-2)}.bui-pr-3{padding-right:var(--bui-space-3)}.bui-pr-4{padding-right:var(--bui-space-4)}.bui-pr-5{padding-right:var(--bui-space-5)}.bui-pr-6{padding-right:var(--bui-space-6)}.bui-pr-7{padding-right:var(--bui-space-7)}.bui-pr-8{padding-right:var(--bui-space-8)}.bui-pr-9{padding-right:var(--bui-space-9)}.bui-pr-10{padding-right:var(--bui-space-10)}.bui-pr-11{padding-right:var(--bui-space-11)}.bui-pr-12{padding-right:var(--bui-space-12)}.bui-pr-13{padding-right:var(--bui-space-13)}.bui-pr-14{padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pr{padding-right:var(--pr-xs)}.xs\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xs\:bui-pr-1{padding-right:var(--bui-space-1)}.xs\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xs\:bui-pr-2{padding-right:var(--bui-space-2)}.xs\:bui-pr-3{padding-right:var(--bui-space-3)}.xs\:bui-pr-4{padding-right:var(--bui-space-4)}.xs\:bui-pr-5{padding-right:var(--bui-space-5)}.xs\:bui-pr-6{padding-right:var(--bui-space-6)}.xs\:bui-pr-7{padding-right:var(--bui-space-7)}.xs\:bui-pr-8{padding-right:var(--bui-space-8)}.xs\:bui-pr-9{padding-right:var(--bui-space-9)}.xs\:bui-pr-10{padding-right:var(--bui-space-10)}.xs\:bui-pr-11{padding-right:var(--bui-space-11)}.xs\:bui-pr-12{padding-right:var(--bui-space-12)}.xs\:bui-pr-13{padding-right:var(--bui-space-13)}.xs\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pr{padding-right:var(--pr-sm)}.sm\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.sm\:bui-pr-1{padding-right:var(--bui-space-1)}.sm\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.sm\:bui-pr-2{padding-right:var(--bui-space-2)}.sm\:bui-pr-3{padding-right:var(--bui-space-3)}.sm\:bui-pr-4{padding-right:var(--bui-space-4)}.sm\:bui-pr-5{padding-right:var(--bui-space-5)}.sm\:bui-pr-6{padding-right:var(--bui-space-6)}.sm\:bui-pr-7{padding-right:var(--bui-space-7)}.sm\:bui-pr-8{padding-right:var(--bui-space-8)}.sm\:bui-pr-9{padding-right:var(--bui-space-9)}.sm\:bui-pr-10{padding-right:var(--bui-space-10)}.sm\:bui-pr-11{padding-right:var(--bui-space-11)}.sm\:bui-pr-12{padding-right:var(--bui-space-12)}.sm\:bui-pr-13{padding-right:var(--bui-space-13)}.sm\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pr{padding-right:var(--pr-md)}.md\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.md\:bui-pr-1{padding-right:var(--bui-space-1)}.md\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.md\:bui-pr-2{padding-right:var(--bui-space-2)}.md\:bui-pr-3{padding-right:var(--bui-space-3)}.md\:bui-pr-4{padding-right:var(--bui-space-4)}.md\:bui-pr-5{padding-right:var(--bui-space-5)}.md\:bui-pr-6{padding-right:var(--bui-space-6)}.md\:bui-pr-7{padding-right:var(--bui-space-7)}.md\:bui-pr-8{padding-right:var(--bui-space-8)}.md\:bui-pr-9{padding-right:var(--bui-space-9)}.md\:bui-pr-10{padding-right:var(--bui-space-10)}.md\:bui-pr-11{padding-right:var(--bui-space-11)}.md\:bui-pr-12{padding-right:var(--bui-space-12)}.md\:bui-pr-13{padding-right:var(--bui-space-13)}.md\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pr{padding-right:var(--pr-lg)}.lg\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.lg\:bui-pr-1{padding-right:var(--bui-space-1)}.lg\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.lg\:bui-pr-2{padding-right:var(--bui-space-2)}.lg\:bui-pr-3{padding-right:var(--bui-space-3)}.lg\:bui-pr-4{padding-right:var(--bui-space-4)}.lg\:bui-pr-5{padding-right:var(--bui-space-5)}.lg\:bui-pr-6{padding-right:var(--bui-space-6)}.lg\:bui-pr-7{padding-right:var(--bui-space-7)}.lg\:bui-pr-8{padding-right:var(--bui-space-8)}.lg\:bui-pr-9{padding-right:var(--bui-space-9)}.lg\:bui-pr-10{padding-right:var(--bui-space-10)}.lg\:bui-pr-11{padding-right:var(--bui-space-11)}.lg\:bui-pr-12{padding-right:var(--bui-space-12)}.lg\:bui-pr-13{padding-right:var(--bui-space-13)}.lg\:bui-pr-14{padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pr{padding-right:var(--pr-xl)}.xl\:bui-pr-0\.5{padding-right:var(--bui-space-0_5)}.xl\:bui-pr-1{padding-right:var(--bui-space-1)}.xl\:bui-pr-1\.5{padding-right:var(--bui-space-1_5)}.xl\:bui-pr-2{padding-right:var(--bui-space-2)}.xl\:bui-pr-3{padding-right:var(--bui-space-3)}.xl\:bui-pr-4{padding-right:var(--bui-space-4)}.xl\:bui-pr-5{padding-right:var(--bui-space-5)}.xl\:bui-pr-6{padding-right:var(--bui-space-6)}.xl\:bui-pr-7{padding-right:var(--bui-space-7)}.xl\:bui-pr-8{padding-right:var(--bui-space-8)}.xl\:bui-pr-9{padding-right:var(--bui-space-9)}.xl\:bui-pr-10{padding-right:var(--bui-space-10)}.xl\:bui-pr-11{padding-right:var(--bui-space-11)}.xl\:bui-pr-12{padding-right:var(--bui-space-12)}.xl\:bui-pr-13{padding-right:var(--bui-space-13)}.xl\:bui-pr-14{padding-right:var(--bui-space-14)}}.bui-pt{padding-top:var(--pt)}.bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.bui-pt-1{padding-top:var(--bui-space-1)}.bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.bui-pt-2{padding-top:var(--bui-space-2)}.bui-pt-3{padding-top:var(--bui-space-3)}.bui-pt-4{padding-top:var(--bui-space-4)}.bui-pt-5{padding-top:var(--bui-space-5)}.bui-pt-6{padding-top:var(--bui-space-6)}.bui-pt-7{padding-top:var(--bui-space-7)}.bui-pt-8{padding-top:var(--bui-space-8)}.bui-pt-9{padding-top:var(--bui-space-9)}.bui-pt-10{padding-top:var(--bui-space-10)}.bui-pt-11{padding-top:var(--bui-space-11)}.bui-pt-12{padding-top:var(--bui-space-12)}.bui-pt-13{padding-top:var(--bui-space-13)}.bui-pt-14{padding-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pt{padding-top:var(--pt-xs)}.xs\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xs\:bui-pt-1{padding-top:var(--bui-space-1)}.xs\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xs\:bui-pt-2{padding-top:var(--bui-space-2)}.xs\:bui-pt-3{padding-top:var(--bui-space-3)}.xs\:bui-pt-4{padding-top:var(--bui-space-4)}.xs\:bui-pt-5{padding-top:var(--bui-space-5)}.xs\:bui-pt-6{padding-top:var(--bui-space-6)}.xs\:bui-pt-7{padding-top:var(--bui-space-7)}.xs\:bui-pt-8{padding-top:var(--bui-space-8)}.xs\:bui-pt-9{padding-top:var(--bui-space-9)}.xs\:bui-pt-10{padding-top:var(--bui-space-10)}.xs\:bui-pt-11{padding-top:var(--bui-space-11)}.xs\:bui-pt-12{padding-top:var(--bui-space-12)}.xs\:bui-pt-13{padding-top:var(--bui-space-13)}.xs\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pt{padding-top:var(--pt-sm)}.sm\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.sm\:bui-pt-1{padding-top:var(--bui-space-1)}.sm\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.sm\:bui-pt-2{padding-top:var(--bui-space-2)}.sm\:bui-pt-3{padding-top:var(--bui-space-3)}.sm\:bui-pt-4{padding-top:var(--bui-space-4)}.sm\:bui-pt-5{padding-top:var(--bui-space-5)}.sm\:bui-pt-6{padding-top:var(--bui-space-6)}.sm\:bui-pt-7{padding-top:var(--bui-space-7)}.sm\:bui-pt-8{padding-top:var(--bui-space-8)}.sm\:bui-pt-9{padding-top:var(--bui-space-9)}.sm\:bui-pt-10{padding-top:var(--bui-space-10)}.sm\:bui-pt-11{padding-top:var(--bui-space-11)}.sm\:bui-pt-12{padding-top:var(--bui-space-12)}.sm\:bui-pt-13{padding-top:var(--bui-space-13)}.sm\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pt{padding-top:var(--pt-md)}.md\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.md\:bui-pt-1{padding-top:var(--bui-space-1)}.md\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.md\:bui-pt-2{padding-top:var(--bui-space-2)}.md\:bui-pt-3{padding-top:var(--bui-space-3)}.md\:bui-pt-4{padding-top:var(--bui-space-4)}.md\:bui-pt-5{padding-top:var(--bui-space-5)}.md\:bui-pt-6{padding-top:var(--bui-space-6)}.md\:bui-pt-7{padding-top:var(--bui-space-7)}.md\:bui-pt-8{padding-top:var(--bui-space-8)}.md\:bui-pt-9{padding-top:var(--bui-space-9)}.md\:bui-pt-10{padding-top:var(--bui-space-10)}.md\:bui-pt-11{padding-top:var(--bui-space-11)}.md\:bui-pt-12{padding-top:var(--bui-space-12)}.md\:bui-pt-13{padding-top:var(--bui-space-13)}.md\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pt{padding-top:var(--pt-lg)}.lg\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.lg\:bui-pt-1{padding-top:var(--bui-space-1)}.lg\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.lg\:bui-pt-2{padding-top:var(--bui-space-2)}.lg\:bui-pt-3{padding-top:var(--bui-space-3)}.lg\:bui-pt-4{padding-top:var(--bui-space-4)}.lg\:bui-pt-5{padding-top:var(--bui-space-5)}.lg\:bui-pt-6{padding-top:var(--bui-space-6)}.lg\:bui-pt-7{padding-top:var(--bui-space-7)}.lg\:bui-pt-8{padding-top:var(--bui-space-8)}.lg\:bui-pt-9{padding-top:var(--bui-space-9)}.lg\:bui-pt-10{padding-top:var(--bui-space-10)}.lg\:bui-pt-11{padding-top:var(--bui-space-11)}.lg\:bui-pt-12{padding-top:var(--bui-space-12)}.lg\:bui-pt-13{padding-top:var(--bui-space-13)}.lg\:bui-pt-14{padding-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pt{padding-top:var(--pt-xl)}.xl\:bui-pt-0\.5{padding-top:var(--bui-space-0_5)}.xl\:bui-pt-1{padding-top:var(--bui-space-1)}.xl\:bui-pt-1\.5{padding-top:var(--bui-space-1_5)}.xl\:bui-pt-2{padding-top:var(--bui-space-2)}.xl\:bui-pt-3{padding-top:var(--bui-space-3)}.xl\:bui-pt-4{padding-top:var(--bui-space-4)}.xl\:bui-pt-5{padding-top:var(--bui-space-5)}.xl\:bui-pt-6{padding-top:var(--bui-space-6)}.xl\:bui-pt-7{padding-top:var(--bui-space-7)}.xl\:bui-pt-8{padding-top:var(--bui-space-8)}.xl\:bui-pt-9{padding-top:var(--bui-space-9)}.xl\:bui-pt-10{padding-top:var(--bui-space-10)}.xl\:bui-pt-11{padding-top:var(--bui-space-11)}.xl\:bui-pt-12{padding-top:var(--bui-space-12)}.xl\:bui-pt-13{padding-top:var(--bui-space-13)}.xl\:bui-pt-14{padding-top:var(--bui-space-14)}}.bui-pb{padding-bottom:var(--pb)}.bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.bui-pb-1{padding-bottom:var(--bui-space-1)}.bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.bui-pb-2{padding-bottom:var(--bui-space-2)}.bui-pb-3{padding-bottom:var(--bui-space-3)}.bui-pb-4{padding-bottom:var(--bui-space-4)}.bui-pb-5{padding-bottom:var(--bui-space-5)}.bui-pb-6{padding-bottom:var(--bui-space-6)}.bui-pb-7{padding-bottom:var(--bui-space-7)}.bui-pb-8{padding-bottom:var(--bui-space-8)}.bui-pb-9{padding-bottom:var(--bui-space-9)}.bui-pb-10{padding-bottom:var(--bui-space-10)}.bui-pb-11{padding-bottom:var(--bui-space-11)}.bui-pb-12{padding-bottom:var(--bui-space-12)}.bui-pb-13{padding-bottom:var(--bui-space-13)}.bui-pb-14{padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-pb{padding-bottom:var(--pb-xs)}.xs\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xs\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xs\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xs\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xs\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xs\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xs\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xs\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xs\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xs\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xs\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xs\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xs\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xs\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xs\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xs\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-pb{padding-bottom:var(--pb-sm)}.sm\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.sm\:bui-pb-1{padding-bottom:var(--bui-space-1)}.sm\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.sm\:bui-pb-2{padding-bottom:var(--bui-space-2)}.sm\:bui-pb-3{padding-bottom:var(--bui-space-3)}.sm\:bui-pb-4{padding-bottom:var(--bui-space-4)}.sm\:bui-pb-5{padding-bottom:var(--bui-space-5)}.sm\:bui-pb-6{padding-bottom:var(--bui-space-6)}.sm\:bui-pb-7{padding-bottom:var(--bui-space-7)}.sm\:bui-pb-8{padding-bottom:var(--bui-space-8)}.sm\:bui-pb-9{padding-bottom:var(--bui-space-9)}.sm\:bui-pb-10{padding-bottom:var(--bui-space-10)}.sm\:bui-pb-11{padding-bottom:var(--bui-space-11)}.sm\:bui-pb-12{padding-bottom:var(--bui-space-12)}.sm\:bui-pb-13{padding-bottom:var(--bui-space-13)}.sm\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-pb{padding-bottom:var(--pb-md)}.md\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.md\:bui-pb-1{padding-bottom:var(--bui-space-1)}.md\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.md\:bui-pb-2{padding-bottom:var(--bui-space-2)}.md\:bui-pb-3{padding-bottom:var(--bui-space-3)}.md\:bui-pb-4{padding-bottom:var(--bui-space-4)}.md\:bui-pb-5{padding-bottom:var(--bui-space-5)}.md\:bui-pb-6{padding-bottom:var(--bui-space-6)}.md\:bui-pb-7{padding-bottom:var(--bui-space-7)}.md\:bui-pb-8{padding-bottom:var(--bui-space-8)}.md\:bui-pb-9{padding-bottom:var(--bui-space-9)}.md\:bui-pb-10{padding-bottom:var(--bui-space-10)}.md\:bui-pb-11{padding-bottom:var(--bui-space-11)}.md\:bui-pb-12{padding-bottom:var(--bui-space-12)}.md\:bui-pb-13{padding-bottom:var(--bui-space-13)}.md\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-pb{padding-bottom:var(--pb-lg)}.lg\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.lg\:bui-pb-1{padding-bottom:var(--bui-space-1)}.lg\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.lg\:bui-pb-2{padding-bottom:var(--bui-space-2)}.lg\:bui-pb-3{padding-bottom:var(--bui-space-3)}.lg\:bui-pb-4{padding-bottom:var(--bui-space-4)}.lg\:bui-pb-5{padding-bottom:var(--bui-space-5)}.lg\:bui-pb-6{padding-bottom:var(--bui-space-6)}.lg\:bui-pb-7{padding-bottom:var(--bui-space-7)}.lg\:bui-pb-8{padding-bottom:var(--bui-space-8)}.lg\:bui-pb-9{padding-bottom:var(--bui-space-9)}.lg\:bui-pb-10{padding-bottom:var(--bui-space-10)}.lg\:bui-pb-11{padding-bottom:var(--bui-space-11)}.lg\:bui-pb-12{padding-bottom:var(--bui-space-12)}.lg\:bui-pb-13{padding-bottom:var(--bui-space-13)}.lg\:bui-pb-14{padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-pb{padding-bottom:var(--pb-xl)}.xl\:bui-pb-0\.5{padding-bottom:var(--bui-space-0_5)}.xl\:bui-pb-1{padding-bottom:var(--bui-space-1)}.xl\:bui-pb-1\.5{padding-bottom:var(--bui-space-1_5)}.xl\:bui-pb-2{padding-bottom:var(--bui-space-2)}.xl\:bui-pb-3{padding-bottom:var(--bui-space-3)}.xl\:bui-pb-4{padding-bottom:var(--bui-space-4)}.xl\:bui-pb-5{padding-bottom:var(--bui-space-5)}.xl\:bui-pb-6{padding-bottom:var(--bui-space-6)}.xl\:bui-pb-7{padding-bottom:var(--bui-space-7)}.xl\:bui-pb-8{padding-bottom:var(--bui-space-8)}.xl\:bui-pb-9{padding-bottom:var(--bui-space-9)}.xl\:bui-pb-10{padding-bottom:var(--bui-space-10)}.xl\:bui-pb-11{padding-bottom:var(--bui-space-11)}.xl\:bui-pb-12{padding-bottom:var(--bui-space-12)}.xl\:bui-pb-13{padding-bottom:var(--bui-space-13)}.xl\:bui-pb-14{padding-bottom:var(--bui-space-14)}}.bui-py{padding-top:var(--py);padding-bottom:var(--py)}.bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-py{padding-top:var(--py-xs);padding-bottom:var(--py-xs)}.xs\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xs\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xs\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xs\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xs\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xs\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xs\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xs\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xs\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xs\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xs\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xs\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xs\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xs\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xs\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xs\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-py{padding-top:var(--py-sm);padding-bottom:var(--py-sm)}.sm\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.sm\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.sm\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.sm\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.sm\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.sm\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.sm\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.sm\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.sm\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.sm\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.sm\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.sm\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.sm\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.sm\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.sm\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.sm\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-py{padding-top:var(--py-md);padding-bottom:var(--py-md)}.md\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.md\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.md\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.md\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.md\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.md\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.md\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.md\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.md\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.md\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.md\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.md\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.md\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.md\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.md\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.md\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-py{padding-top:var(--py-lg);padding-bottom:var(--py-lg)}.lg\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.lg\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.lg\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.lg\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.lg\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.lg\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.lg\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.lg\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.lg\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.lg\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.lg\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.lg\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.lg\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.lg\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.lg\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.lg\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-py{padding-top:var(--py-xl);padding-bottom:var(--py-xl)}.xl\:bui-py-0\.5{padding-top:var(--bui-space-0_5);padding-bottom:var(--bui-space-0_5)}.xl\:bui-py-1{padding-top:var(--bui-space-1);padding-bottom:var(--bui-space-1)}.xl\:bui-py-1\.5{padding-top:var(--bui-space-1_5);padding-bottom:var(--bui-space-1_5)}.xl\:bui-py-2{padding-top:var(--bui-space-2);padding-bottom:var(--bui-space-2)}.xl\:bui-py-3{padding-top:var(--bui-space-3);padding-bottom:var(--bui-space-3)}.xl\:bui-py-4{padding-top:var(--bui-space-4);padding-bottom:var(--bui-space-4)}.xl\:bui-py-5{padding-top:var(--bui-space-5);padding-bottom:var(--bui-space-5)}.xl\:bui-py-6{padding-top:var(--bui-space-6);padding-bottom:var(--bui-space-6)}.xl\:bui-py-7{padding-top:var(--bui-space-7);padding-bottom:var(--bui-space-7)}.xl\:bui-py-8{padding-top:var(--bui-space-8);padding-bottom:var(--bui-space-8)}.xl\:bui-py-9{padding-top:var(--bui-space-9);padding-bottom:var(--bui-space-9)}.xl\:bui-py-10{padding-top:var(--bui-space-10);padding-bottom:var(--bui-space-10)}.xl\:bui-py-11{padding-top:var(--bui-space-11);padding-bottom:var(--bui-space-11)}.xl\:bui-py-12{padding-top:var(--bui-space-12);padding-bottom:var(--bui-space-12)}.xl\:bui-py-13{padding-top:var(--bui-space-13);padding-bottom:var(--bui-space-13)}.xl\:bui-py-14{padding-top:var(--bui-space-14);padding-bottom:var(--bui-space-14)}}.bui-px{padding-left:var(--px);padding-right:var(--px)}.bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-px{padding-left:var(--px-xs);padding-right:var(--px-xs)}.xs\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xs\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xs\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xs\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xs\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xs\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xs\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xs\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xs\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xs\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xs\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xs\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xs\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xs\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xs\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xs\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-px{padding-left:var(--px-sm);padding-right:var(--px-sm)}.sm\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.sm\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.sm\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.sm\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.sm\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.sm\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.sm\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.sm\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.sm\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.sm\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.sm\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.sm\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.sm\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.sm\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.sm\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.sm\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-px{padding-left:var(--px-md);padding-right:var(--px-md)}.md\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.md\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.md\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.md\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.md\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.md\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.md\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.md\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.md\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.md\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.md\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.md\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.md\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.md\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.md\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.md\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-px{padding-left:var(--px-lg);padding-right:var(--px-lg)}.lg\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.lg\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.lg\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.lg\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.lg\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.lg\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.lg\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.lg\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.lg\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.lg\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.lg\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.lg\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.lg\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.lg\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.lg\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.lg\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-px{padding-left:var(--px-xl);padding-right:var(--px-xl)}.xl\:bui-px-0\.5{padding-left:var(--bui-space-0_5);padding-right:var(--bui-space-0_5)}.xl\:bui-px-1{padding-left:var(--bui-space-1);padding-right:var(--bui-space-1)}.xl\:bui-px-1\.5{padding-left:var(--bui-space-1_5);padding-right:var(--bui-space-1_5)}.xl\:bui-px-2{padding-left:var(--bui-space-2);padding-right:var(--bui-space-2)}.xl\:bui-px-3{padding-left:var(--bui-space-3);padding-right:var(--bui-space-3)}.xl\:bui-px-4{padding-left:var(--bui-space-4);padding-right:var(--bui-space-4)}.xl\:bui-px-5{padding-left:var(--bui-space-5);padding-right:var(--bui-space-5)}.xl\:bui-px-6{padding-left:var(--bui-space-6);padding-right:var(--bui-space-6)}.xl\:bui-px-7{padding-left:var(--bui-space-7);padding-right:var(--bui-space-7)}.xl\:bui-px-8{padding-left:var(--bui-space-8);padding-right:var(--bui-space-8)}.xl\:bui-px-9{padding-left:var(--bui-space-9);padding-right:var(--bui-space-9)}.xl\:bui-px-10{padding-left:var(--bui-space-10);padding-right:var(--bui-space-10)}.xl\:bui-px-11{padding-left:var(--bui-space-11);padding-right:var(--bui-space-11)}.xl\:bui-px-12{padding-left:var(--bui-space-12);padding-right:var(--bui-space-12)}.xl\:bui-px-13{padding-left:var(--bui-space-13);padding-right:var(--bui-space-13)}.xl\:bui-px-14{padding-left:var(--bui-space-14);padding-right:var(--bui-space-14)}}.bui-m{margin:var(--m)}.bui-m-0\.5{margin:var(--bui-space-0_5)}.bui-m-1{margin:var(--bui-space-1)}.bui-m-1\.5{margin:var(--bui-space-1_5)}.bui-m-2{margin:var(--bui-space-2)}.bui-m-3{margin:var(--bui-space-3)}.bui-m-4{margin:var(--bui-space-4)}.bui-m-5{margin:var(--bui-space-5)}.bui-m-6{margin:var(--bui-space-6)}.bui-m-7{margin:var(--bui-space-7)}.bui-m-8{margin:var(--bui-space-8)}.bui-m-9{margin:var(--bui-space-9)}.bui-m-10{margin:var(--bui-space-10)}.bui-m-11{margin:var(--bui-space-11)}.bui-m-12{margin:var(--bui-space-12)}.bui-m-13{margin:var(--bui-space-13)}.bui-m-14{margin:var(--bui-space-14)}@media (width>=640px){.xs\:bui-m{margin:var(--p-xs)}.xs\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xs\:bui-m-1{margin:var(--bui-space-1)}.xs\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xs\:bui-m-2{margin:var(--bui-space-2)}.xs\:bui-m-3{margin:var(--bui-space-3)}.xs\:bui-m-4{margin:var(--bui-space-4)}.xs\:bui-m-5{margin:var(--bui-space-5)}.xs\:bui-m-6{margin:var(--bui-space-6)}.xs\:bui-m-7{margin:var(--bui-space-7)}.xs\:bui-m-8{margin:var(--bui-space-8)}.xs\:bui-m-9{margin:var(--bui-space-9)}.xs\:bui-m-10{margin:var(--bui-space-10)}.xs\:bui-m-11{margin:var(--bui-space-11)}.xs\:bui-m-12{margin:var(--bui-space-12)}.xs\:bui-m-13{margin:var(--bui-space-13)}.xs\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-m{margin:var(--p-sm)}.sm\:bui-m-0\.5{margin:var(--bui-space-0_5)}.sm\:bui-m-1{margin:var(--bui-space-1)}.sm\:bui-m-1\.5{margin:var(--bui-space-1_5)}.sm\:bui-m-2{margin:var(--bui-space-2)}.sm\:bui-m-3{margin:var(--bui-space-3)}.sm\:bui-m-4{margin:var(--bui-space-4)}.sm\:bui-m-5{margin:var(--bui-space-5)}.sm\:bui-m-6{margin:var(--bui-space-6)}.sm\:bui-m-7{margin:var(--bui-space-7)}.sm\:bui-m-8{margin:var(--bui-space-8)}.sm\:bui-m-9{margin:var(--bui-space-9)}.sm\:bui-m-10{margin:var(--bui-space-10)}.sm\:bui-m-11{margin:var(--bui-space-11)}.sm\:bui-m-12{margin:var(--bui-space-12)}.sm\:bui-m-13{margin:var(--bui-space-13)}.sm\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-m{margin:var(--p-md)}.md\:bui-m-0\.5{margin:var(--bui-space-0_5)}.md\:bui-m-1{margin:var(--bui-space-1)}.md\:bui-m-1\.5{margin:var(--bui-space-1_5)}.md\:bui-m-2{margin:var(--bui-space-2)}.md\:bui-m-3{margin:var(--bui-space-3)}.md\:bui-m-4{margin:var(--bui-space-4)}.md\:bui-m-5{margin:var(--bui-space-5)}.md\:bui-m-6{margin:var(--bui-space-6)}.md\:bui-m-7{margin:var(--bui-space-7)}.md\:bui-m-8{margin:var(--bui-space-8)}.md\:bui-m-9{margin:var(--bui-space-9)}.md\:bui-m-10{margin:var(--bui-space-10)}.md\:bui-m-11{margin:var(--bui-space-11)}.md\:bui-m-12{margin:var(--bui-space-12)}.md\:bui-m-13{margin:var(--bui-space-13)}.md\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-m{margin:var(--p-lg)}.lg\:bui-m-0\.5{margin:var(--bui-space-0_5)}.lg\:bui-m-1{margin:var(--bui-space-1)}.lg\:bui-m-1\.5{margin:var(--bui-space-1_5)}.lg\:bui-m-2{margin:var(--bui-space-2)}.lg\:bui-m-3{margin:var(--bui-space-3)}.lg\:bui-m-4{margin:var(--bui-space-4)}.lg\:bui-m-5{margin:var(--bui-space-5)}.lg\:bui-m-6{margin:var(--bui-space-6)}.lg\:bui-m-7{margin:var(--bui-space-7)}.lg\:bui-m-8{margin:var(--bui-space-8)}.lg\:bui-m-9{margin:var(--bui-space-9)}.lg\:bui-m-10{margin:var(--bui-space-10)}.lg\:bui-m-11{margin:var(--bui-space-11)}.lg\:bui-m-12{margin:var(--bui-space-12)}.lg\:bui-m-13{margin:var(--bui-space-13)}.lg\:bui-m-14{margin:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-m{margin:var(--p-xl)}.xl\:bui-m-0\.5{margin:var(--bui-space-0_5)}.xl\:bui-m-1{margin:var(--bui-space-1)}.xl\:bui-m-1\.5{margin:var(--bui-space-1_5)}.xl\:bui-m-2{margin:var(--bui-space-2)}.xl\:bui-m-3{margin:var(--bui-space-3)}.xl\:bui-m-4{margin:var(--bui-space-4)}.xl\:bui-m-5{margin:var(--bui-space-5)}.xl\:bui-m-6{margin:var(--bui-space-6)}.xl\:bui-m-7{margin:var(--bui-space-7)}.xl\:bui-m-8{margin:var(--bui-space-8)}.xl\:bui-m-9{margin:var(--bui-space-9)}.xl\:bui-m-10{margin:var(--bui-space-10)}.xl\:bui-m-11{margin:var(--bui-space-11)}.xl\:bui-m-12{margin:var(--bui-space-12)}.xl\:bui-m-13{margin:var(--bui-space-13)}.xl\:bui-m-14{margin:var(--bui-space-14)}}.bui-ml{margin-left:var(--ml)}.bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.bui-ml-1{margin-left:var(--bui-space-1)}.bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.bui-ml-2{margin-left:var(--bui-space-2)}.bui-ml-3{margin-left:var(--bui-space-3)}.bui-ml-4{margin-left:var(--bui-space-4)}.bui-ml-5{margin-left:var(--bui-space-5)}.bui-ml-6{margin-left:var(--bui-space-6)}.bui-ml-7{margin-left:var(--bui-space-7)}.bui-ml-8{margin-left:var(--bui-space-8)}.bui-ml-9{margin-left:var(--bui-space-9)}.bui-ml-10{margin-left:var(--bui-space-10)}.bui-ml-11{margin-left:var(--bui-space-11)}.bui-ml-12{margin-left:var(--bui-space-12)}.bui-ml-13{margin-left:var(--bui-space-13)}.bui-ml-14{margin-left:var(--bui-space-14)}@media (width>=640px){.xs\:bui-ml{margin-left:var(--ml-xs)}.xs\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xs\:bui-ml-1{margin-left:var(--bui-space-1)}.xs\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xs\:bui-ml-2{margin-left:var(--bui-space-2)}.xs\:bui-ml-3{margin-left:var(--bui-space-3)}.xs\:bui-ml-4{margin-left:var(--bui-space-4)}.xs\:bui-ml-5{margin-left:var(--bui-space-5)}.xs\:bui-ml-6{margin-left:var(--bui-space-6)}.xs\:bui-ml-7{margin-left:var(--bui-space-7)}.xs\:bui-ml-8{margin-left:var(--bui-space-8)}.xs\:bui-ml-9{margin-left:var(--bui-space-9)}.xs\:bui-ml-10{margin-left:var(--bui-space-10)}.xs\:bui-ml-11{margin-left:var(--bui-space-11)}.xs\:bui-ml-12{margin-left:var(--bui-space-12)}.xs\:bui-ml-13{margin-left:var(--bui-space-13)}.xs\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-ml{margin-left:var(--ml-sm)}.sm\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.sm\:bui-ml-1{margin-left:var(--bui-space-1)}.sm\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.sm\:bui-ml-2{margin-left:var(--bui-space-2)}.sm\:bui-ml-3{margin-left:var(--bui-space-3)}.sm\:bui-ml-4{margin-left:var(--bui-space-4)}.sm\:bui-ml-5{margin-left:var(--bui-space-5)}.sm\:bui-ml-6{margin-left:var(--bui-space-6)}.sm\:bui-ml-7{margin-left:var(--bui-space-7)}.sm\:bui-ml-8{margin-left:var(--bui-space-8)}.sm\:bui-ml-9{margin-left:var(--bui-space-9)}.sm\:bui-ml-10{margin-left:var(--bui-space-10)}.sm\:bui-ml-11{margin-left:var(--bui-space-11)}.sm\:bui-ml-12{margin-left:var(--bui-space-12)}.sm\:bui-ml-13{margin-left:var(--bui-space-13)}.sm\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-ml{margin-left:var(--ml-md)}.md\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.md\:bui-ml-1{margin-left:var(--bui-space-1)}.md\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.md\:bui-ml-2{margin-left:var(--bui-space-2)}.md\:bui-ml-3{margin-left:var(--bui-space-3)}.md\:bui-ml-4{margin-left:var(--bui-space-4)}.md\:bui-ml-5{margin-left:var(--bui-space-5)}.md\:bui-ml-6{margin-left:var(--bui-space-6)}.md\:bui-ml-7{margin-left:var(--bui-space-7)}.md\:bui-ml-8{margin-left:var(--bui-space-8)}.md\:bui-ml-9{margin-left:var(--bui-space-9)}.md\:bui-ml-10{margin-left:var(--bui-space-10)}.md\:bui-ml-11{margin-left:var(--bui-space-11)}.md\:bui-ml-12{margin-left:var(--bui-space-12)}.md\:bui-ml-13{margin-left:var(--bui-space-13)}.md\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-ml{margin-left:var(--ml-lg)}.lg\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.lg\:bui-ml-1{margin-left:var(--bui-space-1)}.lg\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.lg\:bui-ml-2{margin-left:var(--bui-space-2)}.lg\:bui-ml-3{margin-left:var(--bui-space-3)}.lg\:bui-ml-4{margin-left:var(--bui-space-4)}.lg\:bui-ml-5{margin-left:var(--bui-space-5)}.lg\:bui-ml-6{margin-left:var(--bui-space-6)}.lg\:bui-ml-7{margin-left:var(--bui-space-7)}.lg\:bui-ml-8{margin-left:var(--bui-space-8)}.lg\:bui-ml-9{margin-left:var(--bui-space-9)}.lg\:bui-ml-10{margin-left:var(--bui-space-10)}.lg\:bui-ml-11{margin-left:var(--bui-space-11)}.lg\:bui-ml-12{margin-left:var(--bui-space-12)}.lg\:bui-ml-13{margin-left:var(--bui-space-13)}.lg\:bui-ml-14{margin-left:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-ml{margin-left:var(--ml-xl)}.xl\:bui-ml-0\.5{margin-left:var(--bui-space-0_5)}.xl\:bui-ml-1{margin-left:var(--bui-space-1)}.xl\:bui-ml-1\.5{margin-left:var(--bui-space-1_5)}.xl\:bui-ml-2{margin-left:var(--bui-space-2)}.xl\:bui-ml-3{margin-left:var(--bui-space-3)}.xl\:bui-ml-4{margin-left:var(--bui-space-4)}.xl\:bui-ml-5{margin-left:var(--bui-space-5)}.xl\:bui-ml-6{margin-left:var(--bui-space-6)}.xl\:bui-ml-7{margin-left:var(--bui-space-7)}.xl\:bui-ml-8{margin-left:var(--bui-space-8)}.xl\:bui-ml-9{margin-left:var(--bui-space-9)}.xl\:bui-ml-10{margin-left:var(--bui-space-10)}.xl\:bui-ml-11{margin-left:var(--bui-space-11)}.xl\:bui-ml-12{margin-left:var(--bui-space-12)}.xl\:bui-ml-13{margin-left:var(--bui-space-13)}.xl\:bui-ml-14{margin-left:var(--bui-space-14)}}.bui-mr{margin-right:var(--mr)}.bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.bui-mr-1{margin-right:var(--bui-space-1)}.bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.bui-mr-2{margin-right:var(--bui-space-2)}.bui-mr-3{margin-right:var(--bui-space-3)}.bui-mr-4{margin-right:var(--bui-space-4)}.bui-mr-5{margin-right:var(--bui-space-5)}.bui-mr-6{margin-right:var(--bui-space-6)}.bui-mr-7{margin-right:var(--bui-space-7)}.bui-mr-8{margin-right:var(--bui-space-8)}.bui-mr-9{margin-right:var(--bui-space-9)}.bui-mr-10{margin-right:var(--bui-space-10)}.bui-mr-11{margin-right:var(--bui-space-11)}.bui-mr-12{margin-right:var(--bui-space-12)}.bui-mr-13{margin-right:var(--bui-space-13)}.bui-mr-14{margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mr{margin-right:var(--mr-xs)}.xs\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xs\:bui-mr-1{margin-right:var(--bui-space-1)}.xs\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xs\:bui-mr-2{margin-right:var(--bui-space-2)}.xs\:bui-mr-3{margin-right:var(--bui-space-3)}.xs\:bui-mr-4{margin-right:var(--bui-space-4)}.xs\:bui-mr-5{margin-right:var(--bui-space-5)}.xs\:bui-mr-6{margin-right:var(--bui-space-6)}.xs\:bui-mr-7{margin-right:var(--bui-space-7)}.xs\:bui-mr-8{margin-right:var(--bui-space-8)}.xs\:bui-mr-9{margin-right:var(--bui-space-9)}.xs\:bui-mr-10{margin-right:var(--bui-space-10)}.xs\:bui-mr-11{margin-right:var(--bui-space-11)}.xs\:bui-mr-12{margin-right:var(--bui-space-12)}.xs\:bui-mr-13{margin-right:var(--bui-space-13)}.xs\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mr{margin-right:var(--mr-sm)}.sm\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.sm\:bui-mr-1{margin-right:var(--bui-space-1)}.sm\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.sm\:bui-mr-2{margin-right:var(--bui-space-2)}.sm\:bui-mr-3{margin-right:var(--bui-space-3)}.sm\:bui-mr-4{margin-right:var(--bui-space-4)}.sm\:bui-mr-5{margin-right:var(--bui-space-5)}.sm\:bui-mr-6{margin-right:var(--bui-space-6)}.sm\:bui-mr-7{margin-right:var(--bui-space-7)}.sm\:bui-mr-8{margin-right:var(--bui-space-8)}.sm\:bui-mr-9{margin-right:var(--bui-space-9)}.sm\:bui-mr-10{margin-right:var(--bui-space-10)}.sm\:bui-mr-11{margin-right:var(--bui-space-11)}.sm\:bui-mr-12{margin-right:var(--bui-space-12)}.sm\:bui-mr-13{margin-right:var(--bui-space-13)}.sm\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mr{margin-right:var(--mr-md)}.md\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.md\:bui-mr-1{margin-right:var(--bui-space-1)}.md\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.md\:bui-mr-2{margin-right:var(--bui-space-2)}.md\:bui-mr-3{margin-right:var(--bui-space-3)}.md\:bui-mr-4{margin-right:var(--bui-space-4)}.md\:bui-mr-5{margin-right:var(--bui-space-5)}.md\:bui-mr-6{margin-right:var(--bui-space-6)}.md\:bui-mr-7{margin-right:var(--bui-space-7)}.md\:bui-mr-8{margin-right:var(--bui-space-8)}.md\:bui-mr-9{margin-right:var(--bui-space-9)}.md\:bui-mr-10{margin-right:var(--bui-space-10)}.md\:bui-mr-11{margin-right:var(--bui-space-11)}.md\:bui-mr-12{margin-right:var(--bui-space-12)}.md\:bui-mr-13{margin-right:var(--bui-space-13)}.md\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mr{margin-right:var(--mr-lg)}.lg\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.lg\:bui-mr-1{margin-right:var(--bui-space-1)}.lg\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.lg\:bui-mr-2{margin-right:var(--bui-space-2)}.lg\:bui-mr-3{margin-right:var(--bui-space-3)}.lg\:bui-mr-4{margin-right:var(--bui-space-4)}.lg\:bui-mr-5{margin-right:var(--bui-space-5)}.lg\:bui-mr-6{margin-right:var(--bui-space-6)}.lg\:bui-mr-7{margin-right:var(--bui-space-7)}.lg\:bui-mr-8{margin-right:var(--bui-space-8)}.lg\:bui-mr-9{margin-right:var(--bui-space-9)}.lg\:bui-mr-10{margin-right:var(--bui-space-10)}.lg\:bui-mr-11{margin-right:var(--bui-space-11)}.lg\:bui-mr-12{margin-right:var(--bui-space-12)}.lg\:bui-mr-13{margin-right:var(--bui-space-13)}.lg\:bui-mr-14{margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mr{margin-right:var(--mr-xl)}.xl\:bui-mr-0\.5{margin-right:var(--bui-space-0_5)}.xl\:bui-mr-1{margin-right:var(--bui-space-1)}.xl\:bui-mr-1\.5{margin-right:var(--bui-space-1_5)}.xl\:bui-mr-2{margin-right:var(--bui-space-2)}.xl\:bui-mr-3{margin-right:var(--bui-space-3)}.xl\:bui-mr-4{margin-right:var(--bui-space-4)}.xl\:bui-mr-5{margin-right:var(--bui-space-5)}.xl\:bui-mr-6{margin-right:var(--bui-space-6)}.xl\:bui-mr-7{margin-right:var(--bui-space-7)}.xl\:bui-mr-8{margin-right:var(--bui-space-8)}.xl\:bui-mr-9{margin-right:var(--bui-space-9)}.xl\:bui-mr-10{margin-right:var(--bui-space-10)}.xl\:bui-mr-11{margin-right:var(--bui-space-11)}.xl\:bui-mr-12{margin-right:var(--bui-space-12)}.xl\:bui-mr-13{margin-right:var(--bui-space-13)}.xl\:bui-mr-14{margin-right:var(--bui-space-14)}}.bui-mt{margin-top:var(--mt)}.bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.bui-mt-1{margin-top:var(--bui-space-1)}.bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.bui-mt-2{margin-top:var(--bui-space-2)}.bui-mt-3{margin-top:var(--bui-space-3)}.bui-mt-4{margin-top:var(--bui-space-4)}.bui-mt-5{margin-top:var(--bui-space-5)}.bui-mt-6{margin-top:var(--bui-space-6)}.bui-mt-7{margin-top:var(--bui-space-7)}.bui-mt-8{margin-top:var(--bui-space-8)}.bui-mt-9{margin-top:var(--bui-space-9)}.bui-mt-10{margin-top:var(--bui-space-10)}.bui-mt-11{margin-top:var(--bui-space-11)}.bui-mt-12{margin-top:var(--bui-space-12)}.bui-mt-13{margin-top:var(--bui-space-13)}.bui-mt-14{margin-top:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mt{margin-top:var(--mt-xs)}.xs\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xs\:bui-mt-1{margin-top:var(--bui-space-1)}.xs\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xs\:bui-mt-2{margin-top:var(--bui-space-2)}.xs\:bui-mt-3{margin-top:var(--bui-space-3)}.xs\:bui-mt-4{margin-top:var(--bui-space-4)}.xs\:bui-mt-5{margin-top:var(--bui-space-5)}.xs\:bui-mt-6{margin-top:var(--bui-space-6)}.xs\:bui-mt-7{margin-top:var(--bui-space-7)}.xs\:bui-mt-8{margin-top:var(--bui-space-8)}.xs\:bui-mt-9{margin-top:var(--bui-space-9)}.xs\:bui-mt-10{margin-top:var(--bui-space-10)}.xs\:bui-mt-11{margin-top:var(--bui-space-11)}.xs\:bui-mt-12{margin-top:var(--bui-space-12)}.xs\:bui-mt-13{margin-top:var(--bui-space-13)}.xs\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mt{margin-top:var(--mt-sm)}.sm\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.sm\:bui-mt-1{margin-top:var(--bui-space-1)}.sm\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.sm\:bui-mt-2{margin-top:var(--bui-space-2)}.sm\:bui-mt-3{margin-top:var(--bui-space-3)}.sm\:bui-mt-4{margin-top:var(--bui-space-4)}.sm\:bui-mt-5{margin-top:var(--bui-space-5)}.sm\:bui-mt-6{margin-top:var(--bui-space-6)}.sm\:bui-mt-7{margin-top:var(--bui-space-7)}.sm\:bui-mt-8{margin-top:var(--bui-space-8)}.sm\:bui-mt-9{margin-top:var(--bui-space-9)}.sm\:bui-mt-10{margin-top:var(--bui-space-10)}.sm\:bui-mt-11{margin-top:var(--bui-space-11)}.sm\:bui-mt-12{margin-top:var(--bui-space-12)}.sm\:bui-mt-13{margin-top:var(--bui-space-13)}.sm\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mt{margin-top:var(--mt-md)}.md\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.md\:bui-mt-1{margin-top:var(--bui-space-1)}.md\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.md\:bui-mt-2{margin-top:var(--bui-space-2)}.md\:bui-mt-3{margin-top:var(--bui-space-3)}.md\:bui-mt-4{margin-top:var(--bui-space-4)}.md\:bui-mt-5{margin-top:var(--bui-space-5)}.md\:bui-mt-6{margin-top:var(--bui-space-6)}.md\:bui-mt-7{margin-top:var(--bui-space-7)}.md\:bui-mt-8{margin-top:var(--bui-space-8)}.md\:bui-mt-9{margin-top:var(--bui-space-9)}.md\:bui-mt-10{margin-top:var(--bui-space-10)}.md\:bui-mt-11{margin-top:var(--bui-space-11)}.md\:bui-mt-12{margin-top:var(--bui-space-12)}.md\:bui-mt-13{margin-top:var(--bui-space-13)}.md\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mt{margin-top:var(--mt-lg)}.lg\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.lg\:bui-mt-1{margin-top:var(--bui-space-1)}.lg\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.lg\:bui-mt-2{margin-top:var(--bui-space-2)}.lg\:bui-mt-3{margin-top:var(--bui-space-3)}.lg\:bui-mt-4{margin-top:var(--bui-space-4)}.lg\:bui-mt-5{margin-top:var(--bui-space-5)}.lg\:bui-mt-6{margin-top:var(--bui-space-6)}.lg\:bui-mt-7{margin-top:var(--bui-space-7)}.lg\:bui-mt-8{margin-top:var(--bui-space-8)}.lg\:bui-mt-9{margin-top:var(--bui-space-9)}.lg\:bui-mt-10{margin-top:var(--bui-space-10)}.lg\:bui-mt-11{margin-top:var(--bui-space-11)}.lg\:bui-mt-12{margin-top:var(--bui-space-12)}.lg\:bui-mt-13{margin-top:var(--bui-space-13)}.lg\:bui-mt-14{margin-top:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mt{margin-top:var(--mt-xl)}.xl\:bui-mt-0\.5{margin-top:var(--bui-space-0_5)}.xl\:bui-mt-1{margin-top:var(--bui-space-1)}.xl\:bui-mt-1\.5{margin-top:var(--bui-space-1_5)}.xl\:bui-mt-2{margin-top:var(--bui-space-2)}.xl\:bui-mt-3{margin-top:var(--bui-space-3)}.xl\:bui-mt-4{margin-top:var(--bui-space-4)}.xl\:bui-mt-5{margin-top:var(--bui-space-5)}.xl\:bui-mt-6{margin-top:var(--bui-space-6)}.xl\:bui-mt-7{margin-top:var(--bui-space-7)}.xl\:bui-mt-8{margin-top:var(--bui-space-8)}.xl\:bui-mt-9{margin-top:var(--bui-space-9)}.xl\:bui-mt-10{margin-top:var(--bui-space-10)}.xl\:bui-mt-11{margin-top:var(--bui-space-11)}.xl\:bui-mt-12{margin-top:var(--bui-space-12)}.xl\:bui-mt-13{margin-top:var(--bui-space-13)}.xl\:bui-mt-14{margin-top:var(--bui-space-14)}}.bui-mb{margin-bottom:var(--mb)}.bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.bui-mb-1{margin-bottom:var(--bui-space-1)}.bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.bui-mb-2{margin-bottom:var(--bui-space-2)}.bui-mb-3{margin-bottom:var(--bui-space-3)}.bui-mb-4{margin-bottom:var(--bui-space-4)}.bui-mb-5{margin-bottom:var(--bui-space-5)}.bui-mb-6{margin-bottom:var(--bui-space-6)}.bui-mb-7{margin-bottom:var(--bui-space-7)}.bui-mb-8{margin-bottom:var(--bui-space-8)}.bui-mb-9{margin-bottom:var(--bui-space-9)}.bui-mb-10{margin-bottom:var(--bui-space-10)}.bui-mb-11{margin-bottom:var(--bui-space-11)}.bui-mb-12{margin-bottom:var(--bui-space-12)}.bui-mb-13{margin-bottom:var(--bui-space-13)}.bui-mb-14{margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mb{margin-bottom:var(--mb-xs)}.xs\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xs\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xs\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xs\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xs\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xs\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xs\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xs\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xs\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xs\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xs\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xs\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xs\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xs\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xs\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xs\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mb{margin-bottom:var(--mb-sm)}.sm\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.sm\:bui-mb-1{margin-bottom:var(--bui-space-1)}.sm\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.sm\:bui-mb-2{margin-bottom:var(--bui-space-2)}.sm\:bui-mb-3{margin-bottom:var(--bui-space-3)}.sm\:bui-mb-4{margin-bottom:var(--bui-space-4)}.sm\:bui-mb-5{margin-bottom:var(--bui-space-5)}.sm\:bui-mb-6{margin-bottom:var(--bui-space-6)}.sm\:bui-mb-7{margin-bottom:var(--bui-space-7)}.sm\:bui-mb-8{margin-bottom:var(--bui-space-8)}.sm\:bui-mb-9{margin-bottom:var(--bui-space-9)}.sm\:bui-mb-10{margin-bottom:var(--bui-space-10)}.sm\:bui-mb-11{margin-bottom:var(--bui-space-11)}.sm\:bui-mb-12{margin-bottom:var(--bui-space-12)}.sm\:bui-mb-13{margin-bottom:var(--bui-space-13)}.sm\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mb{margin-bottom:var(--mb-md)}.md\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.md\:bui-mb-1{margin-bottom:var(--bui-space-1)}.md\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.md\:bui-mb-2{margin-bottom:var(--bui-space-2)}.md\:bui-mb-3{margin-bottom:var(--bui-space-3)}.md\:bui-mb-4{margin-bottom:var(--bui-space-4)}.md\:bui-mb-5{margin-bottom:var(--bui-space-5)}.md\:bui-mb-6{margin-bottom:var(--bui-space-6)}.md\:bui-mb-7{margin-bottom:var(--bui-space-7)}.md\:bui-mb-8{margin-bottom:var(--bui-space-8)}.md\:bui-mb-9{margin-bottom:var(--bui-space-9)}.md\:bui-mb-10{margin-bottom:var(--bui-space-10)}.md\:bui-mb-11{margin-bottom:var(--bui-space-11)}.md\:bui-mb-12{margin-bottom:var(--bui-space-12)}.md\:bui-mb-13{margin-bottom:var(--bui-space-13)}.md\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mb{margin-bottom:var(--mb-lg)}.lg\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.lg\:bui-mb-1{margin-bottom:var(--bui-space-1)}.lg\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.lg\:bui-mb-2{margin-bottom:var(--bui-space-2)}.lg\:bui-mb-3{margin-bottom:var(--bui-space-3)}.lg\:bui-mb-4{margin-bottom:var(--bui-space-4)}.lg\:bui-mb-5{margin-bottom:var(--bui-space-5)}.lg\:bui-mb-6{margin-bottom:var(--bui-space-6)}.lg\:bui-mb-7{margin-bottom:var(--bui-space-7)}.lg\:bui-mb-8{margin-bottom:var(--bui-space-8)}.lg\:bui-mb-9{margin-bottom:var(--bui-space-9)}.lg\:bui-mb-10{margin-bottom:var(--bui-space-10)}.lg\:bui-mb-11{margin-bottom:var(--bui-space-11)}.lg\:bui-mb-12{margin-bottom:var(--bui-space-12)}.lg\:bui-mb-13{margin-bottom:var(--bui-space-13)}.lg\:bui-mb-14{margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mb{margin-bottom:var(--mb-xl)}.xl\:bui-mb-0\.5{margin-bottom:var(--bui-space-0_5)}.xl\:bui-mb-1{margin-bottom:var(--bui-space-1)}.xl\:bui-mb-1\.5{margin-bottom:var(--bui-space-1_5)}.xl\:bui-mb-2{margin-bottom:var(--bui-space-2)}.xl\:bui-mb-3{margin-bottom:var(--bui-space-3)}.xl\:bui-mb-4{margin-bottom:var(--bui-space-4)}.xl\:bui-mb-5{margin-bottom:var(--bui-space-5)}.xl\:bui-mb-6{margin-bottom:var(--bui-space-6)}.xl\:bui-mb-7{margin-bottom:var(--bui-space-7)}.xl\:bui-mb-8{margin-bottom:var(--bui-space-8)}.xl\:bui-mb-9{margin-bottom:var(--bui-space-9)}.xl\:bui-mb-10{margin-bottom:var(--bui-space-10)}.xl\:bui-mb-11{margin-bottom:var(--bui-space-11)}.xl\:bui-mb-12{margin-bottom:var(--bui-space-12)}.xl\:bui-mb-13{margin-bottom:var(--bui-space-13)}.xl\:bui-mb-14{margin-bottom:var(--bui-space-14)}}.bui-my{margin-top:var(--my);margin-bottom:var(--my)}.bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}@media (width>=640px){.xs\:bui-my{margin-top:var(--my-xs);margin-bottom:var(--my-xs)}.xs\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xs\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xs\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xs\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xs\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xs\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xs\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xs\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xs\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xs\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xs\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xs\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xs\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xs\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xs\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xs\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-my{margin-top:var(--my-sm);margin-bottom:var(--my-sm)}.sm\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.sm\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.sm\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.sm\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.sm\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.sm\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.sm\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.sm\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.sm\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.sm\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.sm\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.sm\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.sm\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.sm\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.sm\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.sm\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-my{margin-top:var(--my-md);margin-bottom:var(--my-md)}.md\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.md\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.md\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.md\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.md\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.md\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.md\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.md\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.md\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.md\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.md\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.md\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.md\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.md\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.md\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.md\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-my{margin-top:var(--my-lg);margin-bottom:var(--my-lg)}.lg\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.lg\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.lg\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.lg\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.lg\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.lg\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.lg\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.lg\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.lg\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.lg\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.lg\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.lg\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.lg\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.lg\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.lg\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.lg\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-my{margin-top:var(--my-xl);margin-bottom:var(--my-xl)}.xl\:bui-my-0\.5{margin-top:var(--bui-space-0_5);margin-bottom:var(--bui-space-0_5)}.xl\:bui-my-1{margin-top:var(--bui-space-1);margin-bottom:var(--bui-space-1)}.xl\:bui-my-1\.5{margin-top:var(--bui-space-1_5);margin-bottom:var(--bui-space-1_5)}.xl\:bui-my-2{margin-top:var(--bui-space-2);margin-bottom:var(--bui-space-2)}.xl\:bui-my-3{margin-top:var(--bui-space-3);margin-bottom:var(--bui-space-3)}.xl\:bui-my-4{margin-top:var(--bui-space-4);margin-bottom:var(--bui-space-4)}.xl\:bui-my-5{margin-top:var(--bui-space-5);margin-bottom:var(--bui-space-5)}.xl\:bui-my-6{margin-top:var(--bui-space-6);margin-bottom:var(--bui-space-6)}.xl\:bui-my-7{margin-top:var(--bui-space-7);margin-bottom:var(--bui-space-7)}.xl\:bui-my-8{margin-top:var(--bui-space-8);margin-bottom:var(--bui-space-8)}.xl\:bui-my-9{margin-top:var(--bui-space-9);margin-bottom:var(--bui-space-9)}.xl\:bui-my-10{margin-top:var(--bui-space-10);margin-bottom:var(--bui-space-10)}.xl\:bui-my-11{margin-top:var(--bui-space-11);margin-bottom:var(--bui-space-11)}.xl\:bui-my-12{margin-top:var(--bui-space-12);margin-bottom:var(--bui-space-12)}.xl\:bui-my-13{margin-top:var(--bui-space-13);margin-bottom:var(--bui-space-13)}.xl\:bui-my-14{margin-top:var(--bui-space-14);margin-bottom:var(--bui-space-14)}}.bui-mx{margin-left:var(--mx);margin-right:var(--mx)}.bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}@media (width>=640px){.xs\:bui-mx{margin-left:var(--mx-xs);margin-right:var(--mx-xs)}.xs\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xs\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xs\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xs\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xs\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xs\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xs\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xs\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xs\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xs\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xs\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xs\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xs\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xs\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xs\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xs\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-mx{margin-left:var(--mx-sm);margin-right:var(--mx-sm)}.sm\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.sm\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.sm\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.sm\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.sm\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.sm\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.sm\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.sm\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.sm\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.sm\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.sm\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.sm\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.sm\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.sm\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.sm\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.sm\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-mx{margin-left:var(--mx-md);margin-right:var(--mx-md)}.md\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.md\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.md\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.md\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.md\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.md\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.md\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.md\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.md\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.md\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.md\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.md\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.md\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.md\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.md\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.md\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-mx{margin-left:var(--mx-lg);margin-right:var(--mx-lg)}.lg\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.lg\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.lg\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.lg\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.lg\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.lg\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.lg\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.lg\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.lg\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.lg\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.lg\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.lg\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.lg\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.lg\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.lg\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.lg\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-mx{margin-left:var(--mx-xl);margin-right:var(--mx-xl)}.xl\:bui-mx-0\.5{margin-left:var(--bui-space-0_5);margin-right:var(--bui-space-0_5)}.xl\:bui-mx-1{margin-left:var(--bui-space-1);margin-right:var(--bui-space-1)}.xl\:bui-mx-1\.5{margin-left:var(--bui-space-1_5);margin-right:var(--bui-space-1_5)}.xl\:bui-mx-2{margin-left:var(--bui-space-2);margin-right:var(--bui-space-2)}.xl\:bui-mx-3{margin-left:var(--bui-space-3);margin-right:var(--bui-space-3)}.xl\:bui-mx-4{margin-left:var(--bui-space-4);margin-right:var(--bui-space-4)}.xl\:bui-mx-5{margin-left:var(--bui-space-5);margin-right:var(--bui-space-5)}.xl\:bui-mx-6{margin-left:var(--bui-space-6);margin-right:var(--bui-space-6)}.xl\:bui-mx-7{margin-left:var(--bui-space-7);margin-right:var(--bui-space-7)}.xl\:bui-mx-8{margin-left:var(--bui-space-8);margin-right:var(--bui-space-8)}.xl\:bui-mx-9{margin-left:var(--bui-space-9);margin-right:var(--bui-space-9)}.xl\:bui-mx-10{margin-left:var(--bui-space-10);margin-right:var(--bui-space-10)}.xl\:bui-mx-11{margin-left:var(--bui-space-11);margin-right:var(--bui-space-11)}.xl\:bui-mx-12{margin-left:var(--bui-space-12);margin-right:var(--bui-space-12)}.xl\:bui-mx-13{margin-left:var(--bui-space-13);margin-right:var(--bui-space-13)}.xl\:bui-mx-14{margin-left:var(--bui-space-14);margin-right:var(--bui-space-14)}}.bui-display-none{display:none}.bui-display-inline{display:inline}.bui-display-inline-block{display:inline-block}.bui-display-block{display:block}@media (width>=640px){.xs\:bui-display-none{display:none}.xs\:bui-display-inline{display:inline}.xs\:bui-display-inline-block{display:inline-block}.xs\:bui-display-block{display:block}}@media (width>=768px){.sm\:bui-display-none{display:none}.sm\:bui-display-inline{display:inline}.sm\:bui-display-inline-block{display:inline-block}.sm\:bui-display-block{display:block}}@media (width>=1024px){.md\:bui-display-none{display:none}.md\:bui-display-inline{display:inline}.md\:bui-display-inline-block{display:inline-block}.md\:bui-display-block{display:block}}@media (width>=1280px){.lg\:bui-display-none{display:none}.lg\:bui-display-inline{display:inline}.lg\:bui-display-inline-block{display:inline-block}.lg\:bui-display-block{display:block}}@media (width>=1536px){.xl\:bui-display-none{display:none}.xl\:bui-display-inline{display:inline}.xl\:bui-display-inline-block{display:inline-block}.xl\:bui-display-block{display:block}}.bui-w{width:var(--width)}.bui-min-w{min-width:var(--min-width)}.bui-max-w{max-width:var(--max-width)}@media (width>=640px){.xs\:bui-w{width:var(--width)}.xs\:bui-min-w{min-width:var(--min-width)}.xs\:bui-max-w{max-width:var(--max-width)}}@media (width>=768px){.sm\:bui-w{width:var(--width)}.sm\:bui-min-w{min-width:var(--min-width)}.sm\:bui-max-w{max-width:var(--max-width)}}@media (width>=1024px){.md\:bui-w{width:var(--width)}.md\:bui-min-w{min-width:var(--min-width)}.md\:bui-max-w{max-width:var(--max-width)}}@media (width>=1280px){.lg\:bui-w{width:var(--width)}.lg\:bui-min-w{min-width:var(--min-width)}.lg\:bui-max-w{max-width:var(--max-width)}}@media (width>=1536px){.xl\:bui-w{width:var(--width)}.xl\:bui-min-w{min-width:var(--min-width)}.xl\:bui-max-w{max-width:var(--max-width)}}.bui-h{height:var(--height)}.bui-min-h{min-height:var(--min-height)}.bui-max-h{max-height:var(--max-height)}@media (width>=640px){.xs\:bui-h{height:var(--height)}.xs\:bui-min-h{min-height:var(--min-height)}.xs\:bui-max-h{max-height:var(--max-height)}}@media (width>=768px){.sm\:bui-h{height:var(--height)}.sm\:bui-min-h{min-height:var(--min-height)}.sm\:bui-max-h{max-height:var(--max-height)}}@media (width>=1024px){.md\:bui-h{height:var(--height)}.md\:bui-min-h{min-height:var(--min-height)}.md\:bui-max-h{max-height:var(--max-height)}}@media (width>=1280px){.lg\:bui-h{height:var(--height)}.lg\:bui-min-h{min-height:var(--min-height)}.lg\:bui-max-h{max-height:var(--max-height)}}@media (width>=1536px){.xl\:bui-h{height:var(--height)}.xl\:bui-min-h{min-height:var(--min-height)}.xl\:bui-max-h{max-height:var(--max-height)}}.bui-position-absolute{position:absolute}.bui-position-fixed{position:fixed}.bui-position-sticky{position:sticky}.bui-position-relative{position:relative}.bui-position-static{position:static}@media (width>=640px){.xs\:bui-position-absolute{position:absolute}.xs\:bui-position-fixed{position:fixed}.xs\:bui-position-sticky{position:sticky}.xs\:bui-position-relative{position:relative}.xs\:bui-position-static{position:static}}@media (width>=768px){.sm\:bui-position-absolute{position:absolute}.sm\:bui-position-fixed{position:fixed}.sm\:bui-position-sticky{position:sticky}.sm\:bui-position-relative{position:relative}.sm\:bui-position-static{position:static}}@media (width>=1024px){.md\:bui-position-absolute{position:absolute}.md\:bui-position-fixed{position:fixed}.md\:bui-position-sticky{position:sticky}.md\:bui-position-relative{position:relative}.md\:bui-position-static{position:static}}@media (width>=1280px){.lg\:bui-position-absolute{position:absolute}.lg\:bui-position-fixed{position:fixed}.lg\:bui-position-sticky{position:sticky}.lg\:bui-position-relative{position:relative}.lg\:bui-position-static{position:static}}@media (width>=1536px){.xl\:bui-position-absolute{position:absolute}.xl\:bui-position-fixed{position:fixed}.xl\:bui-position-sticky{position:sticky}.xl\:bui-position-relative{position:relative}.xl\:bui-position-static{position:static}}.bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.bui-col-span-1{grid-column:span 1/span 1}.bui-col-span-2{grid-column:span 2/span 2}.bui-col-span-3{grid-column:span 3/span 3}.bui-col-span-4{grid-column:span 4/span 4}.bui-col-span-5{grid-column:span 5/span 5}.bui-col-span-6{grid-column:span 6/span 6}.bui-col-span-7{grid-column:span 7/span 7}.bui-col-span-8{grid-column:span 8/span 8}.bui-col-span-9{grid-column:span 9/span 9}.bui-col-span-10{grid-column:span 10/span 10}.bui-col-span-11{grid-column:span 11/span 11}.bui-col-span-12{grid-column:span 12/span 12}.bui-col-span-auto{grid-column:span auto/span auto}.bui-col-start-1{grid-column-start:1}.bui-col-start-2{grid-column-start:2}.bui-col-start-3{grid-column-start:3}.bui-col-start-4{grid-column-start:4}.bui-col-start-5{grid-column-start:5}.bui-col-start-6{grid-column-start:6}.bui-col-start-7{grid-column-start:7}.bui-col-start-8{grid-column-start:8}.bui-col-start-9{grid-column-start:9}.bui-col-start-10{grid-column-start:10}.bui-col-start-11{grid-column-start:11}.bui-col-start-12{grid-column-start:12}.bui-col-start-13{grid-column-start:13}.bui-col-start-auto{grid-column-start:auto}.bui-col-end-1{grid-column-end:1}.bui-col-end-2{grid-column-end:2}.bui-col-end-3{grid-column-end:3}.bui-col-end-4{grid-column-end:4}.bui-col-end-5{grid-column-end:5}.bui-col-end-6{grid-column-end:6}.bui-col-end-7{grid-column-end:7}.bui-col-end-8{grid-column-end:8}.bui-col-end-9{grid-column-end:9}.bui-col-end-10{grid-column-end:10}.bui-col-end-11{grid-column-end:11}.bui-col-end-12{grid-column-end:12}.bui-col-end-13{grid-column-end:13}.bui-col-end-auto{grid-column-end:auto}.bui-row-span-1{grid-row:span 1/span 1}.bui-row-span-2{grid-row:span 2/span 2}.bui-row-span-3{grid-row:span 3/span 3}.bui-row-span-4{grid-row:span 4/span 4}.bui-row-span-5{grid-row:span 5/span 5}.bui-row-span-6{grid-row:span 6/span 6}.bui-row-span-7{grid-row:span 7/span 7}.bui-row-span-8{grid-row:span 8/span 8}.bui-row-span-9{grid-row:span 9/span 9}.bui-row-span-10{grid-row:span 10/span 10}.bui-row-span-11{grid-row:span 11/span 11}.bui-row-span-12{grid-row:span 12/span 12}.bui-row-span-auto{grid-row:span auto/span auto}@media (width>=640px){.xs\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xs\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xs\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xs\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xs\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xs\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xs\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xs\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xs\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xs\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xs\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xs\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xs\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xs\:bui-col-span-1{grid-column:span 1/span 1}.xs\:bui-col-span-2{grid-column:span 2/span 2}.xs\:bui-col-span-3{grid-column:span 3/span 3}.xs\:bui-col-span-4{grid-column:span 4/span 4}.xs\:bui-col-span-5{grid-column:span 5/span 5}.xs\:bui-col-span-6{grid-column:span 6/span 6}.xs\:bui-col-span-7{grid-column:span 7/span 7}.xs\:bui-col-span-8{grid-column:span 8/span 8}.xs\:bui-col-span-9{grid-column:span 9/span 9}.xs\:bui-col-span-10{grid-column:span 10/span 10}.xs\:bui-col-span-11{grid-column:span 11/span 11}.xs\:bui-col-span-12{grid-column:span 12/span 12}.xs\:bui-col-span-auto{grid-column:span auto/span auto}.xs\:bui-col-start-1{grid-column-start:1}.xs\:bui-col-start-2{grid-column-start:2}.xs\:bui-col-start-3{grid-column-start:3}.xs\:bui-col-start-4{grid-column-start:4}.xs\:bui-col-start-5{grid-column-start:5}.xs\:bui-col-start-6{grid-column-start:6}.xs\:bui-col-start-7{grid-column-start:7}.xs\:bui-col-start-8{grid-column-start:8}.xs\:bui-col-start-9{grid-column-start:9}.xs\:bui-col-start-10{grid-column-start:10}.xs\:bui-col-start-11{grid-column-start:11}.xs\:bui-col-start-12{grid-column-start:12}.xs\:bui-col-start-13{grid-column-start:13}.xs\:bui-col-start-auto{grid-column-start:auto}.xs\:bui-col-end-1{grid-column-end:1}.xs\:bui-col-end-2{grid-column-end:2}.xs\:bui-col-end-3{grid-column-end:3}.xs\:bui-col-end-4{grid-column-end:4}.xs\:bui-col-end-5{grid-column-end:5}.xs\:bui-col-end-6{grid-column-end:6}.xs\:bui-col-end-7{grid-column-end:7}.xs\:bui-col-end-8{grid-column-end:8}.xs\:bui-col-end-9{grid-column-end:9}.xs\:bui-col-end-10{grid-column-end:10}.xs\:bui-col-end-11{grid-column-end:11}.xs\:bui-col-end-12{grid-column-end:12}.xs\:bui-col-end-13{grid-column-end:13}.xs\:bui-col-end-auto{grid-column-end:auto}.xs\:bui-row-span-1{grid-row:span 1/span 1}.xs\:bui-row-span-2{grid-row:span 2/span 2}.xs\:bui-row-span-3{grid-row:span 3/span 3}.xs\:bui-row-span-4{grid-row:span 4/span 4}.xs\:bui-row-span-5{grid-row:span 5/span 5}.xs\:bui-row-span-6{grid-row:span 6/span 6}.xs\:bui-row-span-7{grid-row:span 7/span 7}.xs\:bui-row-span-8{grid-row:span 8/span 8}.xs\:bui-row-span-9{grid-row:span 9/span 9}.xs\:bui-row-span-10{grid-row:span 10/span 10}.xs\:bui-row-span-11{grid-row:span 11/span 11}.xs\:bui-row-span-12{grid-row:span 12/span 12}.xs\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=768px){.sm\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.sm\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.sm\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.sm\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.sm\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.sm\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.sm\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.sm\:bui-col-span-1{grid-column:span 1/span 1}.sm\:bui-col-span-2{grid-column:span 2/span 2}.sm\:bui-col-span-3{grid-column:span 3/span 3}.sm\:bui-col-span-4{grid-column:span 4/span 4}.sm\:bui-col-span-5{grid-column:span 5/span 5}.sm\:bui-col-span-6{grid-column:span 6/span 6}.sm\:bui-col-span-7{grid-column:span 7/span 7}.sm\:bui-col-span-8{grid-column:span 8/span 8}.sm\:bui-col-span-9{grid-column:span 9/span 9}.sm\:bui-col-span-10{grid-column:span 10/span 10}.sm\:bui-col-span-11{grid-column:span 11/span 11}.sm\:bui-col-span-12{grid-column:span 12/span 12}.sm\:bui-col-span-auto{grid-column:span auto/span auto}.sm\:bui-col-start-1{grid-column-start:1}.sm\:bui-col-start-2{grid-column-start:2}.sm\:bui-col-start-3{grid-column-start:3}.sm\:bui-col-start-4{grid-column-start:4}.sm\:bui-col-start-5{grid-column-start:5}.sm\:bui-col-start-6{grid-column-start:6}.sm\:bui-col-start-7{grid-column-start:7}.sm\:bui-col-start-8{grid-column-start:8}.sm\:bui-col-start-9{grid-column-start:9}.sm\:bui-col-start-10{grid-column-start:10}.sm\:bui-col-start-11{grid-column-start:11}.sm\:bui-col-start-12{grid-column-start:12}.sm\:bui-col-start-13{grid-column-start:13}.sm\:bui-col-start-auto{grid-column-start:auto}.sm\:bui-col-end-1{grid-column-end:1}.sm\:bui-col-end-2{grid-column-end:2}.sm\:bui-col-end-3{grid-column-end:3}.sm\:bui-col-end-4{grid-column-end:4}.sm\:bui-col-end-5{grid-column-end:5}.sm\:bui-col-end-6{grid-column-end:6}.sm\:bui-col-end-7{grid-column-end:7}.sm\:bui-col-end-8{grid-column-end:8}.sm\:bui-col-end-9{grid-column-end:9}.sm\:bui-col-end-10{grid-column-end:10}.sm\:bui-col-end-11{grid-column-end:11}.sm\:bui-col-end-12{grid-column-end:12}.sm\:bui-col-end-13{grid-column-end:13}.sm\:bui-col-end-auto{grid-column-end:auto}.sm\:bui-row-span-1{grid-row:span 1/span 1}.sm\:bui-row-span-2{grid-row:span 2/span 2}.sm\:bui-row-span-3{grid-row:span 3/span 3}.sm\:bui-row-span-4{grid-row:span 4/span 4}.sm\:bui-row-span-5{grid-row:span 5/span 5}.sm\:bui-row-span-6{grid-row:span 6/span 6}.sm\:bui-row-span-7{grid-row:span 7/span 7}.sm\:bui-row-span-8{grid-row:span 8/span 8}.sm\:bui-row-span-9{grid-row:span 9/span 9}.sm\:bui-row-span-10{grid-row:span 10/span 10}.sm\:bui-row-span-11{grid-row:span 11/span 11}.sm\:bui-row-span-12{grid-row:span 12/span 12}.sm\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1024px){.md\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.md\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.md\:bui-col-span-1{grid-column:span 1/span 1}.md\:bui-col-span-2{grid-column:span 2/span 2}.md\:bui-col-span-3{grid-column:span 3/span 3}.md\:bui-col-span-4{grid-column:span 4/span 4}.md\:bui-col-span-5{grid-column:span 5/span 5}.md\:bui-col-span-6{grid-column:span 6/span 6}.md\:bui-col-span-7{grid-column:span 7/span 7}.md\:bui-col-span-8{grid-column:span 8/span 8}.md\:bui-col-span-9{grid-column:span 9/span 9}.md\:bui-col-span-10{grid-column:span 10/span 10}.md\:bui-col-span-11{grid-column:span 11/span 11}.md\:bui-col-span-12{grid-column:span 12/span 12}.md\:bui-col-span-auto{grid-column:span auto/span auto}.md\:bui-col-start-1{grid-column-start:1}.md\:bui-col-start-2{grid-column-start:2}.md\:bui-col-start-3{grid-column-start:3}.md\:bui-col-start-4{grid-column-start:4}.md\:bui-col-start-5{grid-column-start:5}.md\:bui-col-start-6{grid-column-start:6}.md\:bui-col-start-7{grid-column-start:7}.md\:bui-col-start-8{grid-column-start:8}.md\:bui-col-start-9{grid-column-start:9}.md\:bui-col-start-10{grid-column-start:10}.md\:bui-col-start-11{grid-column-start:11}.md\:bui-col-start-12{grid-column-start:12}.md\:bui-col-start-13{grid-column-start:13}.md\:bui-col-start-auto{grid-column-start:auto}.md\:bui-col-end-1{grid-column-end:1}.md\:bui-col-end-2{grid-column-end:2}.md\:bui-col-end-3{grid-column-end:3}.md\:bui-col-end-4{grid-column-end:4}.md\:bui-col-end-5{grid-column-end:5}.md\:bui-col-end-6{grid-column-end:6}.md\:bui-col-end-7{grid-column-end:7}.md\:bui-col-end-8{grid-column-end:8}.md\:bui-col-end-9{grid-column-end:9}.md\:bui-col-end-10{grid-column-end:10}.md\:bui-col-end-11{grid-column-end:11}.md\:bui-col-end-12{grid-column-end:12}.md\:bui-col-end-13{grid-column-end:13}.md\:bui-col-end-auto{grid-column-end:auto}.md\:bui-row-span-1{grid-row:span 1/span 1}.md\:bui-row-span-2{grid-row:span 2/span 2}.md\:bui-row-span-3{grid-row:span 3/span 3}.md\:bui-row-span-4{grid-row:span 4/span 4}.md\:bui-row-span-5{grid-row:span 5/span 5}.md\:bui-row-span-6{grid-row:span 6/span 6}.md\:bui-row-span-7{grid-row:span 7/span 7}.md\:bui-row-span-8{grid-row:span 8/span 8}.md\:bui-row-span-9{grid-row:span 9/span 9}.md\:bui-row-span-10{grid-row:span 10/span 10}.md\:bui-row-span-11{grid-row:span 11/span 11}.md\:bui-row-span-12{grid-row:span 12/span 12}.md\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1280px){.lg\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.lg\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.lg\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.lg\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.lg\:bui-col-span-1{grid-column:span 1/span 1}.lg\:bui-col-span-2{grid-column:span 2/span 2}.lg\:bui-col-span-3{grid-column:span 3/span 3}.lg\:bui-col-span-4{grid-column:span 4/span 4}.lg\:bui-col-span-5{grid-column:span 5/span 5}.lg\:bui-col-span-6{grid-column:span 6/span 6}.lg\:bui-col-span-7{grid-column:span 7/span 7}.lg\:bui-col-span-8{grid-column:span 8/span 8}.lg\:bui-col-span-9{grid-column:span 9/span 9}.lg\:bui-col-span-10{grid-column:span 10/span 10}.lg\:bui-col-span-11{grid-column:span 11/span 11}.lg\:bui-col-span-12{grid-column:span 12/span 12}.lg\:bui-col-span-auto{grid-column:span auto/span auto}.lg\:bui-col-start-1{grid-column-start:1}.lg\:bui-col-start-2{grid-column-start:2}.lg\:bui-col-start-3{grid-column-start:3}.lg\:bui-col-start-4{grid-column-start:4}.lg\:bui-col-start-5{grid-column-start:5}.lg\:bui-col-start-6{grid-column-start:6}.lg\:bui-col-start-7{grid-column-start:7}.lg\:bui-col-start-8{grid-column-start:8}.lg\:bui-col-start-9{grid-column-start:9}.lg\:bui-col-start-10{grid-column-start:10}.lg\:bui-col-start-11{grid-column-start:11}.lg\:bui-col-start-12{grid-column-start:12}.lg\:bui-col-start-13{grid-column-start:13}.lg\:bui-col-start-auto{grid-column-start:auto}.lg\:bui-col-end-1{grid-column-end:1}.lg\:bui-col-end-2{grid-column-end:2}.lg\:bui-col-end-3{grid-column-end:3}.lg\:bui-col-end-4{grid-column-end:4}.lg\:bui-col-end-5{grid-column-end:5}.lg\:bui-col-end-6{grid-column-end:6}.lg\:bui-col-end-7{grid-column-end:7}.lg\:bui-col-end-8{grid-column-end:8}.lg\:bui-col-end-9{grid-column-end:9}.lg\:bui-col-end-10{grid-column-end:10}.lg\:bui-col-end-11{grid-column-end:11}.lg\:bui-col-end-12{grid-column-end:12}.lg\:bui-col-end-13{grid-column-end:13}.lg\:bui-col-end-auto{grid-column-end:auto}.lg\:bui-row-span-1{grid-row:span 1/span 1}.lg\:bui-row-span-2{grid-row:span 2/span 2}.lg\:bui-row-span-3{grid-row:span 3/span 3}.lg\:bui-row-span-4{grid-row:span 4/span 4}.lg\:bui-row-span-5{grid-row:span 5/span 5}.lg\:bui-row-span-6{grid-row:span 6/span 6}.lg\:bui-row-span-7{grid-row:span 7/span 7}.lg\:bui-row-span-8{grid-row:span 8/span 8}.lg\:bui-row-span-9{grid-row:span 9/span 9}.lg\:bui-row-span-10{grid-row:span 10/span 10}.lg\:bui-row-span-11{grid-row:span 11/span 11}.lg\:bui-row-span-12{grid-row:span 12/span 12}.lg\:bui-row-span-auto{grid-row:span auto/span auto}}@media (width>=1536px){.xl\:bui-columns-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:bui-columns-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:bui-columns-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:bui-columns-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:bui-columns-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:bui-columns-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:bui-columns-7{grid-template-columns:repeat(7,minmax(0,1fr))}.xl\:bui-columns-8{grid-template-columns:repeat(8,minmax(0,1fr))}.xl\:bui-columns-9{grid-template-columns:repeat(9,minmax(0,1fr))}.xl\:bui-columns-10{grid-template-columns:repeat(10,minmax(0,1fr))}.xl\:bui-columns-11{grid-template-columns:repeat(11,minmax(0,1fr))}.xl\:bui-columns-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:bui-columns-auto{grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.xl\:bui-col-span-1{grid-column:span 1/span 1}.xl\:bui-col-span-2{grid-column:span 2/span 2}.xl\:bui-col-span-3{grid-column:span 3/span 3}.xl\:bui-col-span-4{grid-column:span 4/span 4}.xl\:bui-col-span-5{grid-column:span 5/span 5}.xl\:bui-col-span-6{grid-column:span 6/span 6}.xl\:bui-col-span-7{grid-column:span 7/span 7}.xl\:bui-col-span-8{grid-column:span 8/span 8}.xl\:bui-col-span-9{grid-column:span 9/span 9}.xl\:bui-col-span-10{grid-column:span 10/span 10}.xl\:bui-col-span-11{grid-column:span 11/span 11}.xl\:bui-col-span-12{grid-column:span 12/span 12}.xl\:bui-col-span-auto{grid-column:span auto/span auto}.xl\:bui-col-start-1{grid-column-start:1}.xl\:bui-col-start-2{grid-column-start:2}.xl\:bui-col-start-3{grid-column-start:3}.xl\:bui-col-start-4{grid-column-start:4}.xl\:bui-col-start-5{grid-column-start:5}.xl\:bui-col-start-6{grid-column-start:6}.xl\:bui-col-start-7{grid-column-start:7}.xl\:bui-col-start-8{grid-column-start:8}.xl\:bui-col-start-9{grid-column-start:9}.xl\:bui-col-start-10{grid-column-start:10}.xl\:bui-col-start-11{grid-column-start:11}.xl\:bui-col-start-12{grid-column-start:12}.xl\:bui-col-start-13{grid-column-start:13}.xl\:bui-col-start-auto{grid-column-start:auto}.xl\:bui-col-end-1{grid-column-end:1}.xl\:bui-col-end-2{grid-column-end:2}.xl\:bui-col-end-3{grid-column-end:3}.xl\:bui-col-end-4{grid-column-end:4}.xl\:bui-col-end-5{grid-column-end:5}.xl\:bui-col-end-6{grid-column-end:6}.xl\:bui-col-end-7{grid-column-end:7}.xl\:bui-col-end-8{grid-column-end:8}.xl\:bui-col-end-9{grid-column-end:9}.xl\:bui-col-end-10{grid-column-end:10}.xl\:bui-col-end-11{grid-column-end:11}.xl\:bui-col-end-12{grid-column-end:12}.xl\:bui-col-end-13{grid-column-end:13}.xl\:bui-col-end-auto{grid-column-end:auto}.xl\:bui-row-span-1{grid-row:span 1/span 1}.xl\:bui-row-span-2{grid-row:span 2/span 2}.xl\:bui-row-span-3{grid-row:span 3/span 3}.xl\:bui-row-span-4{grid-row:span 4/span 4}.xl\:bui-row-span-5{grid-row:span 5/span 5}.xl\:bui-row-span-6{grid-row:span 6/span 6}.xl\:bui-row-span-7{grid-row:span 7/span 7}.xl\:bui-row-span-8{grid-row:span 8/span 8}.xl\:bui-row-span-9{grid-row:span 9/span 9}.xl\:bui-row-span-10{grid-row:span 10/span 10}.xl\:bui-row-span-11{grid-row:span 11/span 11}.xl\:bui-row-span-12{grid-row:span 12/span 12}.xl\:bui-row-span-auto{grid-row:span auto/span auto}}.bui-gap{gap:var(--gap)}.bui-gap-0\.5{gap:var(--bui-space-0_5)}.bui-gap-1{gap:var(--bui-space-1)}.bui-gap-1\.5{gap:var(--bui-space-1_5)}.bui-gap-2{gap:var(--bui-space-2)}.bui-gap-3{gap:var(--bui-space-3)}.bui-gap-4{gap:var(--bui-space-4)}.bui-gap-5{gap:var(--bui-space-5)}.bui-gap-6{gap:var(--bui-space-6)}.bui-gap-7{gap:var(--bui-space-7)}.bui-gap-8{gap:var(--bui-space-8)}.bui-gap-9{gap:var(--bui-space-9)}.bui-gap-10{gap:var(--bui-space-10)}.bui-gap-11{gap:var(--bui-space-11)}.bui-gap-12{gap:var(--bui-space-12)}.bui-gap-13{gap:var(--bui-space-13)}.bui-gap-14{gap:var(--bui-space-14)}@media (width>=640px){.xs\:bui-gap{gap:var(--gap-xs)}.xs\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xs\:bui-gap-1{gap:var(--bui-space-1)}.xs\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xs\:bui-gap-2{gap:var(--bui-space-2)}.xs\:bui-gap-3{gap:var(--bui-space-3)}.xs\:bui-gap-4{gap:var(--bui-space-4)}.xs\:bui-gap-5{gap:var(--bui-space-5)}.xs\:bui-gap-6{gap:var(--bui-space-6)}.xs\:bui-gap-7{gap:var(--bui-space-7)}.xs\:bui-gap-8{gap:var(--bui-space-8)}.xs\:bui-gap-9{gap:var(--bui-space-9)}.xs\:bui-gap-10{gap:var(--bui-space-10)}.xs\:bui-gap-11{gap:var(--bui-space-11)}.xs\:bui-gap-12{gap:var(--bui-space-12)}.xs\:bui-gap-13{gap:var(--bui-space-13)}.xs\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=768px){.sm\:bui-gap{gap:var(--gap-sm)}.sm\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.sm\:bui-gap-1{gap:var(--bui-space-1)}.sm\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.sm\:bui-gap-2{gap:var(--bui-space-2)}.sm\:bui-gap-3{gap:var(--bui-space-3)}.sm\:bui-gap-4{gap:var(--bui-space-4)}.sm\:bui-gap-5{gap:var(--bui-space-5)}.sm\:bui-gap-6{gap:var(--bui-space-6)}.sm\:bui-gap-7{gap:var(--bui-space-7)}.sm\:bui-gap-8{gap:var(--bui-space-8)}.sm\:bui-gap-9{gap:var(--bui-space-9)}.sm\:bui-gap-10{gap:var(--bui-space-10)}.sm\:bui-gap-11{gap:var(--bui-space-11)}.sm\:bui-gap-12{gap:var(--bui-space-12)}.sm\:bui-gap-13{gap:var(--bui-space-13)}.sm\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1024px){.md\:bui-gap{gap:var(--gap-md)}.md\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.md\:bui-gap-1{gap:var(--bui-space-1)}.md\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.md\:bui-gap-2{gap:var(--bui-space-2)}.md\:bui-gap-3{gap:var(--bui-space-3)}.md\:bui-gap-4{gap:var(--bui-space-4)}.md\:bui-gap-5{gap:var(--bui-space-5)}.md\:bui-gap-6{gap:var(--bui-space-6)}.md\:bui-gap-7{gap:var(--bui-space-7)}.md\:bui-gap-8{gap:var(--bui-space-8)}.md\:bui-gap-9{gap:var(--bui-space-9)}.md\:bui-gap-10{gap:var(--bui-space-10)}.md\:bui-gap-11{gap:var(--bui-space-11)}.md\:bui-gap-12{gap:var(--bui-space-12)}.md\:bui-gap-13{gap:var(--bui-space-13)}.md\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1280px){.lg\:bui-gap{gap:var(--gap-lg)}.lg\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.lg\:bui-gap-1{gap:var(--bui-space-1)}.lg\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.lg\:bui-gap-2{gap:var(--bui-space-2)}.lg\:bui-gap-3{gap:var(--bui-space-3)}.lg\:bui-gap-4{gap:var(--bui-space-4)}.lg\:bui-gap-5{gap:var(--bui-space-5)}.lg\:bui-gap-6{gap:var(--bui-space-6)}.lg\:bui-gap-7{gap:var(--bui-space-7)}.lg\:bui-gap-8{gap:var(--bui-space-8)}.lg\:bui-gap-9{gap:var(--bui-space-9)}.lg\:bui-gap-10{gap:var(--bui-space-10)}.lg\:bui-gap-11{gap:var(--bui-space-11)}.lg\:bui-gap-12{gap:var(--bui-space-12)}.lg\:bui-gap-13{gap:var(--bui-space-13)}.lg\:bui-gap-14{gap:var(--bui-space-14)}}@media (width>=1536px){.xl\:bui-gap{gap:var(--gap-xl)}.xl\:bui-gap-0\.5{gap:var(--bui-space-0_5)}.xl\:bui-gap-1{gap:var(--bui-space-1)}.xl\:bui-gap-1\.5{gap:var(--bui-space-1_5)}.xl\:bui-gap-2{gap:var(--bui-space-2)}.xl\:bui-gap-3{gap:var(--bui-space-3)}.xl\:bui-gap-4{gap:var(--bui-space-4)}.xl\:bui-gap-5{gap:var(--bui-space-5)}.xl\:bui-gap-6{gap:var(--bui-space-6)}.xl\:bui-gap-7{gap:var(--bui-space-7)}.xl\:bui-gap-8{gap:var(--bui-space-8)}.xl\:bui-gap-9{gap:var(--bui-space-9)}.xl\:bui-gap-10{gap:var(--bui-space-10)}.xl\:bui-gap-11{gap:var(--bui-space-11)}.xl\:bui-gap-12{gap:var(--bui-space-12)}.xl\:bui-gap-13{gap:var(--bui-space-13)}.xl\:bui-gap-14{gap:var(--bui-space-14)}}.bui-align-start{align-items:start}.bui-align-center{align-items:center}.bui-align-end{align-items:end}.bui-align-baseline{align-items:baseline}.bui-align-stretch{align-items:stretch}.bui-jc-start{justify-content:start}.bui-jc-center{justify-content:center}.bui-jc-end{justify-content:end}.bui-jc-between{justify-content:space-between}.bui-fd-row{flex-direction:row}.bui-fd-column{flex-direction:column}.bui-fd-row-reverse{flex-direction:row-reverse}.bui-fd-column-reverse{flex-direction:column-reverse}@media (width>=640px){.xs\:bui-align-start{align-items:start}.xs\:bui-align-center{align-items:center}.xs\:bui-align-end{align-items:end}.xs\:bui-align-stretch{align-items:stretch}.xs\:bui-jc-start{justify-content:start}.xs\:bui-jc-center{justify-content:center}.xs\:bui-jc-end{justify-content:end}.xs\:bui-jc-between{justify-content:space-between}.xs\:bui-fd-row{flex-direction:row}.xs\:bui-fd-column{flex-direction:column}.xs\:bui-fd-row-reverse{flex-direction:row-reverse}.xs\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=768px){.sm\:bui-align-start{align-items:start}.sm\:bui-align-center{align-items:center}.sm\:bui-align-end{align-items:end}.sm\:bui-align-stretch{align-items:stretch}.sm\:bui-jc-start{justify-content:start}.sm\:bui-jc-center{justify-content:center}.sm\:bui-jc-end{justify-content:end}.sm\:bui-jc-between{justify-content:space-between}.sm\:bui-fd-row{flex-direction:row}.sm\:bui-fd-column{flex-direction:column}.sm\:bui-fd-row-reverse{flex-direction:row-reverse}.sm\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1024px){.md\:bui-align-start{align-items:start}.md\:bui-align-center{align-items:center}.md\:bui-align-end{align-items:end}.md\:bui-align-stretch{align-items:stretch}.md\:bui-jc-start{justify-content:start}.md\:bui-jc-center{justify-content:center}.md\:bui-jc-end{justify-content:end}.md\:bui-jc-between{justify-content:space-between}.md\:bui-fd-row{flex-direction:row}.md\:bui-fd-column{flex-direction:column}.md\:bui-fd-row-reverse{flex-direction:row-reverse}.md\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1280px){.lg\:bui-align-start{align-items:start}.lg\:bui-align-center{align-items:center}.lg\:bui-align-end{align-items:end}.lg\:bui-align-stretch{align-items:stretch}.lg\:bui-jc-start{justify-content:start}.lg\:bui-jc-center{justify-content:center}.lg\:bui-jc-end{justify-content:end}.lg\:bui-jc-between{justify-content:space-between}.lg\:bui-fd-row{flex-direction:row}.lg\:bui-fd-column{flex-direction:column}.lg\:bui-fd-row-reverse{flex-direction:row-reverse}.lg\:bui-fd-column-reverse{flex-direction:column-reverse}}@media (width>=1536px){.xl\:bui-align-start{align-items:start}.xl\:bui-align-center{align-items:center}.xl\:bui-align-end{align-items:end}.xl\:bui-align-stretch{align-items:stretch}.xl\:bui-jc-start{justify-content:start}.xl\:bui-jc-center{justify-content:center}.xl\:bui-jc-end{justify-content:end}.xl\:bui-jc-between{justify-content:space-between}.xl\:bui-fd-row{flex-direction:row}.xl\:bui-fd-column{flex-direction:column}.xl\:bui-fd-row-reverse{flex-direction:row-reverse}.xl\:bui-fd-column-reverse{flex-direction:column-reverse}}} \ No newline at end of file diff --git a/docs-ui/public/theme-spotify.css b/docs-ui/public/theme-spotify.css deleted file mode 100644 index 22eda0cabe..0000000000 --- a/docs-ui/public/theme-spotify.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:CircularSpTitle;font-weight:900;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff)format("woff")}@font-face{font-family:CircularSpTitle;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:400;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff)format("woff")}@font-face{font-family:CircularSp;font-weight:700;font-display:swap;unicode-range:U+0,U+D,U+20-7E,U+A0-17E,U+18F,U+192,U+1A0-1A1,U+1AF-1B0,U+1B5-1B6,U+1C4-1C6,U+1F1-1F3,U+1FA-1FF,U+218-21B,U+237,U+259,U+2BB-2BC,U+2C6-2C7,U+2C9,U+2D8-2DD,U+300-301,U+303,U+309,U+323,U+394,U+3A9,U+3BC,U+3C0,U+1E80-1E85,U+1E8A-1E8B,U+1EA0-1EF9,U+1FD6,U+2007-2008,U+200B,U+2010-2011,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2042,U+2044,U+2051,U+2070,U+2074-2079,U+2080-2089,U+20AB-20AC,U+2113,U+2117,U+2122,U+2126,U+2160-2169,U+216C-216F,U+2190-2193,U+2196-2199,U+21A9,U+21B0-21B5,U+21C6,U+2202,U+2206,U+220F,U+2211-2212,U+2215,U+2219-221A,U+221E,U+222B,U+2248,U+2260,U+2264-2265,U+22C5,U+24C5,U+25A0-25A1,U+25AF,U+25B2-25B3,U+25CA-25CB,U+25CF,U+262E,U+2713,U+2715,U+2780-2788,U+E000,U+E002,U+F6C3,U+FB00-FB04,U+FEFF,U+FF0C,U+FF0E,U+FF1A-FF1B,U+FFFF;src:url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2)format("woff2"),url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff)format("woff")}[data-theme-name=spotify]{--bui-font-text:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-title:CircularSpTitle,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;--bui-font-regular:CircularSp,CircularSp-Arab,CircularSp-Hebr,CircularSp-Cyrl,CircularSp-Grek,CircularSp-Deva;& .bui-Button{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3)}& .bui-ButtonIcon{padding:0}& .bui-MenuPopup{box-sizing:border-box;max-width:21.25rem}& .bui-MenuSubmenuTrigger,& .bui-MenuItem{height:auto;min-height:2rem}& .bui-Text{font-family:var(--bui-font-text)}& .bui-Heading{font-family:var(--bui-font-title)}& .bui-TableRow{border-radius:var(--bui-radius-4);border:none}& .bui-TableRow:hover td:first-child{border-top-left-radius:var(--bui-radius-2);border-bottom-left-radius:var(--bui-radius-2)}& .bui-TableRow:hover td:last-child{border-top-right-radius:var(--bui-radius-2);border-bottom-right-radius:var(--bui-radius-2)}& .bui-TableBody:before,& .bui-TableBody:after{line-height:var(--bui-space-1);content:"‌";display:block}& .bui-TableHeader .bui-TableRow{border-bottom:1px solid var(--bui-border)}& .bui-TableHead{font-size:var(--bui-font-size-2);color:var(--bui-fg-secondary);font-weight:var(--bui-font-weight-regular)}& .bui-HeaderToolbar{padding-top:var(--bui-space-2);padding-inline:var(--bui-space-2)}& .bui-HeaderToolbarWrapper{border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-3);border:none}& .bui-HeaderToolbarControls{right:calc(var(--bui-space-3) - 1px)}& .bui-HeaderTabsWrapper{margin-top:var(--bui-space-2);margin-inline:var(--bui-space-2);border-radius:var(--bui-radius-3);padding-inline:var(--bui-space-1);border:none}& .bui-Input{border-radius:var(--bui-radius-3)}& .bui-Tag{border-radius:var(--bui-radius-full)}}[data-theme-mode=light][data-theme-name=spotify]{--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-fg-primary:var(--bui-black);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#62ab7c;--bui-border:var(--bui-gray-3);--bui-border-hover:#0000004d;--bui-border-pressed:#00000080;--bui-border-disabled:#0000001a;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#0003;& .bui-HeaderToolbarWrapper,& .bui-HeaderTabsWrapper{border:1px solid var(--bui-border)}}[data-theme-mode=dark][data-theme-name=spotify]{--bui-bg-surface-0:var(--bui-black);--bui-bg-solid:#1ed760;--bui-bg-solid-hover:#3be477;--bui-bg-solid-pressed:#1abc54;--bui-bg-solid-disabled:#0f6c30;--bui-bg-danger:#3b1219;--bui-bg-warning:#302008;--bui-bg-success:#132d21;--bui-fg-primary:var(--bui-white);--bui-fg-secondary:var(--bui-gray-7);--bui-fg-link:var(--bui-white);--bui-fg-link-hover:var(--bui-white);--bui-fg-disabled:var(--bui-gray-5);--bui-fg-solid:var(--bui-black);--bui-fg-solid-disabled:#072f15;--bui-fg-tint:var(--bui-white);--bui-fg-tint-disabled:var(--bui-gray-5);--bui-fg-danger:#e22b2b;--bui-fg-warning:#e36d05;--bui-fg-success:#1db954;--bui-border:var(--bui-gray-3);--bui-border-hover:#fff6;--bui-border-pressed:#ffffff80;--bui-border-disabled:#fff3;--bui-border-danger:#f87a7a;--bui-border-warning:#e36d05;--bui-border-success:#53db83;--bui-ring:#fff3} \ No newline at end of file diff --git a/docs-ui/scripts/sync-changelog.mjs b/docs-ui/scripts/sync-changelog.mjs index 0745979430..fa59db741b 100644 --- a/docs-ui/scripts/sync-changelog.mjs +++ b/docs-ui/scripts/sync-changelog.mjs @@ -1,5 +1,4 @@ #!/usr/bin/env node -/* eslint-disable no-restricted-syntax */ import fs from 'fs'; import path from 'path'; diff --git a/docs-ui/scripts/sync-css.js b/docs-ui/scripts/sync-css.js index d68a852ce0..a9c228ac27 100644 --- a/docs-ui/scripts/sync-css.js +++ b/docs-ui/scripts/sync-css.js @@ -6,7 +6,7 @@ const chokidar = require('chokidar'); // Configuration const config = { UIPath: '../../packages/ui', - publicPath: '../public', + outputPath: '../src/css', files: [ { source: 'css/styles.css', @@ -24,13 +24,13 @@ const config = { class CSSSync { constructor() { this.UIPath = path.resolve(__dirname, config.UIPath); - this.publicPath = path.resolve(__dirname, config.publicPath); + this.outputPath = path.resolve(__dirname, config.outputPath); this.isWatching = process.argv.includes('--watch'); } async syncFile(fileConfig) { const sourcePath = path.join(this.UIPath, fileConfig.source); - const destPath = path.join(this.publicPath, fileConfig.destination); + const destPath = path.join(this.outputPath, fileConfig.destination); try { // Check if source file exists @@ -76,9 +76,9 @@ class CSSSync { ); if (successCount > 0) { - console.log('\n📁 Available CSS files in public/:'); + console.log('\n📁 Available CSS files in src/css/:'); config.files.forEach(file => { - const destPath = path.join(this.publicPath, file.destination); + const destPath = path.join(this.outputPath, file.destination); if (fs.existsSync(destPath)) { const stats = fs.statSync(destPath); const size = (stats.size / 1024).toFixed(2); @@ -129,7 +129,7 @@ class CSSSync { async run() { console.log('🎨 BUI CSS Sync Tool\n'); console.log(`📂 BUI path: ${this.UIPath}`); - console.log(`📂 Public path: ${this.publicPath}\n`); + console.log(`📂 Output path: ${this.outputPath}\n`); // Initial sync await this.syncAll(); diff --git a/docs-ui/src/app/components/box/components.tsx b/docs-ui/src/app/components/box/components.tsx index e197ce2eb8..dac66f1f59 100644 --- a/docs-ui/src/app/components/box/components.tsx +++ b/docs-ui/src/app/components/box/components.tsx @@ -2,11 +2,12 @@ import { Box } from '../../../../../packages/ui/src/components/Box/Box'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; +import { DecorativeBox } from '@/components/DecorativeBox'; export const Default = () => { return ( - - Hello World + + ); }; @@ -36,7 +37,7 @@ export const Responsive = () => { p={{ initial: '2', md: '4' }} display={{ initial: 'block', md: 'flex' }} > - Hello World + Resize to see change ); }; diff --git a/docs-ui/src/app/components/box/page.mdx b/docs-ui/src/app/components/box/page.mdx index 4c9fa17fe9..9b60e5244a 100644 --- a/docs-ui/src/app/components/box/page.mdx +++ b/docs-ui/src/app/components/box/page.mdx @@ -34,7 +34,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ### Surface -Here's a view when boxes have different surface levels. +Use surface levels to create visual hierarchy. } code={boxSurfaceSnippet} layout="side-by-side" /> diff --git a/docs-ui/src/app/components/box/snippets.ts b/docs-ui/src/app/components/box/snippets.ts index 5da9dbc34c..e9af07dec5 100644 --- a/docs-ui/src/app/components/box/snippets.ts +++ b/docs-ui/src/app/components/box/snippets.ts @@ -4,8 +4,8 @@ export const snippetUsage = `import { Box } from '@backstage/ui'; Content with padding and background `; -export const defaultSnippet = ` - Hello World +export const defaultSnippet = ` + `; export const boxSurfaceSnippet = ` @@ -19,5 +19,5 @@ export const boxResponsiveSnippet = ` - Hello World + Content `; diff --git a/docs-ui/src/app/components/container/components.tsx b/docs-ui/src/app/components/container/components.tsx index 76eaf56ff1..a11bbd9463 100644 --- a/docs-ui/src/app/components/container/components.tsx +++ b/docs-ui/src/app/components/container/components.tsx @@ -1,33 +1,20 @@ 'use client'; import { Container } from '../../../../../packages/ui/src/components/Container/Container'; -import { Box } from '../../../../../packages/ui/src/components/Box/Box'; -import { Text } from '../../../../../packages/ui/src/components/Text/Text'; +import { DecorativeBox } from '@/components/DecorativeBox'; -export const Preview = () => { - return ( - - Content is centered with max-width - - ); -}; - -export const BasicUsagePreview = () => { +export const Default = () => { return ( - Hello World - Hello World - Hello World + Page content goes here ); }; -export const ResponsiveSpacingPreview = () => { +export const ResponsiveSpacing = () => { return ( - - Hello World - Hello World - Hello World + + Content with vertical spacing ); }; diff --git a/docs-ui/src/app/components/container/page.mdx b/docs-ui/src/app/components/container/page.mdx index 4ffca3bfc5..e0f4014a07 100644 --- a/docs-ui/src/app/components/container/page.mdx +++ b/docs-ui/src/app/components/container/page.mdx @@ -4,15 +4,10 @@ import { Snippet } from '@/components/Snippet'; import { containerPropDefs } from './props-definition'; import { containerUsageSnippet, - previewSnippet, - containerSimpleSnippet, + defaultSnippet, containerResponsiveSnippet, } from './snippets'; -import { - Preview, - BasicUsagePreview, - ResponsiveSpacingPreview, -} from './components'; +import { Default, ResponsiveSpacing } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; import { ContainerDefinition } from '../../../utils/definitions'; @@ -21,38 +16,30 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; -} code={previewSnippet} /> +} code={defaultSnippet} /> ## Usage -## API reference +## Core Concepts + +Container provides the standard page layout for plugin content. It constrains content to a maximum width, centers it horizontally, and adds consistent horizontal gutters. Use it once per page to wrap your main content area. + +## API Reference ## Examples -### Basic Usage +### Responsive spacing -} - code={containerSimpleSnippet} - layout="side-by-side" -/> +Vertical spacing props accept breakpoint objects. -### Responsive Spacing - -Spacing props accept breakpoint objects for responsive behavior. - -} - code={containerResponsiveSnippet} - layout="side-by-side" -/> +} code={containerResponsiveSnippet} /> diff --git a/docs-ui/src/app/components/container/snippets.ts b/docs-ui/src/app/components/container/snippets.ts index b16c1b81fd..a314b52387 100644 --- a/docs-ui/src/app/components/container/snippets.ts +++ b/docs-ui/src/app/components/container/snippets.ts @@ -1,19 +1,13 @@ export const containerUsageSnippet = `import { Container } from "@backstage/ui"; -Hello World!`; - -export const previewSnippet = ` - Content is centered with max-width + + {/* Your plugin's main content */} `; -export const containerSimpleSnippet = ` - Hello World - Hello World - Hello World +export const defaultSnippet = ` + Page content goes here `; -export const containerResponsiveSnippet = ` - Hello World - Hello World - Hello World +export const containerResponsiveSnippet = ` + Content with vertical spacing `; diff --git a/docs-ui/src/app/components/flex/components.tsx b/docs-ui/src/app/components/flex/components.tsx index fba9aff201..ce6cc8c05f 100644 --- a/docs-ui/src/app/components/flex/components.tsx +++ b/docs-ui/src/app/components/flex/components.tsx @@ -1,29 +1,14 @@ 'use client'; import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex'; -import { Box } from '../../../../../packages/ui/src/components/Box/Box'; - -const boxStyle = { - width: '64px', - height: '64px', - background: '#eaf2fd', - border: '1px solid #2563eb', - borderRadius: '4px', -}; - -const smallBoxStyle = { - padding: '8px 12px', - background: '#eaf2fd', - border: '1px solid #2563eb', - borderRadius: '4px', -}; +import { DecorativeBox } from '@/components/DecorativeBox'; export const Default = () => { return ( - - - + + + ); }; @@ -31,9 +16,9 @@ export const Default = () => { export const DirectionExample = () => { return ( - First - Second - Third + First + Second + Third ); }; @@ -41,9 +26,9 @@ export const DirectionExample = () => { export const ResponsiveExample = () => { return ( - Item 1 - Item 2 - Item 3 + 1 + 2 + 3 ); }; @@ -51,9 +36,9 @@ export const ResponsiveExample = () => { export const AlignExample = () => { return ( - Start - Middle - End + Start + Middle + End ); }; diff --git a/docs-ui/src/app/components/flex/snippets.ts b/docs-ui/src/app/components/flex/snippets.ts index b0c08ed140..8770880c79 100644 --- a/docs-ui/src/app/components/flex/snippets.ts +++ b/docs-ui/src/app/components/flex/snippets.ts @@ -7,25 +7,25 @@ export const flexUsageSnippet = `import { Flex } from '@backstage/ui'; `; export const defaultSnippet = ` - - - + + + `; export const flexDirectionSnippet = ` - First - Second - Third + First + Second + Third `; export const flexResponsiveSnippet = ` - Item 1 - Item 2 - Item 3 + 1 + 2 + 3 `; export const flexAlignSnippet = ` - Start - Middle - End + Start + Middle + End `; diff --git a/docs-ui/src/app/components/grid/components.tsx b/docs-ui/src/app/components/grid/components.tsx index 088897f004..0c0e5a0546 100644 --- a/docs-ui/src/app/components/grid/components.tsx +++ b/docs-ui/src/app/components/grid/components.tsx @@ -1,31 +1,17 @@ 'use client'; import { Grid } from '../../../../../packages/ui/src/components/Grid/Grid'; -import { Box } from '../../../../../packages/ui/src/components/Box/Box'; - -const boxStyle = { - height: '64px', - background: '#eaf2fd', - border: '1px solid #2563eb', - borderRadius: '4px', -}; - -const labelBoxStyle = { - padding: '8px 12px', - background: '#eaf2fd', - border: '1px solid #2563eb', - borderRadius: '4px', -}; +import { DecorativeBox } from '@/components/DecorativeBox'; export const Default = () => { return ( - - - - - - + + + + + + ); }; @@ -33,10 +19,10 @@ export const Default = () => { export const ResponsiveExample = () => { return ( - 1 - 2 - 3 - 4 + 1 + 2 + 3 + 4 ); }; @@ -44,11 +30,11 @@ export const ResponsiveExample = () => { export const GridItemExample = () => { return ( - - Spans 2 columns + + Spans 2 columns - 1 column - 1 column + 1 column + 1 column ); }; diff --git a/docs-ui/src/app/components/grid/snippets.ts b/docs-ui/src/app/components/grid/snippets.ts index f6d32b8802..2915a2ba4b 100644 --- a/docs-ui/src/app/components/grid/snippets.ts +++ b/docs-ui/src/app/components/grid/snippets.ts @@ -7,23 +7,25 @@ export const gridUsageSnippet = `import { Grid } from '@backstage/ui'; `; export const defaultSnippet = ` - - - - - - + + + + + + `; export const gridResponsiveSnippet = ` - 1 - 2 - 3 - 4 + 1 + 2 + 3 + 4 `; export const gridItemSnippet = ` - Spans 2 columns - 1 column - 1 column + + Spans 2 columns + + 1 column + 1 column `; diff --git a/docs-ui/src/app/components/link/components.tsx b/docs-ui/src/app/components/link/components.tsx index ac4bd05801..320dd52517 100644 --- a/docs-ui/src/app/components/link/components.tsx +++ b/docs-ui/src/app/components/link/components.tsx @@ -7,7 +7,9 @@ import { MemoryRouter } from 'react-router-dom'; export const Default = () => { return ( - Sign up for Backstage + + Sign up for Backstage + ); }; @@ -15,7 +17,7 @@ export const Default = () => { export const ExternalLink = () => { return ( - + Sign up for Backstage @@ -58,22 +60,25 @@ export const AllVariants = () => { export const AllColors = () => { return ( - - + + Primary - + Secondary - + Danger - + Warning - + Success + + Info + ); @@ -83,13 +88,28 @@ export const Weight = () => { return ( - + Regular - + Bold ); }; + +export const Standalone = () => { + return ( + + + + Default link + + + Standalone link + + + + ); +}; diff --git a/docs-ui/src/app/components/link/page.mdx b/docs-ui/src/app/components/link/page.mdx index 13710ee259..8715bb270b 100644 --- a/docs-ui/src/app/components/link/page.mdx +++ b/docs-ui/src/app/components/link/page.mdx @@ -10,6 +10,7 @@ import { allVariantsSnippet, allColorsSnippet, weightSnippet, + standaloneSnippet, } from './snippets'; import { Default, @@ -17,6 +18,7 @@ import { AllVariants, AllColors, Weight, + Standalone, } from './components'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -55,6 +57,7 @@ Use `target="_blank"` to open links in a new tab. py={4} preview={} code={externalLinkSnippet} + layout="side-by-side" /> ### Variants @@ -79,7 +82,25 @@ Status colors for contextual links. ### Weight -} code={weightSnippet} /> +} + code={weightSnippet} + layout="side-by-side" +/> + +### Standalone + +Use `standalone` to remove the underline by default. The underline will appear on hover. + +} + code={standaloneSnippet} + layout="side-by-side" +/> diff --git a/docs-ui/src/app/components/link/props-definition.tsx b/docs-ui/src/app/components/link/props-definition.tsx index 7d3b762a24..f0aa95114e 100644 --- a/docs-ui/src/app/components/link/props-definition.tsx +++ b/docs-ui/src/app/components/link/props-definition.tsx @@ -66,6 +66,12 @@ export const linkPropDefs: Record = { type: 'boolean', description: 'Truncates text with ellipsis when it overflows its container.', + default: 'false', + }, + standalone: { + type: 'boolean', + description: 'Removes underline by default. Underline appears on hover.', + default: 'false', }, ...childrenPropDefs, ...classNamePropDefs, diff --git a/docs-ui/src/app/components/link/snippets.ts b/docs-ui/src/app/components/link/snippets.ts index 234b3fe983..386ad9f1a0 100644 --- a/docs-ui/src/app/components/link/snippets.ts +++ b/docs-ui/src/app/components/link/snippets.ts @@ -4,7 +4,7 @@ export const linkUsageSnippet = `import { Link } from '@backstage/ui'; export const defaultSnippet = `Sign up for Backstage`; -export const externalLinkSnippet = ` +export const externalLinkSnippet = ` Sign up for Backstage `; @@ -25,9 +25,15 @@ export const allColorsSnippet = ` Danger Warning Success + Info `; export const weightSnippet = ` Regular Bold `; + +export const standaloneSnippet = ` + Default link + Standalone link +`; diff --git a/docs-ui/src/app/components/text/components.tsx b/docs-ui/src/app/components/text/components.tsx index ef803ea2fb..8021a1dde6 100644 --- a/docs-ui/src/app/components/text/components.tsx +++ b/docs-ui/src/app/components/text/components.tsx @@ -30,6 +30,7 @@ export const Colors = () => { Danger Warning Success + Info ); }; diff --git a/docs-ui/src/app/components/text/snippets.ts b/docs-ui/src/app/components/text/snippets.ts index 7f2c222186..b8cc26bd6b 100644 --- a/docs-ui/src/app/components/text/snippets.ts +++ b/docs-ui/src/app/components/text/snippets.ts @@ -21,6 +21,7 @@ export const colorsSnippet = ` Danger Warning Success + Info `; export const weightsSnippet = ` diff --git a/docs-ui/src/app/layout.tsx b/docs-ui/src/app/layout.tsx index 3647c572f5..a0f8ae5816 100644 --- a/docs-ui/src/app/layout.tsx +++ b/docs-ui/src/app/layout.tsx @@ -8,6 +8,8 @@ import { MobileBottomNav } from '@/components/MobileBottomNav'; import styles from './layout.module.css'; import '../css/globals.css'; +import '../css/theme-backstage.css'; +import '../css/theme-spotify.css'; export const metadata: Metadata = { title: 'Backstage UI', diff --git a/docs-ui/src/app/tokens/page.mdx b/docs-ui/src/app/tokens/page.mdx index 97266afbf4..fdc8aa9a90 100644 --- a/docs-ui/src/app/tokens/page.mdx +++ b/docs-ui/src/app/tokens/page.mdx @@ -240,30 +240,6 @@ color of your app. Used for solid background colors when disabled. - - - --bui-bg-tint - - Used for tint background colors. - - - - --bui-bg-tint-hover - - Used for tint background colors when hovered. - - - - --bui-bg-tint-focus - - Used for tint background colors when active. - - - - --bui-bg-tint-disabled - - Used for tint background colors when disabled. - --bui-bg-danger @@ -282,6 +258,12 @@ color of your app. Used to show success information. + + + --bui-bg-info + + Used to show informational content. + @@ -315,22 +297,6 @@ are prefixed with `fg` to make it easier to identify. It should be used on top of main background surfaces. - - - --bui-fg-link - - - It should be used on top of main background surfaces. - - - - - --bui-fg-link-hover - - - It should be used on top of main background surfaces. - - --bui-fg-disabled @@ -347,33 +313,45 @@ are prefixed with `fg` to make it easier to identify. It should be used on top of solid background colors. - - - --bui-fg-tint - - - It should be used on top of tint background colors. - - - - - --bui-fg-tint-disabled - - - It should be used on top of tint background colors when disabled. - - --bui-fg-danger + Used for error states and destructive actions. + + + + --bui-fg-warning + + + Used for warning states and cautionary information. + + + + + --bui-fg-success + + Used for success states and positive feedback. + + + + --bui-fg-info + + + Used for informational content and neutral status. + + + + + --bui-fg-danger-on-bg + It should be used on top of danger background colors. - --bui-fg-warning + --bui-fg-warning-on-bg It should be used on top of warning background colors. @@ -381,12 +359,20 @@ are prefixed with `fg` to make it easier to identify. - --bui-fg-success + --bui-fg-success-on-bg It should be used on top of success background colors. + + + --bui-fg-info-on-bg + + + It should be used on top of info background colors. + + @@ -449,6 +435,12 @@ low contrast to help as a separator with the different background colors. It should be used on top of `--bui-bg-success`. + + + --bui-border-info + + It should be used on top of `--bui-bg-info`. + diff --git a/docs-ui/src/components/CustomTheme/customTheme.tsx b/docs-ui/src/components/CustomTheme/customTheme.tsx index ae765d7d88..539113140a 100644 --- a/docs-ui/src/components/CustomTheme/customTheme.tsx +++ b/docs-ui/src/components/CustomTheme/customTheme.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, useSyncExternalStore } from 'react'; import CodeMirror from '@uiw/react-codemirror'; import { sass } from '@codemirror/lang-sass'; import styles from './styles.module.css'; @@ -14,6 +14,10 @@ const defaultTheme = `:root { --bui-bg-solid: #000; }`; +// Stable server snapshots for useSyncExternalStore +const serverIsClient = false; +const serverDefaultTheme = defaultTheme; + const myTheme = createTheme({ theme: 'light', settings: { @@ -46,12 +50,41 @@ const myTheme = createTheme({ }); export const CustomTheme = () => { - const [isClient, setIsClient] = useState(false); const [open, setOpen] = useState(true); - const [customTheme, setCustomTheme] = useState(undefined); const { selectedThemeName } = usePlayground(); const [savedMessage, setSavedMessage] = useState('Save'); + // SSR-safe client detection + const isClient = useSyncExternalStore( + () => () => {}, + () => true, + () => serverIsClient, + ); + + // SSR-safe localStorage access for custom theme + const customThemeFromStorage = useSyncExternalStore( + callback => { + window.addEventListener('storage', callback); + return () => window.removeEventListener('storage', callback); + }, + () => { + const stored = localStorage.getItem('customThemeCss'); + if (!stored) { + localStorage.setItem('customThemeCss', defaultTheme); + return defaultTheme; + } + return stored; + }, + () => serverDefaultTheme, + ); + + const [customTheme, setCustomTheme] = useState(customThemeFromStorage); + + // Sync from storage when it changes + useEffect(() => { + setCustomTheme(customThemeFromStorage); + }, [customThemeFromStorage]); + const updateStyleElement = (theme: string) => { let styleElement = document.getElementById( 'custom-theme-style', @@ -66,16 +99,11 @@ export const CustomTheme = () => { styleElement.textContent = theme; }; + // Apply custom theme to DOM useEffect(() => { - if (selectedThemeName === 'custom') { - let storedTheme = localStorage.getItem('customThemeCss'); - if (!storedTheme) { - storedTheme = defaultTheme; - localStorage.setItem('customThemeCss', storedTheme); - } - setCustomTheme(storedTheme); - updateStyleElement(storedTheme); - } else { + if (selectedThemeName === 'custom' && customTheme && isClient) { + updateStyleElement(customTheme); + } else if (isClient) { const styleElement = document.getElementById( 'custom-theme-style', ) as HTMLStyleElement; @@ -83,11 +111,7 @@ export const CustomTheme = () => { styleElement.remove(); } } - }, [selectedThemeName]); - - useEffect(() => { - setIsClient(true); - }, []); + }, [selectedThemeName, customTheme, isClient]); const handleSave = () => { if (customTheme) { diff --git a/docs-ui/src/components/DecorativeBox/index.tsx b/docs-ui/src/components/DecorativeBox/index.tsx index a5c20725bc..8af067459e 100644 --- a/docs-ui/src/components/DecorativeBox/index.tsx +++ b/docs-ui/src/components/DecorativeBox/index.tsx @@ -1,5 +1,17 @@ +'use client'; + +import { CSSProperties, ReactNode } from 'react'; import styles from './styles.module.css'; -export const DecorativeBox = () => { - return
; +interface DecorativeBoxProps { + children?: ReactNode; + style?: CSSProperties; +} + +export const DecorativeBox = ({ children, style }: DecorativeBoxProps) => { + return ( +
+ {children} +
+ ); }; diff --git a/docs-ui/src/components/DecorativeBox/styles.module.css b/docs-ui/src/components/DecorativeBox/styles.module.css index 84a6b579a0..a2a5c9407a 100644 --- a/docs-ui/src/components/DecorativeBox/styles.module.css +++ b/docs-ui/src/components/DecorativeBox/styles.module.css @@ -1,8 +1,14 @@ .box { min-width: 64px; min-height: 64px; + padding: 8px 12px; background-color: #eaf2fd; border-radius: 4px; box-shadow: 0 0 0 1px #2563eb; background-image: url("data:image/svg+xml,%3Csvg width='6' height='6' viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%232563eb' fill-opacity='0.3' fill-rule='evenodd'%3E%3Cpath d='M5 0h1L0 6V5zM6 5v1H5z'/%3E%3C/g%3E%3C/svg%3E"); + color: #2563eb; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; } diff --git a/docs-ui/src/components/TableOfContents/TableOfContents.tsx b/docs-ui/src/components/TableOfContents/TableOfContents.tsx index d18265415b..429d677b80 100644 --- a/docs-ui/src/components/TableOfContents/TableOfContents.tsx +++ b/docs-ui/src/components/TableOfContents/TableOfContents.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useLayoutEffect } from 'react'; import { usePathname } from 'next/navigation'; import styles from './TableOfContents.module.css'; @@ -17,6 +17,29 @@ export function TableOfContents() { const [indicatorHeight, setIndicatorHeight] = useState(0); const pathname = usePathname(); + // Update indicator position when activeId changes + useLayoutEffect(() => { + if (!activeId) return; + + // Use requestAnimationFrame to defer setState call + const rafId = requestAnimationFrame(() => { + const activeElement = document.querySelector( + `[data-toc-id="${activeId}"]`, + ) as HTMLElement; + if (activeElement) { + const list = activeElement.closest('ul'); + if (list) { + const listRect = list.getBoundingClientRect(); + const elementRect = activeElement.getBoundingClientRect(); + setIndicatorTop(elementRect.top - listRect.top); + setIndicatorHeight(elementRect.height); + } + } + }); + + return () => cancelAnimationFrame(rafId); + }, [activeId, headings]); + useEffect(() => { // Extract all H2 and H3 headings from the document const elements = Array.from( @@ -29,18 +52,6 @@ export function TableOfContents() { level: parseInt(element.tagName.substring(1)), })); - setHeadings(headingData); - - // Set initial active heading (first visible heading or first heading) - if (headingData.length > 0) { - const viewportTop = window.scrollY + 100; // offset for header - const visibleHeading = elements.find(element => { - const rect = element.getBoundingClientRect(); - return rect.top + window.scrollY >= viewportTop - 200; - }); - setActiveId(visibleHeading?.id || headingData[0].id); - } - // Set up IntersectionObserver to track visible headings const observerOptions = { rootMargin: '-80px 0px -80% 0px', @@ -62,29 +73,26 @@ export function TableOfContents() { elements.forEach(element => observer.observe(element)); + // Initialize headings and active ID after observer is set up + requestAnimationFrame(() => { + setHeadings(headingData); + + // Set initial active heading (first visible heading or first heading) + if (headingData.length > 0) { + const viewportTop = window.scrollY + 100; // offset for header + const visibleHeading = elements.find(element => { + const rect = element.getBoundingClientRect(); + return rect.top + window.scrollY >= viewportTop - 200; + }); + setActiveId(visibleHeading?.id || headingData[0].id); + } + }); + return () => { elements.forEach(element => observer.unobserve(element)); }; }, [pathname]); - // Update indicator position when activeId changes - useEffect(() => { - if (activeId) { - const activeElement = document.querySelector( - `[data-toc-id="${activeId}"]`, - ) as HTMLElement; - if (activeElement) { - const list = activeElement.closest('ul'); - if (list) { - const listRect = list.getBoundingClientRect(); - const elementRect = activeElement.getBoundingClientRect(); - setIndicatorTop(elementRect.top - listRect.top); - setIndicatorHeight(elementRect.height); - } - } - } - }, [activeId, headings]); - const handleClick = (id: string) => { const element = document.getElementById(id); if (element) { diff --git a/docs-ui/src/css/theme-backstage.css b/docs-ui/src/css/theme-backstage.css new file mode 100644 index 0000000000..e1ba6ba5b0 --- /dev/null +++ b/docs-ui/src/css/theme-backstage.css @@ -0,0 +1,7188 @@ +@layer tokens { + :root { + --bui-font-regular: system-ui; + --bui-font-monospace: ui-monospace, 'Menlo', 'Monaco', 'Consolas', + 'Liberation Mono', 'Courier New', monospace; + --bui-font-weight-regular: 400; + --bui-font-weight-bold: 600; + --bui-font-size-1: 0.625rem; + --bui-font-size-2: 0.75rem; + --bui-font-size-3: 0.875rem; + --bui-font-size-4: 1rem; + --bui-font-size-5: 1.25rem; + --bui-font-size-6: 1.5rem; + --bui-font-size-7: 2rem; + --bui-font-size-8: 3rem; + --bui-font-size-9: 4rem; + --bui-font-size-10: 5.75rem; + --bui-space: 0.25rem; + --bui-space-0_5: calc(var(--bui-space) * 0.5); + --bui-space-1: var(--bui-space); + --bui-space-1_5: calc(var(--bui-space) * 1.5); + --bui-space-2: calc(var(--bui-space) * 2); + --bui-space-3: calc(var(--bui-space) * 3); + --bui-space-4: calc(var(--bui-space) * 4); + --bui-space-5: calc(var(--bui-space) * 5); + --bui-space-6: calc(var(--bui-space) * 6); + --bui-space-7: calc(var(--bui-space) * 7); + --bui-space-8: calc(var(--bui-space) * 8); + --bui-space-9: calc(var(--bui-space) * 9); + --bui-space-10: calc(var(--bui-space) * 10); + --bui-space-11: calc(var(--bui-space) * 11); + --bui-space-12: calc(var(--bui-space) * 12); + --bui-space-13: calc(var(--bui-space) * 13); + --bui-space-14: calc(var(--bui-space) * 14); + --bui-radius-1: calc(0.125rem); + --bui-radius-2: calc(0.25rem); + --bui-radius-3: calc(0.5rem); + --bui-radius-4: calc(0.75rem); + --bui-radius-5: calc(1rem); + --bui-radius-6: calc(1.25rem); + --bui-radius-full: 9999px; + --bui-black: #000; + --bui-white: #fff; + --bui-gray-1: #f8f8f8; + --bui-gray-2: #ececec; + --bui-gray-3: #d9d9d9; + --bui-gray-4: #c1c1c1; + --bui-gray-5: #9e9e9e; + --bui-gray-6: #8c8c8c; + --bui-gray-7: #757575; + --bui-gray-8: #595959; + --bui-bg-surface-0: var(--bui-gray-1); + --bui-bg-surface-1: var(--bui-white); + --bui-bg-surface-2: var(--bui-gray-1); + --bui-bg-surface-3: var(--bui-gray-2); + --bui-bg-solid: #1f5493; + --bui-bg-solid-hover: #163a66; + --bui-bg-solid-pressed: #0f2b4e; + --bui-bg-solid-disabled: #163a66; + --bui-bg-neutral-on-surface-0: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-0-hover: oklch(0% 0 0/0.12); + --bui-bg-neutral-on-surface-0-pressed: oklch(0% 0 0/0.16); + --bui-bg-neutral-on-surface-0-disabled: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-1: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-1-hover: oklch(0% 0 0/0.12); + --bui-bg-neutral-on-surface-1-pressed: oklch(0% 0 0/0.16); + --bui-bg-neutral-on-surface-1-disabled: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-2: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-2-hover: oklch(0% 0 0/0.12); + --bui-bg-neutral-on-surface-2-pressed: oklch(0% 0 0/0.16); + --bui-bg-neutral-on-surface-2-disabled: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-3: oklch(0% 0 0/0.06); + --bui-bg-neutral-on-surface-3-hover: oklch(0% 0 0/0.12); + --bui-bg-neutral-on-surface-3-pressed: oklch(0% 0 0/0.16); + --bui-bg-neutral-on-surface-3-disabled: oklch(0% 0 0/0.06); + --bui-bg-danger: #ffe2e2; + --bui-bg-warning: #ffedd5; + --bui-bg-success: #dcfce7; + --bui-bg-info: #dbeafe; + --bui-fg-primary: var(--bui-black); + --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-disabled: #9e9e9e; + --bui-fg-solid: var(--bui-white); + --bui-fg-solid-disabled: #98a8bc; + --bui-fg-danger-on-bg: #991919; + --bui-fg-warning-on-bg: #92310a; + --bui-fg-success-on-bg: #116932; + --bui-fg-info-on-bg: #173da6; + --bui-fg-danger: #ec3b18; + --bui-fg-warning: #ef7a32; + --bui-fg-success: #1ed760; + --bui-fg-info: #0d74ce; + --bui-border: #0000001a; + --bui-border-hover: #0003; + --bui-border-pressed: #0006; + --bui-border-disabled: #0000001a; + --bui-border-info: #7ea9d6; + --bui-border-danger: #f87a7a; + --bui-border-warning: #e36d05; + --bui-border-success: #53db83; + --bui-ring: #1f5493; + --bui-scrollbar: #a0a0a03b; + --bui-scrollbar-thumb: #a0a0a0; + --bui-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; + --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + [data-theme-mode='dark'] { + --bui-gray-1: #191919; + --bui-gray-2: #242424; + --bui-gray-3: #373737; + --bui-gray-4: #464646; + --bui-gray-5: #575757; + --bui-gray-6: #7b7b7b; + --bui-gray-7: #9e9e9e; + --bui-gray-8: #b4b4b4; + --bui-bg-surface-0: #333; + --bui-bg-surface-1: var(--bui-gray-1); + --bui-bg-surface-2: var(--bui-gray-2); + --bui-bg-surface-3: var(--bui-gray-3); + --bui-bg-solid: #9cc9ff; + --bui-bg-solid-hover: #83b9fd; + --bui-bg-solid-pressed: #83b9fd; + --bui-bg-solid-disabled: #1b3d68; + --bui-bg-neutral-on-surface-0: oklch(100% 0 0/0.1); + --bui-bg-neutral-on-surface-0-hover: oklch(100% 0 0/0.14); + --bui-bg-neutral-on-surface-0-pressed: oklch(100% 0 0/0.2); + --bui-bg-neutral-on-surface-0-disabled: oklch(100% 0 0/0.1); + --bui-bg-neutral-on-surface-1: oklch(100% 0 0/0.06); + --bui-bg-neutral-on-surface-1-hover: oklch(100% 0 0/0.1); + --bui-bg-neutral-on-surface-1-pressed: oklch(100% 0 0/0.16); + --bui-bg-neutral-on-surface-1-disabled: oklch(100% 0 0/0.06); + --bui-bg-neutral-on-surface-2: oklch(100% 0 0/0.08); + --bui-bg-neutral-on-surface-2-hover: oklch(100% 0 0/0.12); + --bui-bg-neutral-on-surface-2-pressed: oklch(100% 0 0/0.2); + --bui-bg-neutral-on-surface-2-disabled: oklch(100% 0 0/0.08); + --bui-bg-neutral-on-surface-3: oklch(100% 0 0/0.08); + --bui-bg-neutral-on-surface-3-hover: oklch(100% 0 0/0.12); + --bui-bg-neutral-on-surface-3-pressed: oklch(100% 0 0/0.2); + --bui-bg-neutral-on-surface-3-disabled: oklch(100% 0 0/0.08); + --bui-bg-danger: #300c0c; + --bui-bg-warning: #302008; + --bui-bg-success: #042713; + --bui-bg-info: #132049; + --bui-fg-primary: var(--bui-white); + --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-disabled: var(--bui-gray-7); + --bui-fg-solid: #101821; + --bui-fg-solid-disabled: #6191cc; + --bui-fg-danger-on-bg: #fca5a5; + --bui-fg-warning-on-bg: #fdba74; + --bui-fg-success-on-bg: #86efac; + --bui-fg-info-on-bg: #a3cfff; + --bui-fg-danger: #ff5a30; + --bui-fg-warning: #ffa057; + --bui-fg-success: #1ed760; + --bui-fg-info: #70b8ff; + --bui-border: #ffffff1f; + --bui-border-hover: #fff6; + --bui-border-pressed: #ffffff80; + --bui-border-disabled: #fff3; + --bui-border-info: #7ea9d6; + --bui-border-danger: #f87a7a; + --bui-border-warning: #e36d05; + --bui-border-success: #53db83; + --bui-ring: #1f5493; + --bui-scrollbar: #3636363a; + --bui-scrollbar-thumb: #575757; + --bui-shadow: none; + } +} +@layer base { + *, + :before, + :after { + box-sizing: border-box; + } + html { + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif, + Apple Color Emoji, Segoe UI Emoji; + line-height: 1.15; + } + body { + margin: 0; + } + b, + strong { + font-weight: bolder; + } + code, + kbd, + samp, + pre { + font-family: ui-monospace, SFMono-Regular, Consolas, Liberation Mono, Menlo, + monospace; + font-size: 1em; + } + small { + font-size: 80%; + } + sub, + sup { + vertical-align: baseline; + font-size: 75%; + line-height: 0; + position: relative; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + border-color: currentColor; + } + button, + input, + optgroup, + select, + textarea { + margin: 0; + font-family: inherit; + font-size: 100%; + line-height: 1.15; + } + button, + [type='button'], + [type='reset'], + [type='submit'] { + -webkit-appearance: button; + } + legend { + padding: 0; + } + progress { + vertical-align: baseline; + } + ::-webkit-inner-spin-button, + ::-webkit-outer-spin-button { + height: auto; + } + [type='search'] { + -webkit-appearance: textfield; + outline-offset: -2px; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; + } + summary { + display: list-item; + } + :where(a) { + color: inherit; + text-decoration: none; + } + @keyframes pulse { + 50% { + opacity: 0.5; + } + } + body { + background-color: var(--bui-bg-surface-0); + color: var(--bui-fg-primary); + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-regular); + font-size: var(--bui-font-size-3); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + [data-theme-mode='dark'] { + color-scheme: dark; + } + [data-theme-mode='light'] { + color-scheme: light; + } +} +@layer components; +@layer utilities { + .bui-p { + padding: var(--p); + } + .bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + .bui-p-1 { + padding: var(--bui-space-1); + } + .bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + .bui-p-2 { + padding: var(--bui-space-2); + } + .bui-p-3 { + padding: var(--bui-space-3); + } + .bui-p-4 { + padding: var(--bui-space-4); + } + .bui-p-5 { + padding: var(--bui-space-5); + } + .bui-p-6 { + padding: var(--bui-space-6); + } + .bui-p-7 { + padding: var(--bui-space-7); + } + .bui-p-8 { + padding: var(--bui-space-8); + } + .bui-p-9 { + padding: var(--bui-space-9); + } + .bui-p-10 { + padding: var(--bui-space-10); + } + .bui-p-11 { + padding: var(--bui-space-11); + } + .bui-p-12 { + padding: var(--bui-space-12); + } + .bui-p-13 { + padding: var(--bui-space-13); + } + .bui-p-14 { + padding: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-p { + padding: var(--p-xs); + } + .xs\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + .xs\:bui-p-1 { + padding: var(--bui-space-1); + } + .xs\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + .xs\:bui-p-2 { + padding: var(--bui-space-2); + } + .xs\:bui-p-3 { + padding: var(--bui-space-3); + } + .xs\:bui-p-4 { + padding: var(--bui-space-4); + } + .xs\:bui-p-5 { + padding: var(--bui-space-5); + } + .xs\:bui-p-6 { + padding: var(--bui-space-6); + } + .xs\:bui-p-7 { + padding: var(--bui-space-7); + } + .xs\:bui-p-8 { + padding: var(--bui-space-8); + } + .xs\:bui-p-9 { + padding: var(--bui-space-9); + } + .xs\:bui-p-10 { + padding: var(--bui-space-10); + } + .xs\:bui-p-11 { + padding: var(--bui-space-11); + } + .xs\:bui-p-12 { + padding: var(--bui-space-12); + } + .xs\:bui-p-13 { + padding: var(--bui-space-13); + } + .xs\:bui-p-14 { + padding: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-p { + padding: var(--p-sm); + } + .sm\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + .sm\:bui-p-1 { + padding: var(--bui-space-1); + } + .sm\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + .sm\:bui-p-2 { + padding: var(--bui-space-2); + } + .sm\:bui-p-3 { + padding: var(--bui-space-3); + } + .sm\:bui-p-4 { + padding: var(--bui-space-4); + } + .sm\:bui-p-5 { + padding: var(--bui-space-5); + } + .sm\:bui-p-6 { + padding: var(--bui-space-6); + } + .sm\:bui-p-7 { + padding: var(--bui-space-7); + } + .sm\:bui-p-8 { + padding: var(--bui-space-8); + } + .sm\:bui-p-9 { + padding: var(--bui-space-9); + } + .sm\:bui-p-10 { + padding: var(--bui-space-10); + } + .sm\:bui-p-11 { + padding: var(--bui-space-11); + } + .sm\:bui-p-12 { + padding: var(--bui-space-12); + } + .sm\:bui-p-13 { + padding: var(--bui-space-13); + } + .sm\:bui-p-14 { + padding: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-p { + padding: var(--p-md); + } + .md\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + .md\:bui-p-1 { + padding: var(--bui-space-1); + } + .md\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + .md\:bui-p-2 { + padding: var(--bui-space-2); + } + .md\:bui-p-3 { + padding: var(--bui-space-3); + } + .md\:bui-p-4 { + padding: var(--bui-space-4); + } + .md\:bui-p-5 { + padding: var(--bui-space-5); + } + .md\:bui-p-6 { + padding: var(--bui-space-6); + } + .md\:bui-p-7 { + padding: var(--bui-space-7); + } + .md\:bui-p-8 { + padding: var(--bui-space-8); + } + .md\:bui-p-9 { + padding: var(--bui-space-9); + } + .md\:bui-p-10 { + padding: var(--bui-space-10); + } + .md\:bui-p-11 { + padding: var(--bui-space-11); + } + .md\:bui-p-12 { + padding: var(--bui-space-12); + } + .md\:bui-p-13 { + padding: var(--bui-space-13); + } + .md\:bui-p-14 { + padding: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-p { + padding: var(--p-lg); + } + .lg\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + .lg\:bui-p-1 { + padding: var(--bui-space-1); + } + .lg\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + .lg\:bui-p-2 { + padding: var(--bui-space-2); + } + .lg\:bui-p-3 { + padding: var(--bui-space-3); + } + .lg\:bui-p-4 { + padding: var(--bui-space-4); + } + .lg\:bui-p-5 { + padding: var(--bui-space-5); + } + .lg\:bui-p-6 { + padding: var(--bui-space-6); + } + .lg\:bui-p-7 { + padding: var(--bui-space-7); + } + .lg\:bui-p-8 { + padding: var(--bui-space-8); + } + .lg\:bui-p-9 { + padding: var(--bui-space-9); + } + .lg\:bui-p-10 { + padding: var(--bui-space-10); + } + .lg\:bui-p-11 { + padding: var(--bui-space-11); + } + .lg\:bui-p-12 { + padding: var(--bui-space-12); + } + .lg\:bui-p-13 { + padding: var(--bui-space-13); + } + .lg\:bui-p-14 { + padding: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-p { + padding: var(--p-xl); + } + .xl\:bui-p-0\.5 { + padding: var(--bui-space-0_5); + } + .xl\:bui-p-1 { + padding: var(--bui-space-1); + } + .xl\:bui-p-1\.5 { + padding: var(--bui-space-1_5); + } + .xl\:bui-p-2 { + padding: var(--bui-space-2); + } + .xl\:bui-p-3 { + padding: var(--bui-space-3); + } + .xl\:bui-p-4 { + padding: var(--bui-space-4); + } + .xl\:bui-p-5 { + padding: var(--bui-space-5); + } + .xl\:bui-p-6 { + padding: var(--bui-space-6); + } + .xl\:bui-p-7 { + padding: var(--bui-space-7); + } + .xl\:bui-p-8 { + padding: var(--bui-space-8); + } + .xl\:bui-p-9 { + padding: var(--bui-space-9); + } + .xl\:bui-p-10 { + padding: var(--bui-space-10); + } + .xl\:bui-p-11 { + padding: var(--bui-space-11); + } + .xl\:bui-p-12 { + padding: var(--bui-space-12); + } + .xl\:bui-p-13 { + padding: var(--bui-space-13); + } + .xl\:bui-p-14 { + padding: var(--bui-space-14); + } + } + .bui-pl { + padding-left: var(--pl); + } + .bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + .bui-pl-1 { + padding-left: var(--bui-space-1); + } + .bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + .bui-pl-2 { + padding-left: var(--bui-space-2); + } + .bui-pl-3 { + padding-left: var(--bui-space-3); + } + .bui-pl-4 { + padding-left: var(--bui-space-4); + } + .bui-pl-5 { + padding-left: var(--bui-space-5); + } + .bui-pl-6 { + padding-left: var(--bui-space-6); + } + .bui-pl-7 { + padding-left: var(--bui-space-7); + } + .bui-pl-8 { + padding-left: var(--bui-space-8); + } + .bui-pl-9 { + padding-left: var(--bui-space-9); + } + .bui-pl-10 { + padding-left: var(--bui-space-10); + } + .bui-pl-11 { + padding-left: var(--bui-space-11); + } + .bui-pl-12 { + padding-left: var(--bui-space-12); + } + .bui-pl-13 { + padding-left: var(--bui-space-13); + } + .bui-pl-14 { + padding-left: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-pl { + padding-left: var(--pl-xs); + } + .xs\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + .xs\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + .xs\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + .xs\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + .xs\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + .xs\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + .xs\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + .xs\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + .xs\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + .xs\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + .xs\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + .xs\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + .xs\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + .xs\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + .xs\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + .xs\:bui-pl-14 { + padding-left: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-pl { + padding-left: var(--pl-sm); + } + .sm\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + .sm\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + .sm\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + .sm\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + .sm\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + .sm\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + .sm\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + .sm\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + .sm\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + .sm\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + .sm\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + .sm\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + .sm\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + .sm\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + .sm\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + .sm\:bui-pl-14 { + padding-left: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-pl { + padding-left: var(--pl-md); + } + .md\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + .md\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + .md\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + .md\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + .md\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + .md\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + .md\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + .md\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + .md\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + .md\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + .md\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + .md\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + .md\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + .md\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + .md\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + .md\:bui-pl-14 { + padding-left: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-pl { + padding-left: var(--pl-lg); + } + .lg\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + .lg\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + .lg\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + .lg\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + .lg\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + .lg\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + .lg\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + .lg\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + .lg\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + .lg\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + .lg\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + .lg\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + .lg\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + .lg\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + .lg\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + .lg\:bui-pl-14 { + padding-left: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-pl { + padding-left: var(--pl-xl); + } + .xl\:bui-pl-0\.5 { + padding-left: var(--bui-space-0_5); + } + .xl\:bui-pl-1 { + padding-left: var(--bui-space-1); + } + .xl\:bui-pl-1\.5 { + padding-left: var(--bui-space-1_5); + } + .xl\:bui-pl-2 { + padding-left: var(--bui-space-2); + } + .xl\:bui-pl-3 { + padding-left: var(--bui-space-3); + } + .xl\:bui-pl-4 { + padding-left: var(--bui-space-4); + } + .xl\:bui-pl-5 { + padding-left: var(--bui-space-5); + } + .xl\:bui-pl-6 { + padding-left: var(--bui-space-6); + } + .xl\:bui-pl-7 { + padding-left: var(--bui-space-7); + } + .xl\:bui-pl-8 { + padding-left: var(--bui-space-8); + } + .xl\:bui-pl-9 { + padding-left: var(--bui-space-9); + } + .xl\:bui-pl-10 { + padding-left: var(--bui-space-10); + } + .xl\:bui-pl-11 { + padding-left: var(--bui-space-11); + } + .xl\:bui-pl-12 { + padding-left: var(--bui-space-12); + } + .xl\:bui-pl-13 { + padding-left: var(--bui-space-13); + } + .xl\:bui-pl-14 { + padding-left: var(--bui-space-14); + } + } + .bui-pr { + padding-right: var(--pr); + } + .bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + .bui-pr-1 { + padding-right: var(--bui-space-1); + } + .bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + .bui-pr-2 { + padding-right: var(--bui-space-2); + } + .bui-pr-3 { + padding-right: var(--bui-space-3); + } + .bui-pr-4 { + padding-right: var(--bui-space-4); + } + .bui-pr-5 { + padding-right: var(--bui-space-5); + } + .bui-pr-6 { + padding-right: var(--bui-space-6); + } + .bui-pr-7 { + padding-right: var(--bui-space-7); + } + .bui-pr-8 { + padding-right: var(--bui-space-8); + } + .bui-pr-9 { + padding-right: var(--bui-space-9); + } + .bui-pr-10 { + padding-right: var(--bui-space-10); + } + .bui-pr-11 { + padding-right: var(--bui-space-11); + } + .bui-pr-12 { + padding-right: var(--bui-space-12); + } + .bui-pr-13 { + padding-right: var(--bui-space-13); + } + .bui-pr-14 { + padding-right: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-pr { + padding-right: var(--pr-xs); + } + .xs\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + .xs\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + .xs\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + .xs\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + .xs\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + .xs\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + .xs\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + .xs\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + .xs\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + .xs\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + .xs\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + .xs\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + .xs\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + .xs\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + .xs\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + .xs\:bui-pr-14 { + padding-right: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-pr { + padding-right: var(--pr-sm); + } + .sm\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + .sm\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + .sm\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + .sm\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + .sm\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + .sm\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + .sm\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + .sm\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + .sm\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + .sm\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + .sm\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + .sm\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + .sm\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + .sm\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + .sm\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + .sm\:bui-pr-14 { + padding-right: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-pr { + padding-right: var(--pr-md); + } + .md\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + .md\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + .md\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + .md\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + .md\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + .md\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + .md\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + .md\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + .md\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + .md\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + .md\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + .md\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + .md\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + .md\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + .md\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + .md\:bui-pr-14 { + padding-right: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-pr { + padding-right: var(--pr-lg); + } + .lg\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + .lg\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + .lg\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + .lg\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + .lg\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + .lg\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + .lg\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + .lg\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + .lg\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + .lg\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + .lg\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + .lg\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + .lg\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + .lg\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + .lg\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + .lg\:bui-pr-14 { + padding-right: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-pr { + padding-right: var(--pr-xl); + } + .xl\:bui-pr-0\.5 { + padding-right: var(--bui-space-0_5); + } + .xl\:bui-pr-1 { + padding-right: var(--bui-space-1); + } + .xl\:bui-pr-1\.5 { + padding-right: var(--bui-space-1_5); + } + .xl\:bui-pr-2 { + padding-right: var(--bui-space-2); + } + .xl\:bui-pr-3 { + padding-right: var(--bui-space-3); + } + .xl\:bui-pr-4 { + padding-right: var(--bui-space-4); + } + .xl\:bui-pr-5 { + padding-right: var(--bui-space-5); + } + .xl\:bui-pr-6 { + padding-right: var(--bui-space-6); + } + .xl\:bui-pr-7 { + padding-right: var(--bui-space-7); + } + .xl\:bui-pr-8 { + padding-right: var(--bui-space-8); + } + .xl\:bui-pr-9 { + padding-right: var(--bui-space-9); + } + .xl\:bui-pr-10 { + padding-right: var(--bui-space-10); + } + .xl\:bui-pr-11 { + padding-right: var(--bui-space-11); + } + .xl\:bui-pr-12 { + padding-right: var(--bui-space-12); + } + .xl\:bui-pr-13 { + padding-right: var(--bui-space-13); + } + .xl\:bui-pr-14 { + padding-right: var(--bui-space-14); + } + } + .bui-pt { + padding-top: var(--pt); + } + .bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + .bui-pt-1 { + padding-top: var(--bui-space-1); + } + .bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + .bui-pt-2 { + padding-top: var(--bui-space-2); + } + .bui-pt-3 { + padding-top: var(--bui-space-3); + } + .bui-pt-4 { + padding-top: var(--bui-space-4); + } + .bui-pt-5 { + padding-top: var(--bui-space-5); + } + .bui-pt-6 { + padding-top: var(--bui-space-6); + } + .bui-pt-7 { + padding-top: var(--bui-space-7); + } + .bui-pt-8 { + padding-top: var(--bui-space-8); + } + .bui-pt-9 { + padding-top: var(--bui-space-9); + } + .bui-pt-10 { + padding-top: var(--bui-space-10); + } + .bui-pt-11 { + padding-top: var(--bui-space-11); + } + .bui-pt-12 { + padding-top: var(--bui-space-12); + } + .bui-pt-13 { + padding-top: var(--bui-space-13); + } + .bui-pt-14 { + padding-top: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-pt { + padding-top: var(--pt-xs); + } + .xs\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + .xs\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + .xs\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + .xs\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + .xs\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + .xs\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + .xs\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + .xs\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + .xs\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + .xs\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + .xs\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + .xs\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + .xs\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + .xs\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + .xs\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + .xs\:bui-pt-14 { + padding-top: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-pt { + padding-top: var(--pt-sm); + } + .sm\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + .sm\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + .sm\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + .sm\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + .sm\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + .sm\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + .sm\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + .sm\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + .sm\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + .sm\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + .sm\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + .sm\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + .sm\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + .sm\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + .sm\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + .sm\:bui-pt-14 { + padding-top: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-pt { + padding-top: var(--pt-md); + } + .md\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + .md\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + .md\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + .md\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + .md\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + .md\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + .md\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + .md\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + .md\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + .md\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + .md\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + .md\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + .md\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + .md\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + .md\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + .md\:bui-pt-14 { + padding-top: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-pt { + padding-top: var(--pt-lg); + } + .lg\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + .lg\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + .lg\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + .lg\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + .lg\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + .lg\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + .lg\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + .lg\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + .lg\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + .lg\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + .lg\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + .lg\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + .lg\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + .lg\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + .lg\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + .lg\:bui-pt-14 { + padding-top: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-pt { + padding-top: var(--pt-xl); + } + .xl\:bui-pt-0\.5 { + padding-top: var(--bui-space-0_5); + } + .xl\:bui-pt-1 { + padding-top: var(--bui-space-1); + } + .xl\:bui-pt-1\.5 { + padding-top: var(--bui-space-1_5); + } + .xl\:bui-pt-2 { + padding-top: var(--bui-space-2); + } + .xl\:bui-pt-3 { + padding-top: var(--bui-space-3); + } + .xl\:bui-pt-4 { + padding-top: var(--bui-space-4); + } + .xl\:bui-pt-5 { + padding-top: var(--bui-space-5); + } + .xl\:bui-pt-6 { + padding-top: var(--bui-space-6); + } + .xl\:bui-pt-7 { + padding-top: var(--bui-space-7); + } + .xl\:bui-pt-8 { + padding-top: var(--bui-space-8); + } + .xl\:bui-pt-9 { + padding-top: var(--bui-space-9); + } + .xl\:bui-pt-10 { + padding-top: var(--bui-space-10); + } + .xl\:bui-pt-11 { + padding-top: var(--bui-space-11); + } + .xl\:bui-pt-12 { + padding-top: var(--bui-space-12); + } + .xl\:bui-pt-13 { + padding-top: var(--bui-space-13); + } + .xl\:bui-pt-14 { + padding-top: var(--bui-space-14); + } + } + .bui-pb { + padding-bottom: var(--pb); + } + .bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + .bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + .bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + .bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + .bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + .bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + .bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + .bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + .bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + .bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + .bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + .bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + .bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + .bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + .bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + .bui-pb-14 { + padding-bottom: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-pb { + padding-bottom: var(--pb-xs); + } + .xs\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + .xs\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + .xs\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + .xs\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + .xs\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + .xs\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + .xs\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + .xs\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + .xs\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + .xs\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + .xs\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + .xs\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + .xs\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + .xs\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + .xs\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + .xs\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-pb { + padding-bottom: var(--pb-sm); + } + .sm\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + .sm\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + .sm\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + .sm\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + .sm\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + .sm\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + .sm\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + .sm\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + .sm\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + .sm\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + .sm\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + .sm\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + .sm\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + .sm\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + .sm\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + .sm\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-pb { + padding-bottom: var(--pb-md); + } + .md\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + .md\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + .md\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + .md\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + .md\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + .md\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + .md\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + .md\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + .md\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + .md\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + .md\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + .md\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + .md\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + .md\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + .md\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + .md\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-pb { + padding-bottom: var(--pb-lg); + } + .lg\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + .lg\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + .lg\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + .lg\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + .lg\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + .lg\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + .lg\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + .lg\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + .lg\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + .lg\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + .lg\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + .lg\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + .lg\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + .lg\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + .lg\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + .lg\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-pb { + padding-bottom: var(--pb-xl); + } + .xl\:bui-pb-0\.5 { + padding-bottom: var(--bui-space-0_5); + } + .xl\:bui-pb-1 { + padding-bottom: var(--bui-space-1); + } + .xl\:bui-pb-1\.5 { + padding-bottom: var(--bui-space-1_5); + } + .xl\:bui-pb-2 { + padding-bottom: var(--bui-space-2); + } + .xl\:bui-pb-3 { + padding-bottom: var(--bui-space-3); + } + .xl\:bui-pb-4 { + padding-bottom: var(--bui-space-4); + } + .xl\:bui-pb-5 { + padding-bottom: var(--bui-space-5); + } + .xl\:bui-pb-6 { + padding-bottom: var(--bui-space-6); + } + .xl\:bui-pb-7 { + padding-bottom: var(--bui-space-7); + } + .xl\:bui-pb-8 { + padding-bottom: var(--bui-space-8); + } + .xl\:bui-pb-9 { + padding-bottom: var(--bui-space-9); + } + .xl\:bui-pb-10 { + padding-bottom: var(--bui-space-10); + } + .xl\:bui-pb-11 { + padding-bottom: var(--bui-space-11); + } + .xl\:bui-pb-12 { + padding-bottom: var(--bui-space-12); + } + .xl\:bui-pb-13 { + padding-bottom: var(--bui-space-13); + } + .xl\:bui-pb-14 { + padding-bottom: var(--bui-space-14); + } + } + .bui-py { + padding-top: var(--py); + padding-bottom: var(--py); + } + .bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + .bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + .bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + .bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + .bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + .bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + .bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + .bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + .bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + .bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + .bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + .bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + .bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + .bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + .bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + .bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-py { + padding-top: var(--py-xs); + padding-bottom: var(--py-xs); + } + .xs\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + .xs\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + .xs\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + .xs\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + .xs\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + .xs\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + .xs\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + .xs\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + .xs\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + .xs\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + .xs\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + .xs\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + .xs\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + .xs\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + .xs\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + .xs\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-py { + padding-top: var(--py-sm); + padding-bottom: var(--py-sm); + } + .sm\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + .sm\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + .sm\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + .sm\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + .sm\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + .sm\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + .sm\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + .sm\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + .sm\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + .sm\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + .sm\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + .sm\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + .sm\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + .sm\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + .sm\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + .sm\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-py { + padding-top: var(--py-md); + padding-bottom: var(--py-md); + } + .md\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + .md\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + .md\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + .md\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + .md\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + .md\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + .md\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + .md\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + .md\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + .md\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + .md\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + .md\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + .md\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + .md\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + .md\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + .md\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-py { + padding-top: var(--py-lg); + padding-bottom: var(--py-lg); + } + .lg\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + .lg\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + .lg\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + .lg\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + .lg\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + .lg\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + .lg\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + .lg\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + .lg\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + .lg\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + .lg\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + .lg\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + .lg\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + .lg\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + .lg\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + .lg\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-py { + padding-top: var(--py-xl); + padding-bottom: var(--py-xl); + } + .xl\:bui-py-0\.5 { + padding-top: var(--bui-space-0_5); + padding-bottom: var(--bui-space-0_5); + } + .xl\:bui-py-1 { + padding-top: var(--bui-space-1); + padding-bottom: var(--bui-space-1); + } + .xl\:bui-py-1\.5 { + padding-top: var(--bui-space-1_5); + padding-bottom: var(--bui-space-1_5); + } + .xl\:bui-py-2 { + padding-top: var(--bui-space-2); + padding-bottom: var(--bui-space-2); + } + .xl\:bui-py-3 { + padding-top: var(--bui-space-3); + padding-bottom: var(--bui-space-3); + } + .xl\:bui-py-4 { + padding-top: var(--bui-space-4); + padding-bottom: var(--bui-space-4); + } + .xl\:bui-py-5 { + padding-top: var(--bui-space-5); + padding-bottom: var(--bui-space-5); + } + .xl\:bui-py-6 { + padding-top: var(--bui-space-6); + padding-bottom: var(--bui-space-6); + } + .xl\:bui-py-7 { + padding-top: var(--bui-space-7); + padding-bottom: var(--bui-space-7); + } + .xl\:bui-py-8 { + padding-top: var(--bui-space-8); + padding-bottom: var(--bui-space-8); + } + .xl\:bui-py-9 { + padding-top: var(--bui-space-9); + padding-bottom: var(--bui-space-9); + } + .xl\:bui-py-10 { + padding-top: var(--bui-space-10); + padding-bottom: var(--bui-space-10); + } + .xl\:bui-py-11 { + padding-top: var(--bui-space-11); + padding-bottom: var(--bui-space-11); + } + .xl\:bui-py-12 { + padding-top: var(--bui-space-12); + padding-bottom: var(--bui-space-12); + } + .xl\:bui-py-13 { + padding-top: var(--bui-space-13); + padding-bottom: var(--bui-space-13); + } + .xl\:bui-py-14 { + padding-top: var(--bui-space-14); + padding-bottom: var(--bui-space-14); + } + } + .bui-px { + padding-left: var(--px); + padding-right: var(--px); + } + .bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + .bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + .bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + .bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + .bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + .bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + .bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + .bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + .bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + .bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + .bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + .bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + .bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + .bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + .bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + .bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-px { + padding-left: var(--px-xs); + padding-right: var(--px-xs); + } + .xs\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + .xs\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + .xs\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + .xs\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + .xs\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + .xs\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + .xs\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + .xs\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + .xs\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + .xs\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + .xs\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + .xs\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + .xs\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + .xs\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + .xs\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + .xs\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-px { + padding-left: var(--px-sm); + padding-right: var(--px-sm); + } + .sm\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + .sm\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + .sm\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + .sm\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + .sm\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + .sm\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + .sm\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + .sm\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + .sm\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + .sm\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + .sm\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + .sm\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + .sm\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + .sm\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + .sm\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + .sm\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-px { + padding-left: var(--px-md); + padding-right: var(--px-md); + } + .md\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + .md\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + .md\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + .md\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + .md\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + .md\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + .md\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + .md\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + .md\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + .md\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + .md\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + .md\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + .md\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + .md\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + .md\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + .md\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-px { + padding-left: var(--px-lg); + padding-right: var(--px-lg); + } + .lg\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + .lg\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + .lg\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + .lg\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + .lg\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + .lg\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + .lg\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + .lg\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + .lg\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + .lg\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + .lg\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + .lg\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + .lg\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + .lg\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + .lg\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + .lg\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-px { + padding-left: var(--px-xl); + padding-right: var(--px-xl); + } + .xl\:bui-px-0\.5 { + padding-left: var(--bui-space-0_5); + padding-right: var(--bui-space-0_5); + } + .xl\:bui-px-1 { + padding-left: var(--bui-space-1); + padding-right: var(--bui-space-1); + } + .xl\:bui-px-1\.5 { + padding-left: var(--bui-space-1_5); + padding-right: var(--bui-space-1_5); + } + .xl\:bui-px-2 { + padding-left: var(--bui-space-2); + padding-right: var(--bui-space-2); + } + .xl\:bui-px-3 { + padding-left: var(--bui-space-3); + padding-right: var(--bui-space-3); + } + .xl\:bui-px-4 { + padding-left: var(--bui-space-4); + padding-right: var(--bui-space-4); + } + .xl\:bui-px-5 { + padding-left: var(--bui-space-5); + padding-right: var(--bui-space-5); + } + .xl\:bui-px-6 { + padding-left: var(--bui-space-6); + padding-right: var(--bui-space-6); + } + .xl\:bui-px-7 { + padding-left: var(--bui-space-7); + padding-right: var(--bui-space-7); + } + .xl\:bui-px-8 { + padding-left: var(--bui-space-8); + padding-right: var(--bui-space-8); + } + .xl\:bui-px-9 { + padding-left: var(--bui-space-9); + padding-right: var(--bui-space-9); + } + .xl\:bui-px-10 { + padding-left: var(--bui-space-10); + padding-right: var(--bui-space-10); + } + .xl\:bui-px-11 { + padding-left: var(--bui-space-11); + padding-right: var(--bui-space-11); + } + .xl\:bui-px-12 { + padding-left: var(--bui-space-12); + padding-right: var(--bui-space-12); + } + .xl\:bui-px-13 { + padding-left: var(--bui-space-13); + padding-right: var(--bui-space-13); + } + .xl\:bui-px-14 { + padding-left: var(--bui-space-14); + padding-right: var(--bui-space-14); + } + } + .bui-m { + margin: var(--m); + } + .bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + .bui-m-1 { + margin: var(--bui-space-1); + } + .bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + .bui-m-2 { + margin: var(--bui-space-2); + } + .bui-m-3 { + margin: var(--bui-space-3); + } + .bui-m-4 { + margin: var(--bui-space-4); + } + .bui-m-5 { + margin: var(--bui-space-5); + } + .bui-m-6 { + margin: var(--bui-space-6); + } + .bui-m-7 { + margin: var(--bui-space-7); + } + .bui-m-8 { + margin: var(--bui-space-8); + } + .bui-m-9 { + margin: var(--bui-space-9); + } + .bui-m-10 { + margin: var(--bui-space-10); + } + .bui-m-11 { + margin: var(--bui-space-11); + } + .bui-m-12 { + margin: var(--bui-space-12); + } + .bui-m-13 { + margin: var(--bui-space-13); + } + .bui-m-14 { + margin: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-m { + margin: var(--p-xs); + } + .xs\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + .xs\:bui-m-1 { + margin: var(--bui-space-1); + } + .xs\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + .xs\:bui-m-2 { + margin: var(--bui-space-2); + } + .xs\:bui-m-3 { + margin: var(--bui-space-3); + } + .xs\:bui-m-4 { + margin: var(--bui-space-4); + } + .xs\:bui-m-5 { + margin: var(--bui-space-5); + } + .xs\:bui-m-6 { + margin: var(--bui-space-6); + } + .xs\:bui-m-7 { + margin: var(--bui-space-7); + } + .xs\:bui-m-8 { + margin: var(--bui-space-8); + } + .xs\:bui-m-9 { + margin: var(--bui-space-9); + } + .xs\:bui-m-10 { + margin: var(--bui-space-10); + } + .xs\:bui-m-11 { + margin: var(--bui-space-11); + } + .xs\:bui-m-12 { + margin: var(--bui-space-12); + } + .xs\:bui-m-13 { + margin: var(--bui-space-13); + } + .xs\:bui-m-14 { + margin: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-m { + margin: var(--p-sm); + } + .sm\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + .sm\:bui-m-1 { + margin: var(--bui-space-1); + } + .sm\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + .sm\:bui-m-2 { + margin: var(--bui-space-2); + } + .sm\:bui-m-3 { + margin: var(--bui-space-3); + } + .sm\:bui-m-4 { + margin: var(--bui-space-4); + } + .sm\:bui-m-5 { + margin: var(--bui-space-5); + } + .sm\:bui-m-6 { + margin: var(--bui-space-6); + } + .sm\:bui-m-7 { + margin: var(--bui-space-7); + } + .sm\:bui-m-8 { + margin: var(--bui-space-8); + } + .sm\:bui-m-9 { + margin: var(--bui-space-9); + } + .sm\:bui-m-10 { + margin: var(--bui-space-10); + } + .sm\:bui-m-11 { + margin: var(--bui-space-11); + } + .sm\:bui-m-12 { + margin: var(--bui-space-12); + } + .sm\:bui-m-13 { + margin: var(--bui-space-13); + } + .sm\:bui-m-14 { + margin: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-m { + margin: var(--p-md); + } + .md\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + .md\:bui-m-1 { + margin: var(--bui-space-1); + } + .md\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + .md\:bui-m-2 { + margin: var(--bui-space-2); + } + .md\:bui-m-3 { + margin: var(--bui-space-3); + } + .md\:bui-m-4 { + margin: var(--bui-space-4); + } + .md\:bui-m-5 { + margin: var(--bui-space-5); + } + .md\:bui-m-6 { + margin: var(--bui-space-6); + } + .md\:bui-m-7 { + margin: var(--bui-space-7); + } + .md\:bui-m-8 { + margin: var(--bui-space-8); + } + .md\:bui-m-9 { + margin: var(--bui-space-9); + } + .md\:bui-m-10 { + margin: var(--bui-space-10); + } + .md\:bui-m-11 { + margin: var(--bui-space-11); + } + .md\:bui-m-12 { + margin: var(--bui-space-12); + } + .md\:bui-m-13 { + margin: var(--bui-space-13); + } + .md\:bui-m-14 { + margin: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-m { + margin: var(--p-lg); + } + .lg\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + .lg\:bui-m-1 { + margin: var(--bui-space-1); + } + .lg\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + .lg\:bui-m-2 { + margin: var(--bui-space-2); + } + .lg\:bui-m-3 { + margin: var(--bui-space-3); + } + .lg\:bui-m-4 { + margin: var(--bui-space-4); + } + .lg\:bui-m-5 { + margin: var(--bui-space-5); + } + .lg\:bui-m-6 { + margin: var(--bui-space-6); + } + .lg\:bui-m-7 { + margin: var(--bui-space-7); + } + .lg\:bui-m-8 { + margin: var(--bui-space-8); + } + .lg\:bui-m-9 { + margin: var(--bui-space-9); + } + .lg\:bui-m-10 { + margin: var(--bui-space-10); + } + .lg\:bui-m-11 { + margin: var(--bui-space-11); + } + .lg\:bui-m-12 { + margin: var(--bui-space-12); + } + .lg\:bui-m-13 { + margin: var(--bui-space-13); + } + .lg\:bui-m-14 { + margin: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-m { + margin: var(--p-xl); + } + .xl\:bui-m-0\.5 { + margin: var(--bui-space-0_5); + } + .xl\:bui-m-1 { + margin: var(--bui-space-1); + } + .xl\:bui-m-1\.5 { + margin: var(--bui-space-1_5); + } + .xl\:bui-m-2 { + margin: var(--bui-space-2); + } + .xl\:bui-m-3 { + margin: var(--bui-space-3); + } + .xl\:bui-m-4 { + margin: var(--bui-space-4); + } + .xl\:bui-m-5 { + margin: var(--bui-space-5); + } + .xl\:bui-m-6 { + margin: var(--bui-space-6); + } + .xl\:bui-m-7 { + margin: var(--bui-space-7); + } + .xl\:bui-m-8 { + margin: var(--bui-space-8); + } + .xl\:bui-m-9 { + margin: var(--bui-space-9); + } + .xl\:bui-m-10 { + margin: var(--bui-space-10); + } + .xl\:bui-m-11 { + margin: var(--bui-space-11); + } + .xl\:bui-m-12 { + margin: var(--bui-space-12); + } + .xl\:bui-m-13 { + margin: var(--bui-space-13); + } + .xl\:bui-m-14 { + margin: var(--bui-space-14); + } + } + .bui-ml { + margin-left: var(--ml); + } + .bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + .bui-ml-1 { + margin-left: var(--bui-space-1); + } + .bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + .bui-ml-2 { + margin-left: var(--bui-space-2); + } + .bui-ml-3 { + margin-left: var(--bui-space-3); + } + .bui-ml-4 { + margin-left: var(--bui-space-4); + } + .bui-ml-5 { + margin-left: var(--bui-space-5); + } + .bui-ml-6 { + margin-left: var(--bui-space-6); + } + .bui-ml-7 { + margin-left: var(--bui-space-7); + } + .bui-ml-8 { + margin-left: var(--bui-space-8); + } + .bui-ml-9 { + margin-left: var(--bui-space-9); + } + .bui-ml-10 { + margin-left: var(--bui-space-10); + } + .bui-ml-11 { + margin-left: var(--bui-space-11); + } + .bui-ml-12 { + margin-left: var(--bui-space-12); + } + .bui-ml-13 { + margin-left: var(--bui-space-13); + } + .bui-ml-14 { + margin-left: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-ml { + margin-left: var(--ml-xs); + } + .xs\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + .xs\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + .xs\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + .xs\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + .xs\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + .xs\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + .xs\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + .xs\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + .xs\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + .xs\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + .xs\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + .xs\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + .xs\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + .xs\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + .xs\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + .xs\:bui-ml-14 { + margin-left: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-ml { + margin-left: var(--ml-sm); + } + .sm\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + .sm\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + .sm\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + .sm\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + .sm\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + .sm\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + .sm\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + .sm\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + .sm\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + .sm\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + .sm\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + .sm\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + .sm\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + .sm\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + .sm\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + .sm\:bui-ml-14 { + margin-left: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-ml { + margin-left: var(--ml-md); + } + .md\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + .md\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + .md\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + .md\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + .md\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + .md\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + .md\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + .md\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + .md\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + .md\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + .md\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + .md\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + .md\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + .md\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + .md\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + .md\:bui-ml-14 { + margin-left: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-ml { + margin-left: var(--ml-lg); + } + .lg\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + .lg\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + .lg\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + .lg\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + .lg\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + .lg\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + .lg\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + .lg\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + .lg\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + .lg\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + .lg\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + .lg\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + .lg\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + .lg\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + .lg\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + .lg\:bui-ml-14 { + margin-left: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-ml { + margin-left: var(--ml-xl); + } + .xl\:bui-ml-0\.5 { + margin-left: var(--bui-space-0_5); + } + .xl\:bui-ml-1 { + margin-left: var(--bui-space-1); + } + .xl\:bui-ml-1\.5 { + margin-left: var(--bui-space-1_5); + } + .xl\:bui-ml-2 { + margin-left: var(--bui-space-2); + } + .xl\:bui-ml-3 { + margin-left: var(--bui-space-3); + } + .xl\:bui-ml-4 { + margin-left: var(--bui-space-4); + } + .xl\:bui-ml-5 { + margin-left: var(--bui-space-5); + } + .xl\:bui-ml-6 { + margin-left: var(--bui-space-6); + } + .xl\:bui-ml-7 { + margin-left: var(--bui-space-7); + } + .xl\:bui-ml-8 { + margin-left: var(--bui-space-8); + } + .xl\:bui-ml-9 { + margin-left: var(--bui-space-9); + } + .xl\:bui-ml-10 { + margin-left: var(--bui-space-10); + } + .xl\:bui-ml-11 { + margin-left: var(--bui-space-11); + } + .xl\:bui-ml-12 { + margin-left: var(--bui-space-12); + } + .xl\:bui-ml-13 { + margin-left: var(--bui-space-13); + } + .xl\:bui-ml-14 { + margin-left: var(--bui-space-14); + } + } + .bui-mr { + margin-right: var(--mr); + } + .bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + .bui-mr-1 { + margin-right: var(--bui-space-1); + } + .bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + .bui-mr-2 { + margin-right: var(--bui-space-2); + } + .bui-mr-3 { + margin-right: var(--bui-space-3); + } + .bui-mr-4 { + margin-right: var(--bui-space-4); + } + .bui-mr-5 { + margin-right: var(--bui-space-5); + } + .bui-mr-6 { + margin-right: var(--bui-space-6); + } + .bui-mr-7 { + margin-right: var(--bui-space-7); + } + .bui-mr-8 { + margin-right: var(--bui-space-8); + } + .bui-mr-9 { + margin-right: var(--bui-space-9); + } + .bui-mr-10 { + margin-right: var(--bui-space-10); + } + .bui-mr-11 { + margin-right: var(--bui-space-11); + } + .bui-mr-12 { + margin-right: var(--bui-space-12); + } + .bui-mr-13 { + margin-right: var(--bui-space-13); + } + .bui-mr-14 { + margin-right: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-mr { + margin-right: var(--mr-xs); + } + .xs\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + .xs\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + .xs\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + .xs\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + .xs\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + .xs\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + .xs\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + .xs\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + .xs\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + .xs\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + .xs\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + .xs\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + .xs\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + .xs\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + .xs\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + .xs\:bui-mr-14 { + margin-right: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-mr { + margin-right: var(--mr-sm); + } + .sm\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + .sm\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + .sm\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + .sm\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + .sm\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + .sm\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + .sm\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + .sm\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + .sm\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + .sm\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + .sm\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + .sm\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + .sm\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + .sm\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + .sm\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + .sm\:bui-mr-14 { + margin-right: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-mr { + margin-right: var(--mr-md); + } + .md\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + .md\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + .md\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + .md\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + .md\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + .md\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + .md\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + .md\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + .md\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + .md\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + .md\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + .md\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + .md\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + .md\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + .md\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + .md\:bui-mr-14 { + margin-right: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-mr { + margin-right: var(--mr-lg); + } + .lg\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + .lg\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + .lg\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + .lg\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + .lg\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + .lg\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + .lg\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + .lg\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + .lg\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + .lg\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + .lg\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + .lg\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + .lg\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + .lg\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + .lg\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + .lg\:bui-mr-14 { + margin-right: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-mr { + margin-right: var(--mr-xl); + } + .xl\:bui-mr-0\.5 { + margin-right: var(--bui-space-0_5); + } + .xl\:bui-mr-1 { + margin-right: var(--bui-space-1); + } + .xl\:bui-mr-1\.5 { + margin-right: var(--bui-space-1_5); + } + .xl\:bui-mr-2 { + margin-right: var(--bui-space-2); + } + .xl\:bui-mr-3 { + margin-right: var(--bui-space-3); + } + .xl\:bui-mr-4 { + margin-right: var(--bui-space-4); + } + .xl\:bui-mr-5 { + margin-right: var(--bui-space-5); + } + .xl\:bui-mr-6 { + margin-right: var(--bui-space-6); + } + .xl\:bui-mr-7 { + margin-right: var(--bui-space-7); + } + .xl\:bui-mr-8 { + margin-right: var(--bui-space-8); + } + .xl\:bui-mr-9 { + margin-right: var(--bui-space-9); + } + .xl\:bui-mr-10 { + margin-right: var(--bui-space-10); + } + .xl\:bui-mr-11 { + margin-right: var(--bui-space-11); + } + .xl\:bui-mr-12 { + margin-right: var(--bui-space-12); + } + .xl\:bui-mr-13 { + margin-right: var(--bui-space-13); + } + .xl\:bui-mr-14 { + margin-right: var(--bui-space-14); + } + } + .bui-mt { + margin-top: var(--mt); + } + .bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + .bui-mt-1 { + margin-top: var(--bui-space-1); + } + .bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + .bui-mt-2 { + margin-top: var(--bui-space-2); + } + .bui-mt-3 { + margin-top: var(--bui-space-3); + } + .bui-mt-4 { + margin-top: var(--bui-space-4); + } + .bui-mt-5 { + margin-top: var(--bui-space-5); + } + .bui-mt-6 { + margin-top: var(--bui-space-6); + } + .bui-mt-7 { + margin-top: var(--bui-space-7); + } + .bui-mt-8 { + margin-top: var(--bui-space-8); + } + .bui-mt-9 { + margin-top: var(--bui-space-9); + } + .bui-mt-10 { + margin-top: var(--bui-space-10); + } + .bui-mt-11 { + margin-top: var(--bui-space-11); + } + .bui-mt-12 { + margin-top: var(--bui-space-12); + } + .bui-mt-13 { + margin-top: var(--bui-space-13); + } + .bui-mt-14 { + margin-top: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-mt { + margin-top: var(--mt-xs); + } + .xs\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + .xs\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + .xs\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + .xs\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + .xs\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + .xs\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + .xs\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + .xs\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + .xs\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + .xs\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + .xs\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + .xs\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + .xs\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + .xs\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + .xs\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + .xs\:bui-mt-14 { + margin-top: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-mt { + margin-top: var(--mt-sm); + } + .sm\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + .sm\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + .sm\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + .sm\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + .sm\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + .sm\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + .sm\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + .sm\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + .sm\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + .sm\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + .sm\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + .sm\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + .sm\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + .sm\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + .sm\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + .sm\:bui-mt-14 { + margin-top: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-mt { + margin-top: var(--mt-md); + } + .md\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + .md\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + .md\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + .md\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + .md\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + .md\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + .md\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + .md\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + .md\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + .md\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + .md\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + .md\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + .md\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + .md\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + .md\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + .md\:bui-mt-14 { + margin-top: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-mt { + margin-top: var(--mt-lg); + } + .lg\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + .lg\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + .lg\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + .lg\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + .lg\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + .lg\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + .lg\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + .lg\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + .lg\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + .lg\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + .lg\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + .lg\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + .lg\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + .lg\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + .lg\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + .lg\:bui-mt-14 { + margin-top: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-mt { + margin-top: var(--mt-xl); + } + .xl\:bui-mt-0\.5 { + margin-top: var(--bui-space-0_5); + } + .xl\:bui-mt-1 { + margin-top: var(--bui-space-1); + } + .xl\:bui-mt-1\.5 { + margin-top: var(--bui-space-1_5); + } + .xl\:bui-mt-2 { + margin-top: var(--bui-space-2); + } + .xl\:bui-mt-3 { + margin-top: var(--bui-space-3); + } + .xl\:bui-mt-4 { + margin-top: var(--bui-space-4); + } + .xl\:bui-mt-5 { + margin-top: var(--bui-space-5); + } + .xl\:bui-mt-6 { + margin-top: var(--bui-space-6); + } + .xl\:bui-mt-7 { + margin-top: var(--bui-space-7); + } + .xl\:bui-mt-8 { + margin-top: var(--bui-space-8); + } + .xl\:bui-mt-9 { + margin-top: var(--bui-space-9); + } + .xl\:bui-mt-10 { + margin-top: var(--bui-space-10); + } + .xl\:bui-mt-11 { + margin-top: var(--bui-space-11); + } + .xl\:bui-mt-12 { + margin-top: var(--bui-space-12); + } + .xl\:bui-mt-13 { + margin-top: var(--bui-space-13); + } + .xl\:bui-mt-14 { + margin-top: var(--bui-space-14); + } + } + .bui-mb { + margin-bottom: var(--mb); + } + .bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + .bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + .bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + .bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + .bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + .bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + .bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + .bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + .bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + .bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + .bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + .bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + .bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + .bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + .bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + .bui-mb-14 { + margin-bottom: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-mb { + margin-bottom: var(--mb-xs); + } + .xs\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + .xs\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + .xs\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + .xs\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + .xs\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + .xs\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + .xs\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + .xs\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + .xs\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + .xs\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + .xs\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + .xs\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + .xs\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + .xs\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + .xs\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + .xs\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-mb { + margin-bottom: var(--mb-sm); + } + .sm\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + .sm\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + .sm\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + .sm\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + .sm\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + .sm\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + .sm\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + .sm\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + .sm\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + .sm\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + .sm\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + .sm\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + .sm\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + .sm\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + .sm\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + .sm\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-mb { + margin-bottom: var(--mb-md); + } + .md\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + .md\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + .md\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + .md\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + .md\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + .md\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + .md\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + .md\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + .md\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + .md\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + .md\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + .md\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + .md\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + .md\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + .md\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + .md\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-mb { + margin-bottom: var(--mb-lg); + } + .lg\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + .lg\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + .lg\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + .lg\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + .lg\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + .lg\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + .lg\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + .lg\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + .lg\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + .lg\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + .lg\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + .lg\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + .lg\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + .lg\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + .lg\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + .lg\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-mb { + margin-bottom: var(--mb-xl); + } + .xl\:bui-mb-0\.5 { + margin-bottom: var(--bui-space-0_5); + } + .xl\:bui-mb-1 { + margin-bottom: var(--bui-space-1); + } + .xl\:bui-mb-1\.5 { + margin-bottom: var(--bui-space-1_5); + } + .xl\:bui-mb-2 { + margin-bottom: var(--bui-space-2); + } + .xl\:bui-mb-3 { + margin-bottom: var(--bui-space-3); + } + .xl\:bui-mb-4 { + margin-bottom: var(--bui-space-4); + } + .xl\:bui-mb-5 { + margin-bottom: var(--bui-space-5); + } + .xl\:bui-mb-6 { + margin-bottom: var(--bui-space-6); + } + .xl\:bui-mb-7 { + margin-bottom: var(--bui-space-7); + } + .xl\:bui-mb-8 { + margin-bottom: var(--bui-space-8); + } + .xl\:bui-mb-9 { + margin-bottom: var(--bui-space-9); + } + .xl\:bui-mb-10 { + margin-bottom: var(--bui-space-10); + } + .xl\:bui-mb-11 { + margin-bottom: var(--bui-space-11); + } + .xl\:bui-mb-12 { + margin-bottom: var(--bui-space-12); + } + .xl\:bui-mb-13 { + margin-bottom: var(--bui-space-13); + } + .xl\:bui-mb-14 { + margin-bottom: var(--bui-space-14); + } + } + .bui-my { + margin-top: var(--my); + margin-bottom: var(--my); + } + .bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + .bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + .bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + .bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + .bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + .bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + .bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + .bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + .bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + .bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + .bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + .bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + .bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + .bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + .bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + .bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-my { + margin-top: var(--my-xs); + margin-bottom: var(--my-xs); + } + .xs\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + .xs\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + .xs\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + .xs\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + .xs\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + .xs\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + .xs\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + .xs\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + .xs\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + .xs\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + .xs\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + .xs\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + .xs\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + .xs\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + .xs\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + .xs\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-my { + margin-top: var(--my-sm); + margin-bottom: var(--my-sm); + } + .sm\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + .sm\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + .sm\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + .sm\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + .sm\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + .sm\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + .sm\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + .sm\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + .sm\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + .sm\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + .sm\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + .sm\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + .sm\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + .sm\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + .sm\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + .sm\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-my { + margin-top: var(--my-md); + margin-bottom: var(--my-md); + } + .md\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + .md\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + .md\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + .md\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + .md\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + .md\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + .md\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + .md\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + .md\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + .md\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + .md\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + .md\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + .md\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + .md\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + .md\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + .md\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-my { + margin-top: var(--my-lg); + margin-bottom: var(--my-lg); + } + .lg\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + .lg\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + .lg\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + .lg\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + .lg\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + .lg\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + .lg\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + .lg\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + .lg\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + .lg\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + .lg\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + .lg\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + .lg\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + .lg\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + .lg\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + .lg\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-my { + margin-top: var(--my-xl); + margin-bottom: var(--my-xl); + } + .xl\:bui-my-0\.5 { + margin-top: var(--bui-space-0_5); + margin-bottom: var(--bui-space-0_5); + } + .xl\:bui-my-1 { + margin-top: var(--bui-space-1); + margin-bottom: var(--bui-space-1); + } + .xl\:bui-my-1\.5 { + margin-top: var(--bui-space-1_5); + margin-bottom: var(--bui-space-1_5); + } + .xl\:bui-my-2 { + margin-top: var(--bui-space-2); + margin-bottom: var(--bui-space-2); + } + .xl\:bui-my-3 { + margin-top: var(--bui-space-3); + margin-bottom: var(--bui-space-3); + } + .xl\:bui-my-4 { + margin-top: var(--bui-space-4); + margin-bottom: var(--bui-space-4); + } + .xl\:bui-my-5 { + margin-top: var(--bui-space-5); + margin-bottom: var(--bui-space-5); + } + .xl\:bui-my-6 { + margin-top: var(--bui-space-6); + margin-bottom: var(--bui-space-6); + } + .xl\:bui-my-7 { + margin-top: var(--bui-space-7); + margin-bottom: var(--bui-space-7); + } + .xl\:bui-my-8 { + margin-top: var(--bui-space-8); + margin-bottom: var(--bui-space-8); + } + .xl\:bui-my-9 { + margin-top: var(--bui-space-9); + margin-bottom: var(--bui-space-9); + } + .xl\:bui-my-10 { + margin-top: var(--bui-space-10); + margin-bottom: var(--bui-space-10); + } + .xl\:bui-my-11 { + margin-top: var(--bui-space-11); + margin-bottom: var(--bui-space-11); + } + .xl\:bui-my-12 { + margin-top: var(--bui-space-12); + margin-bottom: var(--bui-space-12); + } + .xl\:bui-my-13 { + margin-top: var(--bui-space-13); + margin-bottom: var(--bui-space-13); + } + .xl\:bui-my-14 { + margin-top: var(--bui-space-14); + margin-bottom: var(--bui-space-14); + } + } + .bui-mx { + margin-left: var(--mx); + margin-right: var(--mx); + } + .bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + .bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + .bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + .bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + .bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + .bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + .bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + .bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + .bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + .bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + .bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + .bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + .bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + .bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + .bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + .bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-mx { + margin-left: var(--mx-xs); + margin-right: var(--mx-xs); + } + .xs\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + .xs\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + .xs\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + .xs\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + .xs\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + .xs\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + .xs\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + .xs\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + .xs\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + .xs\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + .xs\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + .xs\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + .xs\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + .xs\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + .xs\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + .xs\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-mx { + margin-left: var(--mx-sm); + margin-right: var(--mx-sm); + } + .sm\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + .sm\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + .sm\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + .sm\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + .sm\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + .sm\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + .sm\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + .sm\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + .sm\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + .sm\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + .sm\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + .sm\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + .sm\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + .sm\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + .sm\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + .sm\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-mx { + margin-left: var(--mx-md); + margin-right: var(--mx-md); + } + .md\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + .md\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + .md\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + .md\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + .md\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + .md\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + .md\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + .md\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + .md\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + .md\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + .md\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + .md\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + .md\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + .md\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + .md\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + .md\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-mx { + margin-left: var(--mx-lg); + margin-right: var(--mx-lg); + } + .lg\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + .lg\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + .lg\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + .lg\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + .lg\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + .lg\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + .lg\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + .lg\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + .lg\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + .lg\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + .lg\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + .lg\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + .lg\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + .lg\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + .lg\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + .lg\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-mx { + margin-left: var(--mx-xl); + margin-right: var(--mx-xl); + } + .xl\:bui-mx-0\.5 { + margin-left: var(--bui-space-0_5); + margin-right: var(--bui-space-0_5); + } + .xl\:bui-mx-1 { + margin-left: var(--bui-space-1); + margin-right: var(--bui-space-1); + } + .xl\:bui-mx-1\.5 { + margin-left: var(--bui-space-1_5); + margin-right: var(--bui-space-1_5); + } + .xl\:bui-mx-2 { + margin-left: var(--bui-space-2); + margin-right: var(--bui-space-2); + } + .xl\:bui-mx-3 { + margin-left: var(--bui-space-3); + margin-right: var(--bui-space-3); + } + .xl\:bui-mx-4 { + margin-left: var(--bui-space-4); + margin-right: var(--bui-space-4); + } + .xl\:bui-mx-5 { + margin-left: var(--bui-space-5); + margin-right: var(--bui-space-5); + } + .xl\:bui-mx-6 { + margin-left: var(--bui-space-6); + margin-right: var(--bui-space-6); + } + .xl\:bui-mx-7 { + margin-left: var(--bui-space-7); + margin-right: var(--bui-space-7); + } + .xl\:bui-mx-8 { + margin-left: var(--bui-space-8); + margin-right: var(--bui-space-8); + } + .xl\:bui-mx-9 { + margin-left: var(--bui-space-9); + margin-right: var(--bui-space-9); + } + .xl\:bui-mx-10 { + margin-left: var(--bui-space-10); + margin-right: var(--bui-space-10); + } + .xl\:bui-mx-11 { + margin-left: var(--bui-space-11); + margin-right: var(--bui-space-11); + } + .xl\:bui-mx-12 { + margin-left: var(--bui-space-12); + margin-right: var(--bui-space-12); + } + .xl\:bui-mx-13 { + margin-left: var(--bui-space-13); + margin-right: var(--bui-space-13); + } + .xl\:bui-mx-14 { + margin-left: var(--bui-space-14); + margin-right: var(--bui-space-14); + } + } + .bui-display-none { + display: none; + } + .bui-display-inline { + display: inline; + } + .bui-display-inline-block { + display: inline-block; + } + .bui-display-block { + display: block; + } + @media (width>=640px) { + .xs\:bui-display-none { + display: none; + } + .xs\:bui-display-inline { + display: inline; + } + .xs\:bui-display-inline-block { + display: inline-block; + } + .xs\:bui-display-block { + display: block; + } + } + @media (width>=768px) { + .sm\:bui-display-none { + display: none; + } + .sm\:bui-display-inline { + display: inline; + } + .sm\:bui-display-inline-block { + display: inline-block; + } + .sm\:bui-display-block { + display: block; + } + } + @media (width>=1024px) { + .md\:bui-display-none { + display: none; + } + .md\:bui-display-inline { + display: inline; + } + .md\:bui-display-inline-block { + display: inline-block; + } + .md\:bui-display-block { + display: block; + } + } + @media (width>=1280px) { + .lg\:bui-display-none { + display: none; + } + .lg\:bui-display-inline { + display: inline; + } + .lg\:bui-display-inline-block { + display: inline-block; + } + .lg\:bui-display-block { + display: block; + } + } + @media (width>=1536px) { + .xl\:bui-display-none { + display: none; + } + .xl\:bui-display-inline { + display: inline; + } + .xl\:bui-display-inline-block { + display: inline-block; + } + .xl\:bui-display-block { + display: block; + } + } + .bui-w { + width: var(--width); + } + .bui-min-w { + min-width: var(--min-width); + } + .bui-max-w { + max-width: var(--max-width); + } + @media (width>=640px) { + .xs\:bui-w { + width: var(--width); + } + .xs\:bui-min-w { + min-width: var(--min-width); + } + .xs\:bui-max-w { + max-width: var(--max-width); + } + } + @media (width>=768px) { + .sm\:bui-w { + width: var(--width); + } + .sm\:bui-min-w { + min-width: var(--min-width); + } + .sm\:bui-max-w { + max-width: var(--max-width); + } + } + @media (width>=1024px) { + .md\:bui-w { + width: var(--width); + } + .md\:bui-min-w { + min-width: var(--min-width); + } + .md\:bui-max-w { + max-width: var(--max-width); + } + } + @media (width>=1280px) { + .lg\:bui-w { + width: var(--width); + } + .lg\:bui-min-w { + min-width: var(--min-width); + } + .lg\:bui-max-w { + max-width: var(--max-width); + } + } + @media (width>=1536px) { + .xl\:bui-w { + width: var(--width); + } + .xl\:bui-min-w { + min-width: var(--min-width); + } + .xl\:bui-max-w { + max-width: var(--max-width); + } + } + .bui-h { + height: var(--height); + } + .bui-min-h { + min-height: var(--min-height); + } + .bui-max-h { + max-height: var(--max-height); + } + @media (width>=640px) { + .xs\:bui-h { + height: var(--height); + } + .xs\:bui-min-h { + min-height: var(--min-height); + } + .xs\:bui-max-h { + max-height: var(--max-height); + } + } + @media (width>=768px) { + .sm\:bui-h { + height: var(--height); + } + .sm\:bui-min-h { + min-height: var(--min-height); + } + .sm\:bui-max-h { + max-height: var(--max-height); + } + } + @media (width>=1024px) { + .md\:bui-h { + height: var(--height); + } + .md\:bui-min-h { + min-height: var(--min-height); + } + .md\:bui-max-h { + max-height: var(--max-height); + } + } + @media (width>=1280px) { + .lg\:bui-h { + height: var(--height); + } + .lg\:bui-min-h { + min-height: var(--min-height); + } + .lg\:bui-max-h { + max-height: var(--max-height); + } + } + @media (width>=1536px) { + .xl\:bui-h { + height: var(--height); + } + .xl\:bui-min-h { + min-height: var(--min-height); + } + .xl\:bui-max-h { + max-height: var(--max-height); + } + } + .bui-position-absolute { + position: absolute; + } + .bui-position-fixed { + position: fixed; + } + .bui-position-sticky { + position: sticky; + } + .bui-position-relative { + position: relative; + } + .bui-position-static { + position: static; + } + @media (width>=640px) { + .xs\:bui-position-absolute { + position: absolute; + } + .xs\:bui-position-fixed { + position: fixed; + } + .xs\:bui-position-sticky { + position: sticky; + } + .xs\:bui-position-relative { + position: relative; + } + .xs\:bui-position-static { + position: static; + } + } + @media (width>=768px) { + .sm\:bui-position-absolute { + position: absolute; + } + .sm\:bui-position-fixed { + position: fixed; + } + .sm\:bui-position-sticky { + position: sticky; + } + .sm\:bui-position-relative { + position: relative; + } + .sm\:bui-position-static { + position: static; + } + } + @media (width>=1024px) { + .md\:bui-position-absolute { + position: absolute; + } + .md\:bui-position-fixed { + position: fixed; + } + .md\:bui-position-sticky { + position: sticky; + } + .md\:bui-position-relative { + position: relative; + } + .md\:bui-position-static { + position: static; + } + } + @media (width>=1280px) { + .lg\:bui-position-absolute { + position: absolute; + } + .lg\:bui-position-fixed { + position: fixed; + } + .lg\:bui-position-sticky { + position: sticky; + } + .lg\:bui-position-relative { + position: relative; + } + .lg\:bui-position-static { + position: static; + } + } + @media (width>=1536px) { + .xl\:bui-position-absolute { + position: absolute; + } + .xl\:bui-position-fixed { + position: fixed; + } + .xl\:bui-position-sticky { + position: sticky; + } + .xl\:bui-position-relative { + position: relative; + } + .xl\:bui-position-static { + position: static; + } + } + .bui-columns-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .bui-columns-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .bui-columns-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .bui-columns-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .bui-columns-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .bui-columns-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .bui-columns-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + .bui-columns-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + .bui-columns-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + .bui-columns-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + .bui-columns-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + .bui-columns-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .bui-columns-auto { + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + } + .bui-col-span-1 { + grid-column: span 1 / span 1; + } + .bui-col-span-2 { + grid-column: span 2 / span 2; + } + .bui-col-span-3 { + grid-column: span 3 / span 3; + } + .bui-col-span-4 { + grid-column: span 4 / span 4; + } + .bui-col-span-5 { + grid-column: span 5 / span 5; + } + .bui-col-span-6 { + grid-column: span 6 / span 6; + } + .bui-col-span-7 { + grid-column: span 7 / span 7; + } + .bui-col-span-8 { + grid-column: span 8 / span 8; + } + .bui-col-span-9 { + grid-column: span 9 / span 9; + } + .bui-col-span-10 { + grid-column: span 10 / span 10; + } + .bui-col-span-11 { + grid-column: span 11 / span 11; + } + .bui-col-span-12 { + grid-column: span 12 / span 12; + } + .bui-col-span-auto { + grid-column: span auto/span auto; + } + .bui-col-start-1 { + grid-column-start: 1; + } + .bui-col-start-2 { + grid-column-start: 2; + } + .bui-col-start-3 { + grid-column-start: 3; + } + .bui-col-start-4 { + grid-column-start: 4; + } + .bui-col-start-5 { + grid-column-start: 5; + } + .bui-col-start-6 { + grid-column-start: 6; + } + .bui-col-start-7 { + grid-column-start: 7; + } + .bui-col-start-8 { + grid-column-start: 8; + } + .bui-col-start-9 { + grid-column-start: 9; + } + .bui-col-start-10 { + grid-column-start: 10; + } + .bui-col-start-11 { + grid-column-start: 11; + } + .bui-col-start-12 { + grid-column-start: 12; + } + .bui-col-start-13 { + grid-column-start: 13; + } + .bui-col-start-auto { + grid-column-start: auto; + } + .bui-col-end-1 { + grid-column-end: 1; + } + .bui-col-end-2 { + grid-column-end: 2; + } + .bui-col-end-3 { + grid-column-end: 3; + } + .bui-col-end-4 { + grid-column-end: 4; + } + .bui-col-end-5 { + grid-column-end: 5; + } + .bui-col-end-6 { + grid-column-end: 6; + } + .bui-col-end-7 { + grid-column-end: 7; + } + .bui-col-end-8 { + grid-column-end: 8; + } + .bui-col-end-9 { + grid-column-end: 9; + } + .bui-col-end-10 { + grid-column-end: 10; + } + .bui-col-end-11 { + grid-column-end: 11; + } + .bui-col-end-12 { + grid-column-end: 12; + } + .bui-col-end-13 { + grid-column-end: 13; + } + .bui-col-end-auto { + grid-column-end: auto; + } + .bui-row-span-1 { + grid-row: span 1 / span 1; + } + .bui-row-span-2 { + grid-row: span 2 / span 2; + } + .bui-row-span-3 { + grid-row: span 3 / span 3; + } + .bui-row-span-4 { + grid-row: span 4 / span 4; + } + .bui-row-span-5 { + grid-row: span 5 / span 5; + } + .bui-row-span-6 { + grid-row: span 6 / span 6; + } + .bui-row-span-7 { + grid-row: span 7 / span 7; + } + .bui-row-span-8 { + grid-row: span 8 / span 8; + } + .bui-row-span-9 { + grid-row: span 9 / span 9; + } + .bui-row-span-10 { + grid-row: span 10 / span 10; + } + .bui-row-span-11 { + grid-row: span 11 / span 11; + } + .bui-row-span-12 { + grid-row: span 12 / span 12; + } + .bui-row-span-auto { + grid-row: span auto/span auto; + } + @media (width>=640px) { + .xs\:bui-columns-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .xs\:bui-columns-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .xs\:bui-columns-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .xs\:bui-columns-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .xs\:bui-columns-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .xs\:bui-columns-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .xs\:bui-columns-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + .xs\:bui-columns-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + .xs\:bui-columns-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + .xs\:bui-columns-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + .xs\:bui-columns-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + .xs\:bui-columns-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .xs\:bui-columns-auto { + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + } + .xs\:bui-col-span-1 { + grid-column: span 1 / span 1; + } + .xs\:bui-col-span-2 { + grid-column: span 2 / span 2; + } + .xs\:bui-col-span-3 { + grid-column: span 3 / span 3; + } + .xs\:bui-col-span-4 { + grid-column: span 4 / span 4; + } + .xs\:bui-col-span-5 { + grid-column: span 5 / span 5; + } + .xs\:bui-col-span-6 { + grid-column: span 6 / span 6; + } + .xs\:bui-col-span-7 { + grid-column: span 7 / span 7; + } + .xs\:bui-col-span-8 { + grid-column: span 8 / span 8; + } + .xs\:bui-col-span-9 { + grid-column: span 9 / span 9; + } + .xs\:bui-col-span-10 { + grid-column: span 10 / span 10; + } + .xs\:bui-col-span-11 { + grid-column: span 11 / span 11; + } + .xs\:bui-col-span-12 { + grid-column: span 12 / span 12; + } + .xs\:bui-col-span-auto { + grid-column: span auto/span auto; + } + .xs\:bui-col-start-1 { + grid-column-start: 1; + } + .xs\:bui-col-start-2 { + grid-column-start: 2; + } + .xs\:bui-col-start-3 { + grid-column-start: 3; + } + .xs\:bui-col-start-4 { + grid-column-start: 4; + } + .xs\:bui-col-start-5 { + grid-column-start: 5; + } + .xs\:bui-col-start-6 { + grid-column-start: 6; + } + .xs\:bui-col-start-7 { + grid-column-start: 7; + } + .xs\:bui-col-start-8 { + grid-column-start: 8; + } + .xs\:bui-col-start-9 { + grid-column-start: 9; + } + .xs\:bui-col-start-10 { + grid-column-start: 10; + } + .xs\:bui-col-start-11 { + grid-column-start: 11; + } + .xs\:bui-col-start-12 { + grid-column-start: 12; + } + .xs\:bui-col-start-13 { + grid-column-start: 13; + } + .xs\:bui-col-start-auto { + grid-column-start: auto; + } + .xs\:bui-col-end-1 { + grid-column-end: 1; + } + .xs\:bui-col-end-2 { + grid-column-end: 2; + } + .xs\:bui-col-end-3 { + grid-column-end: 3; + } + .xs\:bui-col-end-4 { + grid-column-end: 4; + } + .xs\:bui-col-end-5 { + grid-column-end: 5; + } + .xs\:bui-col-end-6 { + grid-column-end: 6; + } + .xs\:bui-col-end-7 { + grid-column-end: 7; + } + .xs\:bui-col-end-8 { + grid-column-end: 8; + } + .xs\:bui-col-end-9 { + grid-column-end: 9; + } + .xs\:bui-col-end-10 { + grid-column-end: 10; + } + .xs\:bui-col-end-11 { + grid-column-end: 11; + } + .xs\:bui-col-end-12 { + grid-column-end: 12; + } + .xs\:bui-col-end-13 { + grid-column-end: 13; + } + .xs\:bui-col-end-auto { + grid-column-end: auto; + } + .xs\:bui-row-span-1 { + grid-row: span 1 / span 1; + } + .xs\:bui-row-span-2 { + grid-row: span 2 / span 2; + } + .xs\:bui-row-span-3 { + grid-row: span 3 / span 3; + } + .xs\:bui-row-span-4 { + grid-row: span 4 / span 4; + } + .xs\:bui-row-span-5 { + grid-row: span 5 / span 5; + } + .xs\:bui-row-span-6 { + grid-row: span 6 / span 6; + } + .xs\:bui-row-span-7 { + grid-row: span 7 / span 7; + } + .xs\:bui-row-span-8 { + grid-row: span 8 / span 8; + } + .xs\:bui-row-span-9 { + grid-row: span 9 / span 9; + } + .xs\:bui-row-span-10 { + grid-row: span 10 / span 10; + } + .xs\:bui-row-span-11 { + grid-row: span 11 / span 11; + } + .xs\:bui-row-span-12 { + grid-row: span 12 / span 12; + } + .xs\:bui-row-span-auto { + grid-row: span auto/span auto; + } + } + @media (width>=768px) { + .sm\:bui-columns-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .sm\:bui-columns-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .sm\:bui-columns-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .sm\:bui-columns-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .sm\:bui-columns-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .sm\:bui-columns-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .sm\:bui-columns-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + .sm\:bui-columns-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + .sm\:bui-columns-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + .sm\:bui-columns-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + .sm\:bui-columns-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + .sm\:bui-columns-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .sm\:bui-columns-auto { + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + } + .sm\:bui-col-span-1 { + grid-column: span 1 / span 1; + } + .sm\:bui-col-span-2 { + grid-column: span 2 / span 2; + } + .sm\:bui-col-span-3 { + grid-column: span 3 / span 3; + } + .sm\:bui-col-span-4 { + grid-column: span 4 / span 4; + } + .sm\:bui-col-span-5 { + grid-column: span 5 / span 5; + } + .sm\:bui-col-span-6 { + grid-column: span 6 / span 6; + } + .sm\:bui-col-span-7 { + grid-column: span 7 / span 7; + } + .sm\:bui-col-span-8 { + grid-column: span 8 / span 8; + } + .sm\:bui-col-span-9 { + grid-column: span 9 / span 9; + } + .sm\:bui-col-span-10 { + grid-column: span 10 / span 10; + } + .sm\:bui-col-span-11 { + grid-column: span 11 / span 11; + } + .sm\:bui-col-span-12 { + grid-column: span 12 / span 12; + } + .sm\:bui-col-span-auto { + grid-column: span auto/span auto; + } + .sm\:bui-col-start-1 { + grid-column-start: 1; + } + .sm\:bui-col-start-2 { + grid-column-start: 2; + } + .sm\:bui-col-start-3 { + grid-column-start: 3; + } + .sm\:bui-col-start-4 { + grid-column-start: 4; + } + .sm\:bui-col-start-5 { + grid-column-start: 5; + } + .sm\:bui-col-start-6 { + grid-column-start: 6; + } + .sm\:bui-col-start-7 { + grid-column-start: 7; + } + .sm\:bui-col-start-8 { + grid-column-start: 8; + } + .sm\:bui-col-start-9 { + grid-column-start: 9; + } + .sm\:bui-col-start-10 { + grid-column-start: 10; + } + .sm\:bui-col-start-11 { + grid-column-start: 11; + } + .sm\:bui-col-start-12 { + grid-column-start: 12; + } + .sm\:bui-col-start-13 { + grid-column-start: 13; + } + .sm\:bui-col-start-auto { + grid-column-start: auto; + } + .sm\:bui-col-end-1 { + grid-column-end: 1; + } + .sm\:bui-col-end-2 { + grid-column-end: 2; + } + .sm\:bui-col-end-3 { + grid-column-end: 3; + } + .sm\:bui-col-end-4 { + grid-column-end: 4; + } + .sm\:bui-col-end-5 { + grid-column-end: 5; + } + .sm\:bui-col-end-6 { + grid-column-end: 6; + } + .sm\:bui-col-end-7 { + grid-column-end: 7; + } + .sm\:bui-col-end-8 { + grid-column-end: 8; + } + .sm\:bui-col-end-9 { + grid-column-end: 9; + } + .sm\:bui-col-end-10 { + grid-column-end: 10; + } + .sm\:bui-col-end-11 { + grid-column-end: 11; + } + .sm\:bui-col-end-12 { + grid-column-end: 12; + } + .sm\:bui-col-end-13 { + grid-column-end: 13; + } + .sm\:bui-col-end-auto { + grid-column-end: auto; + } + .sm\:bui-row-span-1 { + grid-row: span 1 / span 1; + } + .sm\:bui-row-span-2 { + grid-row: span 2 / span 2; + } + .sm\:bui-row-span-3 { + grid-row: span 3 / span 3; + } + .sm\:bui-row-span-4 { + grid-row: span 4 / span 4; + } + .sm\:bui-row-span-5 { + grid-row: span 5 / span 5; + } + .sm\:bui-row-span-6 { + grid-row: span 6 / span 6; + } + .sm\:bui-row-span-7 { + grid-row: span 7 / span 7; + } + .sm\:bui-row-span-8 { + grid-row: span 8 / span 8; + } + .sm\:bui-row-span-9 { + grid-row: span 9 / span 9; + } + .sm\:bui-row-span-10 { + grid-row: span 10 / span 10; + } + .sm\:bui-row-span-11 { + grid-row: span 11 / span 11; + } + .sm\:bui-row-span-12 { + grid-row: span 12 / span 12; + } + .sm\:bui-row-span-auto { + grid-row: span auto/span auto; + } + } + @media (width>=1024px) { + .md\:bui-columns-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .md\:bui-columns-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .md\:bui-columns-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .md\:bui-columns-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .md\:bui-columns-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .md\:bui-columns-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .md\:bui-columns-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + .md\:bui-columns-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + .md\:bui-columns-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + .md\:bui-columns-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + .md\:bui-columns-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + .md\:bui-columns-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .md\:bui-columns-auto { + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + } + .md\:bui-col-span-1 { + grid-column: span 1 / span 1; + } + .md\:bui-col-span-2 { + grid-column: span 2 / span 2; + } + .md\:bui-col-span-3 { + grid-column: span 3 / span 3; + } + .md\:bui-col-span-4 { + grid-column: span 4 / span 4; + } + .md\:bui-col-span-5 { + grid-column: span 5 / span 5; + } + .md\:bui-col-span-6 { + grid-column: span 6 / span 6; + } + .md\:bui-col-span-7 { + grid-column: span 7 / span 7; + } + .md\:bui-col-span-8 { + grid-column: span 8 / span 8; + } + .md\:bui-col-span-9 { + grid-column: span 9 / span 9; + } + .md\:bui-col-span-10 { + grid-column: span 10 / span 10; + } + .md\:bui-col-span-11 { + grid-column: span 11 / span 11; + } + .md\:bui-col-span-12 { + grid-column: span 12 / span 12; + } + .md\:bui-col-span-auto { + grid-column: span auto/span auto; + } + .md\:bui-col-start-1 { + grid-column-start: 1; + } + .md\:bui-col-start-2 { + grid-column-start: 2; + } + .md\:bui-col-start-3 { + grid-column-start: 3; + } + .md\:bui-col-start-4 { + grid-column-start: 4; + } + .md\:bui-col-start-5 { + grid-column-start: 5; + } + .md\:bui-col-start-6 { + grid-column-start: 6; + } + .md\:bui-col-start-7 { + grid-column-start: 7; + } + .md\:bui-col-start-8 { + grid-column-start: 8; + } + .md\:bui-col-start-9 { + grid-column-start: 9; + } + .md\:bui-col-start-10 { + grid-column-start: 10; + } + .md\:bui-col-start-11 { + grid-column-start: 11; + } + .md\:bui-col-start-12 { + grid-column-start: 12; + } + .md\:bui-col-start-13 { + grid-column-start: 13; + } + .md\:bui-col-start-auto { + grid-column-start: auto; + } + .md\:bui-col-end-1 { + grid-column-end: 1; + } + .md\:bui-col-end-2 { + grid-column-end: 2; + } + .md\:bui-col-end-3 { + grid-column-end: 3; + } + .md\:bui-col-end-4 { + grid-column-end: 4; + } + .md\:bui-col-end-5 { + grid-column-end: 5; + } + .md\:bui-col-end-6 { + grid-column-end: 6; + } + .md\:bui-col-end-7 { + grid-column-end: 7; + } + .md\:bui-col-end-8 { + grid-column-end: 8; + } + .md\:bui-col-end-9 { + grid-column-end: 9; + } + .md\:bui-col-end-10 { + grid-column-end: 10; + } + .md\:bui-col-end-11 { + grid-column-end: 11; + } + .md\:bui-col-end-12 { + grid-column-end: 12; + } + .md\:bui-col-end-13 { + grid-column-end: 13; + } + .md\:bui-col-end-auto { + grid-column-end: auto; + } + .md\:bui-row-span-1 { + grid-row: span 1 / span 1; + } + .md\:bui-row-span-2 { + grid-row: span 2 / span 2; + } + .md\:bui-row-span-3 { + grid-row: span 3 / span 3; + } + .md\:bui-row-span-4 { + grid-row: span 4 / span 4; + } + .md\:bui-row-span-5 { + grid-row: span 5 / span 5; + } + .md\:bui-row-span-6 { + grid-row: span 6 / span 6; + } + .md\:bui-row-span-7 { + grid-row: span 7 / span 7; + } + .md\:bui-row-span-8 { + grid-row: span 8 / span 8; + } + .md\:bui-row-span-9 { + grid-row: span 9 / span 9; + } + .md\:bui-row-span-10 { + grid-row: span 10 / span 10; + } + .md\:bui-row-span-11 { + grid-row: span 11 / span 11; + } + .md\:bui-row-span-12 { + grid-row: span 12 / span 12; + } + .md\:bui-row-span-auto { + grid-row: span auto/span auto; + } + } + @media (width>=1280px) { + .lg\:bui-columns-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .lg\:bui-columns-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .lg\:bui-columns-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .lg\:bui-columns-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .lg\:bui-columns-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .lg\:bui-columns-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .lg\:bui-columns-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + .lg\:bui-columns-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + .lg\:bui-columns-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + .lg\:bui-columns-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + .lg\:bui-columns-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + .lg\:bui-columns-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .lg\:bui-columns-auto { + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + } + .lg\:bui-col-span-1 { + grid-column: span 1 / span 1; + } + .lg\:bui-col-span-2 { + grid-column: span 2 / span 2; + } + .lg\:bui-col-span-3 { + grid-column: span 3 / span 3; + } + .lg\:bui-col-span-4 { + grid-column: span 4 / span 4; + } + .lg\:bui-col-span-5 { + grid-column: span 5 / span 5; + } + .lg\:bui-col-span-6 { + grid-column: span 6 / span 6; + } + .lg\:bui-col-span-7 { + grid-column: span 7 / span 7; + } + .lg\:bui-col-span-8 { + grid-column: span 8 / span 8; + } + .lg\:bui-col-span-9 { + grid-column: span 9 / span 9; + } + .lg\:bui-col-span-10 { + grid-column: span 10 / span 10; + } + .lg\:bui-col-span-11 { + grid-column: span 11 / span 11; + } + .lg\:bui-col-span-12 { + grid-column: span 12 / span 12; + } + .lg\:bui-col-span-auto { + grid-column: span auto/span auto; + } + .lg\:bui-col-start-1 { + grid-column-start: 1; + } + .lg\:bui-col-start-2 { + grid-column-start: 2; + } + .lg\:bui-col-start-3 { + grid-column-start: 3; + } + .lg\:bui-col-start-4 { + grid-column-start: 4; + } + .lg\:bui-col-start-5 { + grid-column-start: 5; + } + .lg\:bui-col-start-6 { + grid-column-start: 6; + } + .lg\:bui-col-start-7 { + grid-column-start: 7; + } + .lg\:bui-col-start-8 { + grid-column-start: 8; + } + .lg\:bui-col-start-9 { + grid-column-start: 9; + } + .lg\:bui-col-start-10 { + grid-column-start: 10; + } + .lg\:bui-col-start-11 { + grid-column-start: 11; + } + .lg\:bui-col-start-12 { + grid-column-start: 12; + } + .lg\:bui-col-start-13 { + grid-column-start: 13; + } + .lg\:bui-col-start-auto { + grid-column-start: auto; + } + .lg\:bui-col-end-1 { + grid-column-end: 1; + } + .lg\:bui-col-end-2 { + grid-column-end: 2; + } + .lg\:bui-col-end-3 { + grid-column-end: 3; + } + .lg\:bui-col-end-4 { + grid-column-end: 4; + } + .lg\:bui-col-end-5 { + grid-column-end: 5; + } + .lg\:bui-col-end-6 { + grid-column-end: 6; + } + .lg\:bui-col-end-7 { + grid-column-end: 7; + } + .lg\:bui-col-end-8 { + grid-column-end: 8; + } + .lg\:bui-col-end-9 { + grid-column-end: 9; + } + .lg\:bui-col-end-10 { + grid-column-end: 10; + } + .lg\:bui-col-end-11 { + grid-column-end: 11; + } + .lg\:bui-col-end-12 { + grid-column-end: 12; + } + .lg\:bui-col-end-13 { + grid-column-end: 13; + } + .lg\:bui-col-end-auto { + grid-column-end: auto; + } + .lg\:bui-row-span-1 { + grid-row: span 1 / span 1; + } + .lg\:bui-row-span-2 { + grid-row: span 2 / span 2; + } + .lg\:bui-row-span-3 { + grid-row: span 3 / span 3; + } + .lg\:bui-row-span-4 { + grid-row: span 4 / span 4; + } + .lg\:bui-row-span-5 { + grid-row: span 5 / span 5; + } + .lg\:bui-row-span-6 { + grid-row: span 6 / span 6; + } + .lg\:bui-row-span-7 { + grid-row: span 7 / span 7; + } + .lg\:bui-row-span-8 { + grid-row: span 8 / span 8; + } + .lg\:bui-row-span-9 { + grid-row: span 9 / span 9; + } + .lg\:bui-row-span-10 { + grid-row: span 10 / span 10; + } + .lg\:bui-row-span-11 { + grid-row: span 11 / span 11; + } + .lg\:bui-row-span-12 { + grid-row: span 12 / span 12; + } + .lg\:bui-row-span-auto { + grid-row: span auto/span auto; + } + } + @media (width>=1536px) { + .xl\:bui-columns-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + .xl\:bui-columns-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .xl\:bui-columns-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .xl\:bui-columns-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + .xl\:bui-columns-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .xl\:bui-columns-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + .xl\:bui-columns-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); + } + .xl\:bui-columns-8 { + grid-template-columns: repeat(8, minmax(0, 1fr)); + } + .xl\:bui-columns-9 { + grid-template-columns: repeat(9, minmax(0, 1fr)); + } + .xl\:bui-columns-10 { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + .xl\:bui-columns-11 { + grid-template-columns: repeat(11, minmax(0, 1fr)); + } + .xl\:bui-columns-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .xl\:bui-columns-auto { + grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); + } + .xl\:bui-col-span-1 { + grid-column: span 1 / span 1; + } + .xl\:bui-col-span-2 { + grid-column: span 2 / span 2; + } + .xl\:bui-col-span-3 { + grid-column: span 3 / span 3; + } + .xl\:bui-col-span-4 { + grid-column: span 4 / span 4; + } + .xl\:bui-col-span-5 { + grid-column: span 5 / span 5; + } + .xl\:bui-col-span-6 { + grid-column: span 6 / span 6; + } + .xl\:bui-col-span-7 { + grid-column: span 7 / span 7; + } + .xl\:bui-col-span-8 { + grid-column: span 8 / span 8; + } + .xl\:bui-col-span-9 { + grid-column: span 9 / span 9; + } + .xl\:bui-col-span-10 { + grid-column: span 10 / span 10; + } + .xl\:bui-col-span-11 { + grid-column: span 11 / span 11; + } + .xl\:bui-col-span-12 { + grid-column: span 12 / span 12; + } + .xl\:bui-col-span-auto { + grid-column: span auto/span auto; + } + .xl\:bui-col-start-1 { + grid-column-start: 1; + } + .xl\:bui-col-start-2 { + grid-column-start: 2; + } + .xl\:bui-col-start-3 { + grid-column-start: 3; + } + .xl\:bui-col-start-4 { + grid-column-start: 4; + } + .xl\:bui-col-start-5 { + grid-column-start: 5; + } + .xl\:bui-col-start-6 { + grid-column-start: 6; + } + .xl\:bui-col-start-7 { + grid-column-start: 7; + } + .xl\:bui-col-start-8 { + grid-column-start: 8; + } + .xl\:bui-col-start-9 { + grid-column-start: 9; + } + .xl\:bui-col-start-10 { + grid-column-start: 10; + } + .xl\:bui-col-start-11 { + grid-column-start: 11; + } + .xl\:bui-col-start-12 { + grid-column-start: 12; + } + .xl\:bui-col-start-13 { + grid-column-start: 13; + } + .xl\:bui-col-start-auto { + grid-column-start: auto; + } + .xl\:bui-col-end-1 { + grid-column-end: 1; + } + .xl\:bui-col-end-2 { + grid-column-end: 2; + } + .xl\:bui-col-end-3 { + grid-column-end: 3; + } + .xl\:bui-col-end-4 { + grid-column-end: 4; + } + .xl\:bui-col-end-5 { + grid-column-end: 5; + } + .xl\:bui-col-end-6 { + grid-column-end: 6; + } + .xl\:bui-col-end-7 { + grid-column-end: 7; + } + .xl\:bui-col-end-8 { + grid-column-end: 8; + } + .xl\:bui-col-end-9 { + grid-column-end: 9; + } + .xl\:bui-col-end-10 { + grid-column-end: 10; + } + .xl\:bui-col-end-11 { + grid-column-end: 11; + } + .xl\:bui-col-end-12 { + grid-column-end: 12; + } + .xl\:bui-col-end-13 { + grid-column-end: 13; + } + .xl\:bui-col-end-auto { + grid-column-end: auto; + } + .xl\:bui-row-span-1 { + grid-row: span 1 / span 1; + } + .xl\:bui-row-span-2 { + grid-row: span 2 / span 2; + } + .xl\:bui-row-span-3 { + grid-row: span 3 / span 3; + } + .xl\:bui-row-span-4 { + grid-row: span 4 / span 4; + } + .xl\:bui-row-span-5 { + grid-row: span 5 / span 5; + } + .xl\:bui-row-span-6 { + grid-row: span 6 / span 6; + } + .xl\:bui-row-span-7 { + grid-row: span 7 / span 7; + } + .xl\:bui-row-span-8 { + grid-row: span 8 / span 8; + } + .xl\:bui-row-span-9 { + grid-row: span 9 / span 9; + } + .xl\:bui-row-span-10 { + grid-row: span 10 / span 10; + } + .xl\:bui-row-span-11 { + grid-row: span 11 / span 11; + } + .xl\:bui-row-span-12 { + grid-row: span 12 / span 12; + } + .xl\:bui-row-span-auto { + grid-row: span auto/span auto; + } + } + .bui-gap { + gap: var(--gap); + } + .bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + .bui-gap-1 { + gap: var(--bui-space-1); + } + .bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + .bui-gap-2 { + gap: var(--bui-space-2); + } + .bui-gap-3 { + gap: var(--bui-space-3); + } + .bui-gap-4 { + gap: var(--bui-space-4); + } + .bui-gap-5 { + gap: var(--bui-space-5); + } + .bui-gap-6 { + gap: var(--bui-space-6); + } + .bui-gap-7 { + gap: var(--bui-space-7); + } + .bui-gap-8 { + gap: var(--bui-space-8); + } + .bui-gap-9 { + gap: var(--bui-space-9); + } + .bui-gap-10 { + gap: var(--bui-space-10); + } + .bui-gap-11 { + gap: var(--bui-space-11); + } + .bui-gap-12 { + gap: var(--bui-space-12); + } + .bui-gap-13 { + gap: var(--bui-space-13); + } + .bui-gap-14 { + gap: var(--bui-space-14); + } + @media (width>=640px) { + .xs\:bui-gap { + gap: var(--gap-xs); + } + .xs\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + .xs\:bui-gap-1 { + gap: var(--bui-space-1); + } + .xs\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + .xs\:bui-gap-2 { + gap: var(--bui-space-2); + } + .xs\:bui-gap-3 { + gap: var(--bui-space-3); + } + .xs\:bui-gap-4 { + gap: var(--bui-space-4); + } + .xs\:bui-gap-5 { + gap: var(--bui-space-5); + } + .xs\:bui-gap-6 { + gap: var(--bui-space-6); + } + .xs\:bui-gap-7 { + gap: var(--bui-space-7); + } + .xs\:bui-gap-8 { + gap: var(--bui-space-8); + } + .xs\:bui-gap-9 { + gap: var(--bui-space-9); + } + .xs\:bui-gap-10 { + gap: var(--bui-space-10); + } + .xs\:bui-gap-11 { + gap: var(--bui-space-11); + } + .xs\:bui-gap-12 { + gap: var(--bui-space-12); + } + .xs\:bui-gap-13 { + gap: var(--bui-space-13); + } + .xs\:bui-gap-14 { + gap: var(--bui-space-14); + } + } + @media (width>=768px) { + .sm\:bui-gap { + gap: var(--gap-sm); + } + .sm\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + .sm\:bui-gap-1 { + gap: var(--bui-space-1); + } + .sm\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + .sm\:bui-gap-2 { + gap: var(--bui-space-2); + } + .sm\:bui-gap-3 { + gap: var(--bui-space-3); + } + .sm\:bui-gap-4 { + gap: var(--bui-space-4); + } + .sm\:bui-gap-5 { + gap: var(--bui-space-5); + } + .sm\:bui-gap-6 { + gap: var(--bui-space-6); + } + .sm\:bui-gap-7 { + gap: var(--bui-space-7); + } + .sm\:bui-gap-8 { + gap: var(--bui-space-8); + } + .sm\:bui-gap-9 { + gap: var(--bui-space-9); + } + .sm\:bui-gap-10 { + gap: var(--bui-space-10); + } + .sm\:bui-gap-11 { + gap: var(--bui-space-11); + } + .sm\:bui-gap-12 { + gap: var(--bui-space-12); + } + .sm\:bui-gap-13 { + gap: var(--bui-space-13); + } + .sm\:bui-gap-14 { + gap: var(--bui-space-14); + } + } + @media (width>=1024px) { + .md\:bui-gap { + gap: var(--gap-md); + } + .md\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + .md\:bui-gap-1 { + gap: var(--bui-space-1); + } + .md\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + .md\:bui-gap-2 { + gap: var(--bui-space-2); + } + .md\:bui-gap-3 { + gap: var(--bui-space-3); + } + .md\:bui-gap-4 { + gap: var(--bui-space-4); + } + .md\:bui-gap-5 { + gap: var(--bui-space-5); + } + .md\:bui-gap-6 { + gap: var(--bui-space-6); + } + .md\:bui-gap-7 { + gap: var(--bui-space-7); + } + .md\:bui-gap-8 { + gap: var(--bui-space-8); + } + .md\:bui-gap-9 { + gap: var(--bui-space-9); + } + .md\:bui-gap-10 { + gap: var(--bui-space-10); + } + .md\:bui-gap-11 { + gap: var(--bui-space-11); + } + .md\:bui-gap-12 { + gap: var(--bui-space-12); + } + .md\:bui-gap-13 { + gap: var(--bui-space-13); + } + .md\:bui-gap-14 { + gap: var(--bui-space-14); + } + } + @media (width>=1280px) { + .lg\:bui-gap { + gap: var(--gap-lg); + } + .lg\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + .lg\:bui-gap-1 { + gap: var(--bui-space-1); + } + .lg\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + .lg\:bui-gap-2 { + gap: var(--bui-space-2); + } + .lg\:bui-gap-3 { + gap: var(--bui-space-3); + } + .lg\:bui-gap-4 { + gap: var(--bui-space-4); + } + .lg\:bui-gap-5 { + gap: var(--bui-space-5); + } + .lg\:bui-gap-6 { + gap: var(--bui-space-6); + } + .lg\:bui-gap-7 { + gap: var(--bui-space-7); + } + .lg\:bui-gap-8 { + gap: var(--bui-space-8); + } + .lg\:bui-gap-9 { + gap: var(--bui-space-9); + } + .lg\:bui-gap-10 { + gap: var(--bui-space-10); + } + .lg\:bui-gap-11 { + gap: var(--bui-space-11); + } + .lg\:bui-gap-12 { + gap: var(--bui-space-12); + } + .lg\:bui-gap-13 { + gap: var(--bui-space-13); + } + .lg\:bui-gap-14 { + gap: var(--bui-space-14); + } + } + @media (width>=1536px) { + .xl\:bui-gap { + gap: var(--gap-xl); + } + .xl\:bui-gap-0\.5 { + gap: var(--bui-space-0_5); + } + .xl\:bui-gap-1 { + gap: var(--bui-space-1); + } + .xl\:bui-gap-1\.5 { + gap: var(--bui-space-1_5); + } + .xl\:bui-gap-2 { + gap: var(--bui-space-2); + } + .xl\:bui-gap-3 { + gap: var(--bui-space-3); + } + .xl\:bui-gap-4 { + gap: var(--bui-space-4); + } + .xl\:bui-gap-5 { + gap: var(--bui-space-5); + } + .xl\:bui-gap-6 { + gap: var(--bui-space-6); + } + .xl\:bui-gap-7 { + gap: var(--bui-space-7); + } + .xl\:bui-gap-8 { + gap: var(--bui-space-8); + } + .xl\:bui-gap-9 { + gap: var(--bui-space-9); + } + .xl\:bui-gap-10 { + gap: var(--bui-space-10); + } + .xl\:bui-gap-11 { + gap: var(--bui-space-11); + } + .xl\:bui-gap-12 { + gap: var(--bui-space-12); + } + .xl\:bui-gap-13 { + gap: var(--bui-space-13); + } + .xl\:bui-gap-14 { + gap: var(--bui-space-14); + } + } + .bui-align-start { + align-items: start; + } + .bui-align-center { + align-items: center; + } + .bui-align-end { + align-items: end; + } + .bui-align-baseline { + align-items: baseline; + } + .bui-align-stretch { + align-items: stretch; + } + .bui-jc-start { + justify-content: start; + } + .bui-jc-center { + justify-content: center; + } + .bui-jc-end { + justify-content: end; + } + .bui-jc-between { + justify-content: space-between; + } + .bui-fd-row { + flex-direction: row; + } + .bui-fd-column { + flex-direction: column; + } + .bui-fd-row-reverse { + flex-direction: row-reverse; + } + .bui-fd-column-reverse { + flex-direction: column-reverse; + } + @media (width>=640px) { + .xs\:bui-align-start { + align-items: start; + } + .xs\:bui-align-center { + align-items: center; + } + .xs\:bui-align-end { + align-items: end; + } + .xs\:bui-align-stretch { + align-items: stretch; + } + .xs\:bui-jc-start { + justify-content: start; + } + .xs\:bui-jc-center { + justify-content: center; + } + .xs\:bui-jc-end { + justify-content: end; + } + .xs\:bui-jc-between { + justify-content: space-between; + } + .xs\:bui-fd-row { + flex-direction: row; + } + .xs\:bui-fd-column { + flex-direction: column; + } + .xs\:bui-fd-row-reverse { + flex-direction: row-reverse; + } + .xs\:bui-fd-column-reverse { + flex-direction: column-reverse; + } + } + @media (width>=768px) { + .sm\:bui-align-start { + align-items: start; + } + .sm\:bui-align-center { + align-items: center; + } + .sm\:bui-align-end { + align-items: end; + } + .sm\:bui-align-stretch { + align-items: stretch; + } + .sm\:bui-jc-start { + justify-content: start; + } + .sm\:bui-jc-center { + justify-content: center; + } + .sm\:bui-jc-end { + justify-content: end; + } + .sm\:bui-jc-between { + justify-content: space-between; + } + .sm\:bui-fd-row { + flex-direction: row; + } + .sm\:bui-fd-column { + flex-direction: column; + } + .sm\:bui-fd-row-reverse { + flex-direction: row-reverse; + } + .sm\:bui-fd-column-reverse { + flex-direction: column-reverse; + } + } + @media (width>=1024px) { + .md\:bui-align-start { + align-items: start; + } + .md\:bui-align-center { + align-items: center; + } + .md\:bui-align-end { + align-items: end; + } + .md\:bui-align-stretch { + align-items: stretch; + } + .md\:bui-jc-start { + justify-content: start; + } + .md\:bui-jc-center { + justify-content: center; + } + .md\:bui-jc-end { + justify-content: end; + } + .md\:bui-jc-between { + justify-content: space-between; + } + .md\:bui-fd-row { + flex-direction: row; + } + .md\:bui-fd-column { + flex-direction: column; + } + .md\:bui-fd-row-reverse { + flex-direction: row-reverse; + } + .md\:bui-fd-column-reverse { + flex-direction: column-reverse; + } + } + @media (width>=1280px) { + .lg\:bui-align-start { + align-items: start; + } + .lg\:bui-align-center { + align-items: center; + } + .lg\:bui-align-end { + align-items: end; + } + .lg\:bui-align-stretch { + align-items: stretch; + } + .lg\:bui-jc-start { + justify-content: start; + } + .lg\:bui-jc-center { + justify-content: center; + } + .lg\:bui-jc-end { + justify-content: end; + } + .lg\:bui-jc-between { + justify-content: space-between; + } + .lg\:bui-fd-row { + flex-direction: row; + } + .lg\:bui-fd-column { + flex-direction: column; + } + .lg\:bui-fd-row-reverse { + flex-direction: row-reverse; + } + .lg\:bui-fd-column-reverse { + flex-direction: column-reverse; + } + } + @media (width>=1536px) { + .xl\:bui-align-start { + align-items: start; + } + .xl\:bui-align-center { + align-items: center; + } + .xl\:bui-align-end { + align-items: end; + } + .xl\:bui-align-stretch { + align-items: stretch; + } + .xl\:bui-jc-start { + justify-content: start; + } + .xl\:bui-jc-center { + justify-content: center; + } + .xl\:bui-jc-end { + justify-content: end; + } + .xl\:bui-jc-between { + justify-content: space-between; + } + .xl\:bui-fd-row { + flex-direction: row; + } + .xl\:bui-fd-column { + flex-direction: column; + } + .xl\:bui-fd-row-reverse { + flex-direction: row-reverse; + } + .xl\:bui-fd-column-reverse { + flex-direction: column-reverse; + } + } +} diff --git a/docs-ui/src/css/theme-spotify.css b/docs-ui/src/css/theme-spotify.css new file mode 100644 index 0000000000..dc660b5b5d --- /dev/null +++ b/docs-ui/src/css/theme-spotify.css @@ -0,0 +1,239 @@ +@font-face { + font-family: CircularSpTitle; + font-weight: 900; + font-display: swap; + unicode-range: U+0, U+D, U+20-7E, U+A0-17E, U+18F, U+192, U+1A0-1A1, U+1AF-1B0, + U+1B5-1B6, U+1C4-1C6, U+1F1-1F3, U+1FA-1FF, U+218-21B, U+237, U+259, + U+2BB-2BC, U+2C6-2C7, U+2C9, U+2D8-2DD, U+300-301, U+303, U+309, U+323, + U+394, U+3A9, U+3BC, U+3C0, U+1E80-1E85, U+1E8A-1E8B, U+1EA0-1EF9, U+1FD6, + U+2007-2008, U+200B, U+2010-2011, U+2013-2014, U+2018-201A, U+201C-201E, + U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2042, U+2044, + U+2051, U+2070, U+2074-2079, U+2080-2089, U+20AB-20AC, U+2113, U+2117, + U+2122, U+2126, U+2160-2169, U+216C-216F, U+2190-2193, U+2196-2199, U+21A9, + U+21B0-21B5, U+21C6, U+2202, U+2206, U+220F, U+2211-2212, U+2215, + U+2219-221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+22C5, U+24C5, + U+25A0-25A1, U+25AF, U+25B2-25B3, U+25CA-25CB, U+25CF, U+262E, U+2713, + U+2715, U+2780-2788, U+E000, U+E002, U+F6C3, U+FB00-FB04, U+FEFF, U+FF0C, + U+FF0E, U+FF1A-FF1B, U+FFFF; + src: url(https://encore.scdn.co/fonts/CircularSpTitle-Black-4588c99025b967475c31695b0743dd1a.woff2) + format('woff2'), + url(https://encore.scdn.co/fonts/CircularSpTitle-Black-506746f387a26f25aa3d023b3e501d34.woff) + format('woff'); +} +@font-face { + font-family: CircularSpTitle; + font-weight: 700; + font-display: swap; + unicode-range: U+0, U+D, U+20-7E, U+A0-17E, U+18F, U+192, U+1A0-1A1, U+1AF-1B0, + U+1B5-1B6, U+1C4-1C6, U+1F1-1F3, U+1FA-1FF, U+218-21B, U+237, U+259, + U+2BB-2BC, U+2C6-2C7, U+2C9, U+2D8-2DD, U+300-301, U+303, U+309, U+323, + U+394, U+3A9, U+3BC, U+3C0, U+1E80-1E85, U+1E8A-1E8B, U+1EA0-1EF9, U+1FD6, + U+2007-2008, U+200B, U+2010-2011, U+2013-2014, U+2018-201A, U+201C-201E, + U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2042, U+2044, + U+2051, U+2070, U+2074-2079, U+2080-2089, U+20AB-20AC, U+2113, U+2117, + U+2122, U+2126, U+2160-2169, U+216C-216F, U+2190-2193, U+2196-2199, U+21A9, + U+21B0-21B5, U+21C6, U+2202, U+2206, U+220F, U+2211-2212, U+2215, + U+2219-221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+22C5, U+24C5, + U+25A0-25A1, U+25AF, U+25B2-25B3, U+25CA-25CB, U+25CF, U+262E, U+2713, + U+2715, U+2780-2788, U+E000, U+E002, U+F6C3, U+FB00-FB04, U+FEFF, U+FF0C, + U+FF0E, U+FF1A-FF1B, U+FFFF; + src: url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-b2586b06a2e1522e9d879d84c2792a58.woff2) + format('woff2'), + url(https://encore.scdn.co/fonts/CircularSpTitle-Bold-1624fb2df28c20d7203d7fb86ce8b481.woff) + format('woff'); +} +@font-face { + font-family: CircularSp; + font-weight: 700; + font-display: swap; + unicode-range: U+0, U+D, U+20-7E, U+A0-17E, U+18F, U+192, U+1A0-1A1, U+1AF-1B0, + U+1B5-1B6, U+1C4-1C6, U+1F1-1F3, U+1FA-1FF, U+218-21B, U+237, U+259, + U+2BB-2BC, U+2C6-2C7, U+2C9, U+2D8-2DD, U+300-301, U+303, U+309, U+323, + U+394, U+3A9, U+3BC, U+3C0, U+1E80-1E85, U+1E8A-1E8B, U+1EA0-1EF9, U+1FD6, + U+2007-2008, U+200B, U+2010-2011, U+2013-2014, U+2018-201A, U+201C-201E, + U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2042, U+2044, + U+2051, U+2070, U+2074-2079, U+2080-2089, U+20AB-20AC, U+2113, U+2117, + U+2122, U+2126, U+2160-2169, U+216C-216F, U+2190-2193, U+2196-2199, U+21A9, + U+21B0-21B5, U+21C6, U+2202, U+2206, U+220F, U+2211-2212, U+2215, + U+2219-221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+22C5, U+24C5, + U+25A0-25A1, U+25AF, U+25B2-25B3, U+25CA-25CB, U+25CF, U+262E, U+2713, + U+2715, U+2780-2788, U+E000, U+E002, U+F6C3, U+FB00-FB04, U+FEFF, U+FF0C, + U+FF0E, U+FF1A-FF1B, U+FFFF; + src: url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2) + format('woff2'), + url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff) + format('woff'); +} +@font-face { + font-family: CircularSp; + font-weight: 400; + font-display: swap; + unicode-range: U+0, U+D, U+20-7E, U+A0-17E, U+18F, U+192, U+1A0-1A1, U+1AF-1B0, + U+1B5-1B6, U+1C4-1C6, U+1F1-1F3, U+1FA-1FF, U+218-21B, U+237, U+259, + U+2BB-2BC, U+2C6-2C7, U+2C9, U+2D8-2DD, U+300-301, U+303, U+309, U+323, + U+394, U+3A9, U+3BC, U+3C0, U+1E80-1E85, U+1E8A-1E8B, U+1EA0-1EF9, U+1FD6, + U+2007-2008, U+200B, U+2010-2011, U+2013-2014, U+2018-201A, U+201C-201E, + U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2042, U+2044, + U+2051, U+2070, U+2074-2079, U+2080-2089, U+20AB-20AC, U+2113, U+2117, + U+2122, U+2126, U+2160-2169, U+216C-216F, U+2190-2193, U+2196-2199, U+21A9, + U+21B0-21B5, U+21C6, U+2202, U+2206, U+220F, U+2211-2212, U+2215, + U+2219-221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+22C5, U+24C5, + U+25A0-25A1, U+25AF, U+25B2-25B3, U+25CA-25CB, U+25CF, U+262E, U+2713, + U+2715, U+2780-2788, U+E000, U+E002, U+F6C3, U+FB00-FB04, U+FEFF, U+FF0C, + U+FF0E, U+FF1A-FF1B, U+FFFF; + src: url(https://encore.scdn.co/fonts/CircularSp-Book-a00e99ef9996a3a157fb6b746856d04f.woff2) + format('woff2'), + url(https://encore.scdn.co/fonts/CircularSp-Book-3f73da7d35bd81c706bce7bbb84964de.woff) + format('woff'); +} +@font-face { + font-family: CircularSp; + font-weight: 700; + font-display: swap; + unicode-range: U+0, U+D, U+20-7E, U+A0-17E, U+18F, U+192, U+1A0-1A1, U+1AF-1B0, + U+1B5-1B6, U+1C4-1C6, U+1F1-1F3, U+1FA-1FF, U+218-21B, U+237, U+259, + U+2BB-2BC, U+2C6-2C7, U+2C9, U+2D8-2DD, U+300-301, U+303, U+309, U+323, + U+394, U+3A9, U+3BC, U+3C0, U+1E80-1E85, U+1E8A-1E8B, U+1EA0-1EF9, U+1FD6, + U+2007-2008, U+200B, U+2010-2011, U+2013-2014, U+2018-201A, U+201C-201E, + U+2020-2022, U+2026, U+2030, U+2032-2033, U+2039-203A, U+2042, U+2044, + U+2051, U+2070, U+2074-2079, U+2080-2089, U+20AB-20AC, U+2113, U+2117, + U+2122, U+2126, U+2160-2169, U+216C-216F, U+2190-2193, U+2196-2199, U+21A9, + U+21B0-21B5, U+21C6, U+2202, U+2206, U+220F, U+2211-2212, U+2215, + U+2219-221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+22C5, U+24C5, + U+25A0-25A1, U+25AF, U+25B2-25B3, U+25CA-25CB, U+25CF, U+262E, U+2713, + U+2715, U+2780-2788, U+E000, U+E002, U+F6C3, U+FB00-FB04, U+FEFF, U+FF0C, + U+FF0E, U+FF1A-FF1B, U+FFFF; + src: url(https://encore.scdn.co/fonts/CircularSp-Bold-602e7aefc706aa36c6ec1324b9bbc461.woff2) + format('woff2'), + url(https://encore.scdn.co/fonts/CircularSp-Bold-856afe2da4ba4e61239b129e2c16d633.woff) + format('woff'); +} +[data-theme-name='spotify'] { + --bui-font-text: CircularSp, CircularSp-Arab, CircularSp-Hebr, CircularSp-Cyrl, + CircularSp-Grek, CircularSp-Deva; + --bui-font-title: CircularSpTitle, CircularSp-Arab, CircularSp-Hebr, + CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva; + --bui-font-regular: CircularSp, CircularSp-Arab, CircularSp-Hebr, + CircularSp-Cyrl, CircularSp-Grek, CircularSp-Deva; + & .bui-Button { + border-radius: var(--bui-radius-3); + padding-inline: var(--bui-space-3); + } + & .bui-ButtonIcon { + padding: 0; + } + & .bui-MenuPopup { + box-sizing: border-box; + max-width: 21.25rem; + } + & .bui-MenuSubmenuTrigger, + & .bui-MenuItem { + height: auto; + min-height: 2rem; + } + & .bui-Text { + font-family: var(--bui-font-text); + } + & .bui-Heading { + font-family: var(--bui-font-title); + } + & .bui-TableRow { + border-radius: var(--bui-radius-4); + border: none; + } + & .bui-TableRow:hover td:first-child { + border-top-left-radius: var(--bui-radius-2); + border-bottom-left-radius: var(--bui-radius-2); + } + & .bui-TableRow:hover td:last-child { + border-top-right-radius: var(--bui-radius-2); + border-bottom-right-radius: var(--bui-radius-2); + } + & .bui-TableBody:before, + & .bui-TableBody:after { + line-height: var(--bui-space-1); + content: '‌'; + display: block; + } + & .bui-TableHeader .bui-TableRow { + border-bottom: 1px solid var(--bui-border); + } + & .bui-TableHead { + font-size: var(--bui-font-size-2); + color: var(--bui-fg-secondary); + font-weight: var(--bui-font-weight-regular); + } + & .bui-HeaderToolbar { + padding-top: var(--bui-space-2); + padding-inline: var(--bui-space-2); + } + & .bui-HeaderToolbarWrapper { + border-radius: var(--bui-radius-3); + padding-inline: var(--bui-space-3); + border: none; + } + & .bui-HeaderToolbarControls { + right: calc(var(--bui-space-3) - 1px); + } + & .bui-HeaderTabsWrapper { + margin-top: var(--bui-space-2); + margin-inline: var(--bui-space-2); + border-radius: var(--bui-radius-3); + padding-inline: var(--bui-space-1); + border: none; + } + & .bui-Input { + border-radius: var(--bui-radius-3); + } + & .bui-Tag { + border-radius: var(--bui-radius-full); + } +} +[data-theme-mode='light'][data-theme-name='spotify'] { + --bui-bg-solid: #1ed760; + --bui-bg-solid-hover: #3be477; + --bui-bg-solid-pressed: #1abc54; + --bui-bg-solid-disabled: #0f6c30; + --bui-fg-primary: var(--bui-black); + --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-solid: var(--bui-black); + --bui-fg-solid-disabled: #62ab7c; + --bui-border: var(--bui-gray-3); + --bui-border-hover: #0000004d; + --bui-border-pressed: #00000080; + --bui-border-disabled: #0000001a; + --bui-border-danger: #f87a7a; + --bui-border-warning: #e36d05; + --bui-border-success: #53db83; + --bui-ring: #0003; + & .bui-HeaderToolbarWrapper, + & .bui-HeaderTabsWrapper { + border: 1px solid var(--bui-border); + } +} +[data-theme-mode='dark'][data-theme-name='spotify'] { + --bui-bg-surface-0: var(--bui-black); + --bui-bg-solid: #1ed760; + --bui-bg-solid-hover: #3be477; + --bui-bg-solid-pressed: #1abc54; + --bui-bg-solid-disabled: #0f6c30; + --bui-bg-danger: #3b1219; + --bui-bg-warning: #302008; + --bui-bg-success: #132d21; + --bui-fg-primary: var(--bui-white); + --bui-fg-secondary: var(--bui-gray-7); + --bui-fg-disabled: var(--bui-gray-5); + --bui-fg-solid: var(--bui-black); + --bui-fg-solid-disabled: #072f15; + --bui-fg-danger: #e22b2b; + --bui-fg-warning: #e36d05; + --bui-fg-success: #1db954; + --bui-border: var(--bui-gray-3); + --bui-border-hover: #fff6; + --bui-border-pressed: #ffffff80; + --bui-border-disabled: #fff3; + --bui-border-danger: #f87a7a; + --bui-border-warning: #e36d05; + --bui-border-success: #53db83; + --bui-ring: #fff3; +} diff --git a/docs-ui/src/utils/playground-context.tsx b/docs-ui/src/utils/playground-context.tsx index a1cee6ecba..252194535b 100644 --- a/docs-ui/src/utils/playground-context.tsx +++ b/docs-ui/src/utils/playground-context.tsx @@ -4,6 +4,7 @@ import { ReactNode, useState, useEffect, + useSyncExternalStore, } from 'react'; import { components } from './data'; @@ -31,6 +32,14 @@ const PlaygroundContext = createContext<{ setSelectedThemeName: () => {}, }); +// Stable server snapshots (outside component to avoid recreating) +const defaultThemeSet = new Set(['light']); +const defaultThemeName: ThemeName = 'backstage'; + +// Cache for theme Sets to avoid creating new objects on every getSnapshot call +let cachedThemeValue: string | null = null; +let cachedThemeSet: Set = defaultThemeSet; + // Create a provider component export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { // Check if running in a browser environment @@ -40,57 +49,72 @@ export const PlaygroundProvider = ({ children }: { children: ReactNode }) => { const [selectedComponents, setSelectedComponents] = useState( components.map(component => component.slug), ); - const [selectedTheme, setSelectedTheme] = useState>( - new Set(['light']), - ); - const [selectedThemeName, setSelectedThemeName] = - useState('backstage'); - // Load saved theme from localStorage after hydration - useEffect(() => { - if (isBrowser) { - const savedThemeString = localStorage.getItem('theme-mode'); - if (savedThemeString) { - // Parse the comma-separated string back into a Set - const themeArray = savedThemeString - .split(',') - .filter(Boolean) as Theme[]; - setSelectedTheme(new Set(themeArray)); + // Use useSyncExternalStore for SSR-safe localStorage access + const selectedTheme = useSyncExternalStore( + callback => { + window.addEventListener('storage', callback); + return () => window.removeEventListener('storage', callback); + }, + () => { + const saved = localStorage.getItem('theme-mode'); + + // Return cached Set if value hasn't changed + if (saved === cachedThemeValue) { + return cachedThemeSet; + } + + // Update cache with new value + cachedThemeValue = saved; + if (saved) { + const themeArray = saved.split(',').filter(Boolean) as Theme[]; + cachedThemeSet = new Set(themeArray); } else { - setSelectedTheme(new Set(['light'])); + cachedThemeSet = defaultThemeSet; } - } - }, [isBrowser]); - // Load saved theme name from localStorage after hydration + return cachedThemeSet; + }, + () => defaultThemeSet, // Stable server snapshot + ); + + const selectedThemeName = useSyncExternalStore( + callback => { + window.addEventListener('storage', callback); + return () => window.removeEventListener('storage', callback); + }, + () => { + const saved = localStorage.getItem('theme-name') as ThemeName; + return saved || defaultThemeName; + }, + () => defaultThemeName, // Stable server snapshot + ); + + // Keep setter functions that update both state and localStorage + const setSelectedTheme = (keys: Set) => { + const value = Array.from(keys).join(','); + localStorage.setItem('theme-mode', value); + // Invalidate cache + cachedThemeValue = null; + window.dispatchEvent(new Event('storage')); + }; + + const setSelectedThemeName = (name: ThemeName) => { + localStorage.setItem('theme-name', name); + window.dispatchEvent(new Event('storage')); + }; + + // Sync to DOM attributes when values change useEffect(() => { - if (isBrowser) { - const savedThemeName = localStorage.getItem('theme-name') as ThemeName; - if (savedThemeName) { - setSelectedThemeName(savedThemeName); - } - } - }, [isBrowser]); + document.documentElement.setAttribute( + 'data-theme-mode', + Array.from(selectedTheme).join(','), + ); + }, [selectedTheme]); useEffect(() => { - if (isBrowser) { - document.documentElement.setAttribute( - 'data-theme-mode', - Array.from(selectedTheme).join(','), - ); - localStorage.setItem('theme-mode', Array.from(selectedTheme).join(',')); - } - }, [selectedTheme, isBrowser]); - - useEffect(() => { - if (isBrowser) { - document.documentElement.setAttribute( - 'data-theme-name', - selectedThemeName || 'backstage', - ); - localStorage.setItem('theme-name', selectedThemeName || 'backstage'); - } - }, [selectedThemeName, isBrowser]); + document.documentElement.setAttribute('data-theme-name', selectedThemeName); + }, [selectedThemeName]); return ( , diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 55827ab1dc..a55719e564 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -136,7 +136,15 @@ auth: - resolver: emailMatchingUserEntityProfileEmail ``` -If none of the built-in resolvers are suitable, you can alternatively write a custom resolver. See an example below: +If none of the built-in resolvers are suitable, you can alternatively write a custom resolver. + +First, install the OIDC provider module: + +```bash +yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-oidc-provider +``` + +Then create a custom resolver as shown below: ```ts title="in packages/backend/src/index.ts" /* highlight-add-start */ @@ -146,6 +154,10 @@ import { createOAuthProviderFactory, } from '@backstage/plugin-auth-node'; import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider'; +import { + stringifyEntityRef, + DEFAULT_NAMESPACE, +} from '@backstage/catalog-model'; const myAuthProviderModule = createBackendModule({ // This ID must be exactly "auth" because that's the plugin it targets @@ -168,7 +180,7 @@ const myAuthProviderModule = createBackendModule({ async signInResolver(info, ctx) { const userRef = stringifyEntityRef({ kind: 'User', - name: info.result.userinfo.sub, + name: info.result.fullProfile.userinfo.sub, namespace: DEFAULT_NAMESPACE, }); return ctx.issueToken({ diff --git a/docs/backend-system/architecture/08-naming-patterns.md b/docs/backend-system/architecture/08-naming-patterns.md index 585a9fe87c..d073f3382c 100644 --- a/docs/backend-system/architecture/08-naming-patterns.md +++ b/docs/backend-system/architecture/08-naming-patterns.md @@ -11,10 +11,10 @@ As a rule, all names should be camel case, with the exceptions of plugin and mod ### Plugins -| Description | Pattern | Examples | -| ----------- | ----------------- | ------------------------------------- | -| export | `Plugin` | `catalogPlugin`, `userSettingsPlugin` | -| ID | `''` | `'catalog'`, `'user-settings'` | +| Description | Pattern | Examples | Notes | +| ----------- | ----------------- | ------------------------------------- | --------------------------------------------------- | +| export | `Plugin` | `catalogPlugin`, `userSettingsPlugin` | | +| ID | `''` | `'catalog'`, `'user-settings'` | letters, digits, and dashes, starting with a letter | Example: @@ -27,10 +27,10 @@ export const userSettingsPlugin = createBackendPlugin({ ### Modules -| Description | Pattern | Examples | -| ----------- | ---------------------------- | ----------------------------------- | -| export | `Module` | `catalogModuleGithubEntityProvider` | -| ID | `''` | `'github-entity-provider'` | +| Description | Pattern | Examples | Notes | +| ----------- | ---------------------------- | ----------------------------------- | --------------------------------------------------- | +| export | `Module` | `catalogModuleGithubEntityProvider` | | +| ID | `''` | `'github-entity-provider'` | letters, digits, and dashes, starting with a letter | Example: diff --git a/docs/backend-system/core-services/root-http-router.md b/docs/backend-system/core-services/root-http-router.md index 9c2c60957f..2f7f324005 100644 --- a/docs/backend-system/core-services/root-http-router.md +++ b/docs/backend-system/core-services/root-http-router.md @@ -164,7 +164,7 @@ Note that requests towards `/api/*` will never be handled by the `routes` handle The root HTTP Router service also allows for configuration of the underlying Node.js HTTP server object. This is useful for modifying settings on the HTTP server itself, such as server [`timeout`](https://nodejs.org/api/http.html#servertimeout), [`keepAliveTimeout`](https://nodejs.org/api/http.html#serverkeepalivetimeout), and [`headersTimeout`](https://nodejs.org/api/http.html#serverheaderstimeout). -A `applyDefaults` helper is also made available to use the default app/router configuration while still enabling custom server configuration +An `applyDefaults` helper is also made available to use the default app/router configuration while still enabling custom server configuration ```ts import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter'; diff --git a/docs/conf/user-interface/index.md b/docs/conf/user-interface/index.md index e0fb645d78..002383c6f4 100644 --- a/docs/conf/user-interface/index.md +++ b/docs/conf/user-interface/index.md @@ -131,7 +131,10 @@ And if you’d like to go even further, you can target specific component class | `--bui-fg-solid` | This is for texts or icons on top of a solid backgrounds. | | `--bui-fg-primary` | Your primary text or icon colours. | | `--bui-fg-secondary` | Your secondary text or icon colours. | -| `--bui-fg-link` | Used for links. | +| `--bui-fg-danger` | Used for error states and destructive actions. | +| `--bui-fg-warning` | Used for warning states and cautionary information. | +| `--bui-fg-success` | Used for success states and positive feedback. | +| `--bui-fg-info` | Used for informational content and neutral status. | | `--bui-border` | Main borders around surfaces like `Card`, `Dialog`, ... | | `--bui-font-regular` | The main font of your app. | @@ -169,31 +172,29 @@ These colors are used for the background of your application. We are mostly usin | `--bui-bg-solid-hover` | Used for solid background colors when hovered. | | `--bui-bg-solid-pressed` | Used for solid background colors when pressed. | | `--bui-bg-solid-disabled` | Used for solid background colors when disabled. | -| `--bui-bg-tint` | Used for tint background colors. | -| `--bui-bg-tint-hover` | Used for tint background colors when hovered. | -| `--bui-bg-tint-focus` | Used for tint background colors when active. | -| `--bui-bg-tint-disabled` | Used for tint background colors when disabled. | | `--bui-bg-danger` | Used to show errors information. | | `--bui-bg-warning` | Used to show warnings information. | | `--bui-bg-success` | Used to show success information. | +| `--bui-bg-info` | Used to show informational content. | #### Foreground colors Foreground colours are meant to work in pair with a background colours. Typically this would work for icons, texts, shapes, ... Use a matching name to know what foreground color to use. These colors are prefixed with `fg` to make it easier to identify. -| Token Name | Description | -| ------------------------ | ----------------------------------------------------------------- | -| `--bui-fg-primary` | It should be used on top of main background surfaces. | -| `--bui-fg-secondary` | It should be used on top of main background surfaces. | -| `--bui-fg-link` | It should be used on top of main background surfaces. | -| `--bui-fg-link-hover` | It should be used on top of main background surfaces. | -| `--bui-fg-disabled` | It should be used on top of main background surfaces. | -| `--bui-fg-solid` | It should be used on top of solid background colors. | -| `--bui-fg-tint` | It should be used on top of tint background colors. | -| `--bui-fg-tint-disabled` | It should be used on top of tint background colors when disabled. | -| `--bui-fg-danger` | It should be used on top of danger background colors. | -| `--bui-fg-warning` | It should be used on top of warning background colors. | -| `--bui-fg-success` | It should be used on top of success background colors. | +| Token Name | Description | +| ------------------------ | ------------------------------------------------------ | +| `--bui-fg-primary` | It should be used on top of main background surfaces. | +| `--bui-fg-secondary` | It should be used on top of main background surfaces. | +| `--bui-fg-disabled` | It should be used on top of main background surfaces. | +| `--bui-fg-solid` | It should be used on top of solid background colors. | +| `--bui-fg-danger` | Used for error states and destructive actions. | +| `--bui-fg-warning` | Used for warning states and cautionary information. | +| `--bui-fg-success` | Used for success states and positive feedback. | +| `--bui-fg-info` | Used for informational content and neutral status. | +| `--bui-fg-danger-on-bg` | It should be used on top of danger background colors. | +| `--bui-fg-warning-on-bg` | It should be used on top of warning background colors. | +| `--bui-fg-success-on-bg` | It should be used on top of success background colors. | +| `--bui-fg-info-on-bg` | It should be used on top of info background colors. | #### Border colors diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md index 07cf11aae5..5b8f546b40 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -291,3 +291,65 @@ search: fuzziness: AUTO prefixLength: 3; ``` + +### Custom Authentication Extension Point + +For enterprise environments that require dynamic authentication mechanisms such as bearer tokens with automatic rotation, the Elasticsearch module provides an authentication extension point. This is useful when: + +- Using OAuth2/OIDC identity providers for service authentication +- Tokens need to be refreshed automatically (e.g., tokens that expire hourly) +- Integrating with internal identity services +- Running Elasticsearch/OpenSearch clusters secured by token-based authentication + +To use custom authentication, create a backend module that provides an auth provider: + +```ts title="packages/backend/src/modules/elasticsearchAuth.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { elasticsearchAuthExtensionPoint } from '@backstage/plugin-search-backend-module-elasticsearch'; + +export default createBackendModule({ + pluginId: 'search', + moduleId: 'elasticsearch-custom-auth', + register(env) { + env.registerInit({ + deps: { + elasticsearchAuth: elasticsearchAuthExtensionPoint, + }, + async init({ elasticsearchAuth }) { + elasticsearchAuth.setAuthProvider({ + async getAuthHeaders() { + // Fetch token from your identity service + const token = await myTokenService.getToken(); + return { Authorization: `Bearer ${token}` }; + }, + }); + }, + }); + }, +}); +``` + +Then register this module in your backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +backend.add(import('@backstage/plugin-search-backend')); +backend.add(import('@backstage/plugin-search-backend-module-elasticsearch')); + +/* highlight-add-start */ +backend.add(import('./modules/elasticsearchAuth')); +/* highlight-add-end */ + +backend.start(); +``` + +The `getAuthHeaders` method is called before each request, allowing for just-in-time token retrieval and automatic rotation. When an auth provider is configured, it takes precedence over any static authentication in `app-config.yaml`. + +:::note Note + +Custom authentication is supported for the `elastic`, `opensearch`, and default providers. The `aws` provider uses AWS SigV4 request signing and does not support custom auth providers. + +::: diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 1a64406b04..7e0ee1c5a9 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -91,8 +91,8 @@ The `createTemplateAction` takes an object which specifies the following: may ship with the `scaffolder-backend` plugin. - `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions` endpoint. -- `schema.input` - A `zod` or JSON schema object for input values to your function -- `schema.output` - A `zod` or JSON schema object for values which are output from the +- `schema.input` - A `zod` schema object for input values to your function +- `schema.output` - A `zod` schema object for values which are output from the function using `ctx.output` - `handler` - the actual code which is run as part of the action, with a context @@ -195,7 +195,7 @@ argument. It looks like the following: executed successfully on the previous run. - `ctx.logger` - a [LoggerService](../../backend-system/core-services/logger.md) instance for additional logging inside your action - `ctx.workspacePath` - a string of the working directory of the template run -- `ctx.input` - an object which should match the `zod` or JSON schema provided in the +- `ctx.input` - an object which should match the `zod` schema provided in the `schema.input` part of the action definition - `ctx.output` - a function which you can call to set outputs that match the `zod` schema in `schema.output` for ex. `ctx.output('downloadUrl', myDownloadUrl)` diff --git a/docs/getting-started/config/database.md b/docs/getting-started/config/database.md index d325c73d5f..cb482d5dc0 100644 --- a/docs/getting-started/config/database.md +++ b/docs/getting-started/config/database.md @@ -106,6 +106,88 @@ When filling these out, you have 2 choices, If you opt for the second option of replacing the entire string, take care to not commit your `app-config.yaml` to source control. It may contain passwords that you don't want leaked. +## Passwordless PostgreSQL in the Cloud + +If you want to host your PostgreSQL server in the cloud with passwordless authentication, you can use Azure Database for PostgreSQL with Microsoft Entra authentication or Google Cloud SQL for PostgreSQL with Cloud IAM. + +### Azure with Entra authentication + +Remove `password` from the connection configuration and set `type` to `azure`. + +Optionally set `tokenCredential` with the following properties. If no credential information is provided, it will default to using Default Azure Credential and a tokenRenewalOffsetTime of 5 minutes. + +#### Credential Selection + +The credential type is automatically inferred based on the fields you provide: + +- Client Secret Credential is used when all three are provided: + - `tenantId` + - `clientId` + - `clientSecret` +- Managed Identity Credential is used when only `clientId` is provided. This enables user-assigned managed identity. +- Default Azure Credential is used when no credential fields are provided. Default Azure Credential supports [many credential types](https://learn.microsoft.com/azure/developer/javascript/sdk/authentication/credential-chains#use-defaultazurecredential-for-flexibility), choosing one based on the runtime environment. + +#### Token Renewal + +Set `tokenRenewalOffsetTime` to control how early OAuth tokens should be refreshed. + +The value may be: + +- A human-readable string such as '1d', '2 hours', '30 seconds' +- A duration object, e.g. { minutes: 3, seconds: 30 } + +Azure PostgreSQL uses short-lived Entra ID access tokens. +By default, the database connector refreshes tokens 5 minutes before they expire. + +#### User Configuration + +Set `user` to the display name of your Entra ID group, service principal, or managed identity. Set it to the user principal name if you're authenticating with a user's credentials. + +#### Example + +```yaml title="app-config.yaml" +backend: + database: + client: pg + connection: + # highlight-add-start + type: azure + tokenCredential: + tokenRenewalOffsetTime: 5 minutes + # highlight-add-end + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} + user: ${POSTGRES_USER} + # highlight-remove-start + password: ${POSTGRES_PASSWORD} + # highlight-remove-end +``` + +### Google with Cloud IAM + +Remove `password` from the connection configuration and set `type` to `cloudsql`. + +Under the hood, this implements [Automatic IAM Database Authentication](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector?tab=readme-ov-file#automatic-iam-database-authentication). + +For an IAM user account, set `user` to the user's email address. For a service account, set `user` to the service account's email without the .gserviceaccount.com domain suffix. + +```yaml title="app-config.yaml" +backend: + database: + client: pg + connection: + # highlight-add-start + type: cloudsql + instance: my-project:region:my-instance + # highlight-add-end + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} + user: ${POSTGRES_USER} + # highlight-remove-start + password: ${POSTGRES_PASSWORD} + # highlight-remove-end +``` + ::: [Start the Backstage app](../index.md#2-run-the-backstage-app): diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index c7aabb7bd5..d7c4ee96c8 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -81,7 +81,7 @@ This guide also assumes a basic understanding of working on a Linux based operat - Using `nvm` (recommended) - [Installing nvm](https://github.com/nvm-sh/nvm#install--update-script) - [Install and change Node version with nvm](https://nodejs.org/en/download/package-manager/#nvm) - - Node 24 is a good starting point, this can be installed using `nvm install lts/krypton` + - Node 22 or 24 are recommended, these can be installed using `nvm install 22` or `nvm install 24` - [Binary Download](https://nodejs.org/en/download/) - [Package manager](https://nodejs.org/en/download/package-manager/) - [Using NodeSource packages](https://github.com/nodesource/distributions/blob/master/README.md) diff --git a/docs/notifications/index.md b/docs/notifications/index.md index dce58d3214..3d7dc0eaac 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -147,9 +147,6 @@ add the `UserNotificationSettingsCard` to your frontend. ```tsx title="packages/app/src/App.tsx" }> - - - { const { isMobile } = useSidebarPinState(); const { types } = useSearch(); const catalogApi = useApi(catalogApiRef); + const configApi = useApi(configApiRef); return ( @@ -73,7 +74,7 @@ const SearchPage = () => { { const createMockConnection = () => ({ @@ -140,6 +141,271 @@ describe('postgres', () => { }); }); + it('should default to using default azure credentials when type is azure with no credentials', async () => { + const { DefaultAzureCredential } = jest.requireMock( + '@azure/identity', + ) as jest.Mocked; + + const tokenExpirationTimestamp = new Date( + '2025-01-01T12:34:56.789', + ).valueOf(); + + DefaultAzureCredential.prototype.getToken.mockResolvedValue({ + token: 'afaketoken', + expiresOnTimestamp: tokenExpirationTimestamp, + }); + + const configResult = await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + user: 'some-user@domain.com', + port: 5423, + database: 'other_db', + }, + }), + ); + + expect(DefaultAzureCredential).toHaveBeenCalled(); + expect(configResult).toMatchObject({ + client: 'pg', + connection: expect.any(Function), + useNullAsDefault: true, + }); + + const connectionResult = await configResult.connection(); + + expect(connectionResult).toMatchObject({ + user: 'some-user@domain.com', + password: 'afaketoken', + port: 5423, + expirationChecker: expect.any(Function), + }); + expect(connectionResult).not.toHaveProperty('allowedClockSkewMs'); + expect(connectionResult).not.toHaveProperty('type'); + expect(connectionResult).not.toHaveProperty('tokenCredential'); + }); + + it('uses the correct config when using azure managed identity', async () => { + const { ManagedIdentityCredential } = jest.requireMock( + '@azure/identity', + ) as jest.Mocked; + + const tokenExpirationTimestamp = new Date( + '2025-01-01T12:34:56.789', + ).valueOf(); + + ManagedIdentityCredential.prototype.getToken.mockResolvedValue({ + token: 'afaketoken', + expiresOnTimestamp: tokenExpirationTimestamp, + }); + + const configResult = await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + user: 'some-user@domain.com', + port: 5423, + database: 'other_db', + tokenCredential: { + clientId: 'my-client-id', + }, + }, + }), + ); + + expect(ManagedIdentityCredential).toHaveBeenCalledWith('my-client-id'); + expect(configResult).toMatchObject({ + client: 'pg', + connection: expect.any(Function), + useNullAsDefault: true, + }); + + const connectionResult = await configResult.connection(); + + expect(connectionResult).toMatchObject({ + user: 'some-user@domain.com', + password: 'afaketoken', + port: 5423, + expirationChecker: expect.any(Function), + }); + expect(connectionResult).not.toHaveProperty('type'); + expect(connectionResult).not.toHaveProperty('tokenCredential'); + }); + + it('uses the correct config when using azure client secret credentials', async () => { + const { ClientSecretCredential } = jest.requireMock( + '@azure/identity', + ) as jest.Mocked; + + const tokenExpirationTimestamp = new Date( + '2025-01-01T12:34:56.789', + ).valueOf(); + + ClientSecretCredential.prototype.getToken.mockResolvedValue({ + token: 'afaketoken', + expiresOnTimestamp: tokenExpirationTimestamp, + }); + + const configResult = await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + user: 'some-user@domain.com', + port: 5423, + database: 'other_db', + tokenCredential: { + clientId: 'my-client-id', + tenantId: 'my-tenant-id', + clientSecret: 'my-client-secret', + }, + }, + }), + ); + + expect(ClientSecretCredential).toHaveBeenCalledWith( + 'my-tenant-id', + 'my-client-id', + 'my-client-secret', + ); + expect(configResult).toMatchObject({ + client: 'pg', + connection: expect.any(Function), + useNullAsDefault: true, + }); + + const connectionResult = await configResult.connection(); + + expect(connectionResult).toMatchObject({ + user: 'some-user@domain.com', + password: 'afaketoken', + port: 5423, + expirationChecker: expect.any(Function), + }); + expect(connectionResult).not.toHaveProperty('type'); + expect(connectionResult).not.toHaveProperty('tokenCredential'); + }); + + it('removes tokenCredential from the final connection', async () => { + const { DefaultAzureCredential } = jest.requireMock( + '@azure/identity', + ) as jest.Mocked; + DefaultAzureCredential.prototype.getToken.mockResolvedValue({ + token: 't', + expiresOnTimestamp: Date.now() + 1000, + }); + + const config = new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + instance: 'unused', + tokenCredential: { clientId: 'x' }, + }, + }); + + const configResult = await buildPgDatabaseConfig(config); + const connection = await configResult.connection(); + + expect(connection).not.toHaveProperty('tokenCredential'); + }); + + it('instructs knex to get a new connection object when the old azure token expires', async () => { + const { DefaultAzureCredential } = jest.requireMock( + '@azure/identity', + ) as jest.Mocked; + + const tokenExpirationTimestamp = new Date( + '2025-01-01T12:34:56.789', + ).valueOf(); + + DefaultAzureCredential.prototype.getToken.mockResolvedValue({ + token: 'afaketoken', + expiresOnTimestamp: tokenExpirationTimestamp, + }); + + let configResult = await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + tokenCredential: { + tokenRenewableOffsetTime: '1 minute', + }, + user: 'some-user@domain.com', + database: 'other_db', + port: 5423, + }, + }), + ); + + let connectionResult = await configResult.connection(); + + jest.useFakeTimers({ now: tokenExpirationTimestamp - 90_000 }); + let expirationResult = await connectionResult.expirationChecker(); + expect(expirationResult).toBe(false); + + jest.useFakeTimers({ now: tokenExpirationTimestamp - 60_000 }); + expirationResult = await connectionResult.expirationChecker(); + expect(expirationResult).toBe(true); + + jest.useFakeTimers({ now: tokenExpirationTimestamp }); + expirationResult = await connectionResult.expirationChecker(); + expect(expirationResult).toBe(true); + + // Check the default tokenRenewableOffsetTime of 5 minutes + configResult = await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + user: 'user@contoso.com', + database: 'other_db', + port: 5423, + }, + }), + ); + + connectionResult = await configResult.connection(); + jest.useFakeTimers({ now: tokenExpirationTimestamp - 450_000 }); + expirationResult = await connectionResult.expirationChecker(); + expect(expirationResult).toBe(false); + + jest.useFakeTimers({ now: tokenExpirationTimestamp - 300_000 }); + expirationResult = await connectionResult.expirationChecker(); + expect(expirationResult).toBe(true); + + jest.useFakeTimers({ now: tokenExpirationTimestamp }); + expirationResult = await connectionResult.expirationChecker(); + expect(expirationResult).toBe(true); + }); + + it('throws an error when Azure token acquisition fails', async () => { + const { DefaultAzureCredential } = jest.requireMock( + '@azure/identity', + ) as jest.Mocked; + + DefaultAzureCredential.prototype.getToken.mockResolvedValue(null as any); + + const configResult = await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'azure', + user: 'some-user@domain.com', + database: 'other_db', + }, + }), + ); + + await expect(configResult.connection()).rejects.toThrow( + 'Failed to acquire Azure access token for database authentication', + ); + }); + it('uses the correct config when using cloudsql', async () => { expect( await buildPgDatabaseConfig( @@ -251,6 +517,47 @@ describe('postgres', () => { }); }); + it('passes configured ipType to connector.getOptions', async () => { + const { Connector } = jest.requireMock( + '@google-cloud/cloud-sql-connector', + ) as jest.Mocked; + + const mockStream = (): any => {}; + Connector.prototype.getOptions.mockResolvedValue({ stream: mockStream }); + + await buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'cloudsql', + instance: 'proj:region:inst', + ipAddressType: 'PUBLIC', + }, + }), + ); + + expect(Connector.prototype.getOptions).toHaveBeenCalledWith({ + authType: 'IAM', + instanceConnectionName: 'proj:region:inst', + ipType: 'PUBLIC', + }); + }); + + it('throws when connection.ipAddressType is invalid', async () => { + await expect( + buildPgDatabaseConfig( + new ConfigReader({ + client: 'pg', + connection: { + type: 'cloudsql', + instance: 'proj:region:inst', + ipAddressType: 'INVALID', + }, + }), + ), + ).rejects.toThrow(/Invalid connection.ipAddressType/); + }); + it('passes ip settings to cloud-sql-connector', async () => { const { Connector } = jest.requireMock( '@google-cloud/cloud-sql-connector', diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 380ff2bd0c..afc2125c00 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -15,9 +15,17 @@ */ import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import { + Config, + ConfigReader, + readDurationFromConfig, +} from '@backstage/config'; import { ForwardedError } from '@backstage/errors'; -import { JsonObject } from '@backstage/types'; +import { + durationToMilliseconds, + HumanDuration, + JsonObject, +} from '@backstage/types'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import limiterFactory from 'p-limit'; @@ -25,6 +33,7 @@ import { Client } from 'pg'; import { Connector } from '../types'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; import format from 'pg-format'; +import { TokenCredential } from '@azure/identity'; // Limits the number of concurrent DDL operations to 1 const ddlLimiter = limiterFactory(1); @@ -74,26 +83,142 @@ export async function buildPgDatabaseConfig( }, overrides, ); - - const sanitizedConfig = JSON.parse(JSON.stringify(config)); - - // Trim additional properties from the connection object passed to knex - delete sanitizedConfig.connection.type; - delete sanitizedConfig.connection.instance; + const mergedConfigReader = new ConfigReader(config); if (config.connection.type === 'default' || !config.connection.type) { - return sanitizedConfig; + const connectionValue = config.connection; + const sanitizedConnection = + typeof connectionValue === 'string' || connectionValue instanceof String + ? connectionValue + : // connection is an object, omit config-only props + omit(connectionValue as Record, [ + 'type', + 'instance', + 'tokenCredential', + ]); + + return { + ...config, + connection: sanitizedConnection, + }; } - if (config.connection.type !== 'cloudsql') { - throw new Error(`Unknown connection type: ${config.connection.type}`); + switch (config.connection.type) { + case 'azure': + return buildAzurePgConfig(mergedConfigReader); + case 'cloudsql': + return buildCloudSqlConfig(mergedConfigReader); + default: + throw new Error(`Unknown connection type: ${config.connection.type}`); + } +} + +/* Note: the following type definition is intentionally duplicated in + * /packages/backend-defaults/config.d.ts so the clientSecret property + * can be annotated with "@visibility secret" there. + */ +export type AzureTokenCredentialConfig = { + /** + * How early before an access token expires to refresh it with a new one. + * Defaults to 5 minutes + * Supported formats: + * - A string in the format of '1d', '2 seconds' etc. as supported by the `ms` + * library. + * - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'. + * - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`. + */ + tokenRenewableOffsetTime?: string | HumanDuration; + /** + * The client ID of a user-assigned managed identity. + * If not provided, the system-assigned managed identity is used. + */ + clientId?: string; + clientSecret?: string; + tenantId?: string; +}; + +export async function buildAzurePgConfig(config: Config): Promise { + const { + DefaultAzureCredential, + ManagedIdentityCredential, + ClientSecretCredential, + } = require('@azure/identity'); + + const tokenConfig = config.getOptionalConfig('connection.tokenCredential'); + + const tokenRenewableOffsetTime = durationToMilliseconds( + tokenConfig?.has('tokenRenewableOffsetTime') + ? readDurationFromConfig(tokenConfig, { key: 'tokenRenewableOffsetTime' }) + : { minutes: 5 }, + ); + + const clientId = tokenConfig?.getOptionalString('clientId'); + const tenantId = tokenConfig?.getOptionalString('tenantId'); + const clientSecret = tokenConfig?.getOptionalString('clientSecret'); + let credential: TokenCredential; + + /** + * Determine which TokenCredential to use based on provided config + * 1. If clientId, tenantId and clientSecret are provided, use ClientSecretCredential + * 2. If only clientId is provided, use ManagedIdentityCredential with user-assigned identity + * 3. Otherwise, use DefaultAzureCredential (which may use system-assigned identity among other methods) + */ + if (clientId && tenantId && clientSecret) { + credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + } else if (clientId) { + credential = new ManagedIdentityCredential(clientId); + } else { + credential = new DefaultAzureCredential(); } - if (config.client !== 'pg') { + const rawConfig = config.get() as Record; + + const normalized = normalizeConnection(rawConfig.connection as any); + const sanitizedConnection = omit(normalized, [ + 'type', + 'instance', + 'tokenCredential', + ]) as Partial; + + async function getConnectionConfig() { + const token = await credential.getToken( + 'https://ossrdbms-aad.database.windows.net/.default', + ); + + if (!token) { + throw new Error( + 'Failed to acquire Azure access token for database authentication', + ); + } + + const connectionConfig = { + ...sanitizedConnection, + password: token.token, + expirationChecker: () => + /* return true if the token is within the renewable offset time */ + token.expiresOnTimestamp - tokenRenewableOffsetTime <= Date.now(), + }; + + return connectionConfig; + } + + return { + ...(rawConfig as Record), + connection: getConnectionConfig, + }; +} + +export async function buildCloudSqlConfig( + config: Config, +): Promise { + const client = config.getOptionalString('client'); + + if (client && client !== 'pg') { throw new Error('Cloud SQL only supports the pg client'); } - if (!config.connection.instance) { + const instance = config.getOptionalString('connection.instance'); + if (!instance) { throw new Error('Missing instance connection name for Cloud SQL'); } @@ -103,17 +228,44 @@ export async function buildPgDatabaseConfig( AuthTypes, } = require('@google-cloud/cloud-sql-connector') as typeof import('@google-cloud/cloud-sql-connector'); const connector = new CloudSqlConnector(); + + type IpType = (typeof IpAddressTypes)[keyof typeof IpAddressTypes]; + const ipTypeRaw = config.getOptionalString('connection.ipAddressType'); + + let ipType: IpType | undefined; + if (ipTypeRaw !== undefined) { + if ( + !(Object.values(IpAddressTypes) as Array).includes( + ipTypeRaw as any, + ) + ) { + throw new Error( + `Invalid connection.ipAddressType: ${ipTypeRaw}; valid values: ${Object.values( + IpAddressTypes, + ).join(', ')}`, + ); + } + ipType = ipTypeRaw as unknown as IpType; + } + const clientOpts = await connector.getOptions({ - instanceConnectionName: config.connection.instance, - ipType: config.connection.ipAddressType ?? IpAddressTypes.PUBLIC, + instanceConnectionName: instance, + ipType: ipType ?? IpAddressTypes.PUBLIC, authType: AuthTypes.IAM, }); + const rawConfig = config.get() as Record; + const normalized = normalizeConnection(rawConfig.connection as any); + const sanitizedConnection = omit(normalized, [ + 'type', + 'instance', + ]) as Partial; + return { - ...sanitizedConfig, + ...(rawConfig as Record), client: 'pg', connection: { - ...sanitizedConfig.connection, + ...sanitizedConnection, ...clientOpts, }, }; diff --git a/packages/backend-dynamic-feature-service/README.md b/packages/backend-dynamic-feature-service/README.md index 6f5af13636..8122810f96 100644 --- a/packages/backend-dynamic-feature-service/README.md +++ b/packages/backend-dynamic-feature-service/README.md @@ -1,47 +1,170 @@ # @backstage/backend-dynamic-feature-service -This package adds experimental support for **dynamic backend features (plugins and modules)**, according to the content of the proposal in [RFC 18390](https://github.com/backstage/backstage/issues/18390) +This package provides experimental support for **dynamic backend features (plugins and modules)** in Backstage, allowing you to load plugins at runtime from a separate directory without including them in your main application's package.json. -## Testing the backend dynamic plugins feature +## Purpose -In order to test the dynamic backend plugins feature provided by this package, example applications, as well as example dynamic plugins have been provided in provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase), and instructions are provided in the [related Readme](https://github.com/janus-idp/dynamic-backend-plugins-showcase#readme). +This enables: + +- **Plugin distribution**: Distributing plugins as standalone artifacts +- **Runtime flexibility**: Adding or updating plugins without rebuilding the entire application +- **Isolation**: Keeping plugin-specific dependencies separate from core application dependencies +- **Modular deployments**: Different environments can load different sets of plugins + +**Important:** This service handles loading only - packaging and distribution are separate concerns with multiple approaches available (see Packaging Approaches section). + +## Related Packages + +- **`@backstage/frontend-dynamic-feature-loader`**: Companion package for loading dynamic frontend plugins. This is **only** supported in the New Frontend System. For more details, checkout the [README](../frontend-dynamic-feature-loader/README.md). ## How it works -The dynamic plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically. +### Core Architecture -In the `backend` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: +The service consists of several key components: -```ts -const backend = createBackend(); -+ -+ backend.add(dynamicPluginsFeatureLoader) // provides features loaded by dynamic plugins -+ +1. **Plugin Scanner**: Scans a configured directory (`dynamicPlugins.rootDirectory`) for plugin packages +2. **Module Loader**: Loads plugin modules +3. **Feature Loader**: Integrates loaded plugins into the Backstage backend system +4. **Frontend Asset Server**: Serves frontend plugin assets (JavaScript, CSS, manifests) over HTTP for dynamic frontend plugins + +### Loading Process + +1. **Discovery**: The scanner identifies valid plugin packages in the dynamic plugins root directory +2. **Validation**: Each package is validated for required fields (`main`, `backstage.role`) +3. **Loading**: The module loader loads the plugin code +4. **Integration**: Plugins are registered as Backstage features and made available to the backend + +## Usage + +### Basic Setup + +Add the service to your backend application: + +1. **Install the package**: + +```bash +yarn add @backstage/backend-dynamic-feature-service ``` -### About the expected dynamic plugin structure +2. **Add one line to your backend**: -Due to some limitations of the current backstage codebase, the plugins need to be completed and repackaged to by used as dynamic plugins: +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { dynamicPluginsFeatureLoader } from '@backstage/backend-dynamic-feature-service'; -1. they must provide a named entry point (`dynamicPluginInstaller`) of a specific type (`BackendDynamicPluginInstaller`), as can be found in the `src/dynamic` sub-folder of each dynamic plugin example provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase). -2. they would have a modified `package.json` file in which dependencies are updated to share `@backstage` dependencies with the main application. -3. they may embed some dependency whose code is then merged with the plugin code. +const backend = createBackend(); +// Add this line to enable dynamic plugin support: +backend.add(dynamicPluginsFeatureLoader); +// ... your other plugins +backend.start(); +``` -Points 2 and 3 can be done by the `export-dynamic-plugin` CLI command used to perform the repackaging +### Configuration -### About the `export-dynamic-plugin` command +Configure the dynamic plugins directory in your `app-config.yaml`: -The `export-dynamic-plugin` CLI command, used the dynamic plugin examples provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase), is part of the `@janus-idp/cli` package, and can be used to help packaging the dynamic plugins according to the constraints mentioned above, in order to allow straightforward testing of the dynamic plugins feature. +```yaml +dynamicPlugins: + rootDirectory: dynamic-plugins-root +``` -However the `backend-dynamic-feature-service` experimental package does **not** depend on the use of this additional CLI command, and in future steps of this backend dynamic plugin work, the use of such a dedicated command might not even be necessary. +## Dynamic Plugin Packaging -### About the support of the legacy backend system +### Package Structure Requirements -The backend dynamic plugins feature clearly targets the new backend system. -However some level of compatibility is provided with current backstage codebase, which still uses the legacy backend system, in order to allow testing and exploring dynamic backend plugin support on the widest range of contexts and installations. -However, this is temporary and will be removed once the next backend is ready to be used and has completely replaced the old one. -This is why the API related to the old backend is already marked as deprecated. +Before exploring specific packaging approaches, it's important to understand what constitutes a valid dynamic plugin package. Dynamic plugins follow the basic rules of Node.js packages, including: -### Future work +**Package contents:** -The current implementation of the dynamic plugin manager is a first step towards the final implementation of the dynamic features loading, which will be completed / simplified in future steps, as the status of the backstage codebase allows it. +- **package.json**: Package metadata and configuration +- **JavaScript files**: Either loadable by the Node.js CommonJS module loaders for backend plugins, or RSPack/webpack assets for frontend plugins +- **Configuration schema** (optional): JSON schema file (typically `dist/.config-schema.json`) for plugin configuration validation +- **Private dependencies** (optional): Embedded `node_modules` folder containing private dependencies for backend plugins + +**Required package.json fields:** + +- `name`: Package identifier +- `version`: Package version +- `main`: Entry point to the plugin's JavaScript code +- `backstage.role`: Must be set to `"backend-plugin"` or `"backend-plugin-module"` + +### Packaging Approaches + +Since this service only handles loading, you would choose a packaging approach based on your plugin's dependencies: + +### 1. Simple npm pack + +**When to use:** Plugin only uses dependencies that are already provided by the main Backstage application. + +**How to apply:** + +```bash +cd my-backstage-plugin +yarn pack +# Results in: package.tgz + +# Extract to dynamic plugins directory +mkdir -p /path/to/dynamic-plugins-root/my-backstage-plugin +tar -xzf package.tgz -C /path/to/dynamic-plugins-root/my-backstage-plugin --strip-components=1 +``` + +**Why this works:** The plugin can resolve all its dependencies from the main application's `node_modules`. + +**Reality:** Most plugins have private dependencies not available in the main application, so this approach has limited applicability. + +### 2. Manual packaging with dependency installation + +**When to use:** Plugin has private dependencies not available in the main Backstage application. + +**How to apply:** + +```bash +# Package the plugin +cd my-backstage-plugin +yarn pack + +# Extract and install dependencies +mkdir -p /path/to/dynamic-plugins-root/my-backstage-plugin +tar -xzf package.tgz -C /path/to/dynamic-plugin-root/my-backstage-plugin --strip-components=1 +cp yarn.lock /path/to/dynamic-plugin-root/my-backstage-plugin +cd /path/to/dynamic-plugins-root/my-backstage-plugin +yarn install # Installs all the plugin's dependencies +``` + +**Why this works:** Each plugin gets its own `node_modules` directory with all its dependencies. + +**Example scenario:** Plugin needs `axios@1.4.0` which isn't available in the main application. + +### 3. Custom packaging CLI tool + +**When to use:** When you want to produce self-contained dynamic plugin packages that can be directly extracted without any post-action, and systematically use the core `@backstage` dependencies provided by the Backstage application. + +**What a packaging CLI needs to do:** + +1. **Analyze plugin dependencies** - Identify which are Backstage core vs private dependencies +2. **Create distribution package** - Generate a new directory with modified structure: + - Move `@backstage/*` packages from `dependencies` to `peerDependencies` in package.json + - Keep only private dependencies in the `dependencies` section + - Keep the built JavaScript code unchanged + - Include only the filtered private dependencies in `node_modules` +3. **Result** - A self-contained package that uses the main app's `@backstage/*` packages but includes its own private dependencies + +**Benefits:** + +- Systematic use of main application's `@backstage/*` packages (no version conflicts), enabling the future implementation of `@backstage` dependency version checking at start time +- Self-contained packages with only necessary private dependencies +- No post-installation steps required (extract and run) +- Consistent dependency structure across all dynamic plugins +- Production-ready distribution format + +**Example implementation:** The [`@red-hat-developer-hub/cli`](https://github.com/redhat-developer/rhdh-cli) tool implements this approach: + +```bash +cd my-backstage-plugin +npx @red-hat-developer-hub/cli@latest plugin export +# Creates a self-contained package with embedded dependencies in the `/dist-dynamic` sub-folder + +# Deploy the generated package +cp -r dist-dynamic /path/to/dynamic-plugins-root/my-backstage-plugin +``` diff --git a/packages/backend-dynamic-feature-service/src/schemas/frontend.ts b/packages/backend-dynamic-feature-service/src/schemas/frontend.ts index d101487318..bb83898f8c 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/frontend.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/frontend.ts @@ -31,7 +31,7 @@ import { */ export const dynamicPluginsFrontendSchemas = createBackendModule({ pluginId: 'app', - moduleId: 'core.dynamicplugins.frontendSchemas', + moduleId: 'core-dynamicplugins-frontendSchemas', register(reg) { reg.registerInit({ deps: { diff --git a/packages/backend-plugin-api/src/wiring/constants.ts b/packages/backend-plugin-api/src/wiring/constants.ts new file mode 100644 index 0000000000..48f8e5a5c7 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/constants.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// NOTE: changing any of these constants need to be reflected in +// @backstage/frontend-plugin-api/src/wiring/constants.ts as well + +/** + * The pattern that IDs must match. + * + * @remarks + * ids must only contain the letters `a` through `z` and digits, in groups separated by + * dashes. Additionally, the very first character of the first group + * must be a letter, not a digit + * + * @public + */ +export const ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/i; +export const ID_PATTERN_OLD = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i; diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts index a59a945641..38be8d9d72 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts @@ -15,6 +15,7 @@ */ import { createServiceRef } from '../services'; +import { ID_PATTERN } from './constants'; import { createBackendModule } from './createBackendModule'; import { createExtensionPoint } from './createExtensionPoint'; import { InternalBackendRegistrations } from './types'; @@ -77,4 +78,20 @@ describe('createBackendModule', () => { expect(plugin.$$type).toEqual('@backstage/BackendFeature'); }); + it('should reject modules with invalid moduleId', async () => { + expect(() => + createBackendModule({ + pluginId: 'test', + moduleId: 'invalid:module&id', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ).toThrow( + `Invalid moduleId 'invalid:module&id' for plugin 'test', must match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + }); }); diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.ts index 284125c676..e72341479d 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendModule.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.ts @@ -15,6 +15,7 @@ */ import { BackendFeature } from '../types'; +import { ID_PATTERN, ID_PATTERN_OLD } from './constants'; import { BackendModuleRegistrationPoints, ExtensionPoint, @@ -55,6 +56,17 @@ export interface CreateBackendModuleOptions { export function createBackendModule( options: CreateBackendModuleOptions, ): BackendFeature { + if (!ID_PATTERN.test(options.moduleId)) { + console.warn( + `WARNING: The moduleId '${options.moduleId}' for plugin '${options.pluginId}', will be invalid soon, please change it to match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + } + if (!ID_PATTERN_OLD.test(options.moduleId)) { + throw new Error( + `Invalid moduleId '${options.moduleId}' for plugin '${options.pluginId}', must match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + } + function getRegistrations() { const extensionPoints: InternalBackendModuleRegistrationV1_1['extensionPoints'] = []; diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts index 391b5e47e5..8267a74465 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts @@ -15,6 +15,7 @@ */ import { createServiceRef } from '../services'; +import { ID_PATTERN } from './constants'; import { createBackendPlugin } from './createBackendPlugin'; import { createExtensionPoint } from './createExtensionPoint'; import { InternalBackendRegistrations } from './types'; @@ -90,4 +91,19 @@ describe('createBackendPlugin', () => { expect(plugin.$$type).toEqual('@backstage/BackendFeature'); }); + it('should reject plugins with invalid pluginId', async () => { + expect(() => + createBackendPlugin({ + pluginId: 'test:invalid&id', + register(reg) { + reg.registerInit({ + deps: {}, + async init() {}, + }); + }, + }), + ).toThrow( + `Invalid pluginId 'test:invalid&id', must match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + }); }); diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts index 7cdc985dd7..3378c17b01 100644 --- a/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts @@ -22,6 +22,7 @@ import { InternalBackendPluginRegistrationV1_1, InternalBackendRegistrations, } from './types'; +import { ID_PATTERN, ID_PATTERN_OLD } from './constants'; /** * The configuration options passed to {@link createBackendPlugin}. @@ -50,6 +51,17 @@ export interface CreateBackendPluginOptions { export function createBackendPlugin( options: CreateBackendPluginOptions, ): BackendFeature { + if (!ID_PATTERN.test(options.pluginId)) { + console.warn( + `WARNING: The pluginId '${options.pluginId}' will be invalid soon, please change it to match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + } + if (!ID_PATTERN_OLD.test(options.pluginId)) { + throw new Error( + `Invalid pluginId '${options.pluginId}', must match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + } + function getRegistrations() { const extensionPoints: InternalBackendPluginRegistrationV1_1['extensionPoints'] = []; diff --git a/packages/frontend-plugin-api/src/wiring/constants.ts b/packages/frontend-plugin-api/src/wiring/constants.ts new file mode 100644 index 0000000000..5300cfc27f --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/constants.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// NOTE: changing any of these constants need to be reflected in +// @backstage/backend-plugin-api/src/wiring/constants.ts as well + +/** + * The pattern that IDs must match. + * + * @remarks + * ids must only contain the letters `a` through `z` and digits, in groups separated by + * dashes. Additionally, the very first character of the first group + * must be a letter, not a digit + * + * @public + */ +export const ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/i; diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts index a3226721a1..1e1f7e1a84 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts @@ -142,6 +142,17 @@ describe('createFrontendPlugin', () => { expect(String(plugin)).toBe('Plugin{id=test}'); }); + it('should warn about invalid plugin IDs', () => { + const consoleWarn = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + createFrontendPlugin({ pluginId: 'invalid&id' }); + expect(consoleWarn).toHaveBeenCalledWith( + expect.stringContaining("The pluginId 'invalid&id' will be invalid soon"), + ); + consoleWarn.mockRestore(); + }); + it('should create a plugin with extension instances', async () => { const plugin = createFrontendPlugin({ pluginId: 'test', diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 18fd52162a..62fd1e88ff 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -30,6 +30,7 @@ import { FeatureFlagConfig } from './types'; import { MakeSortedExtensionsMap } from './MakeSortedExtensionsMap'; import { JsonObject } from '@backstage/types'; import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; +import { ID_PATTERN } from './constants'; /** * Information about the plugin. @@ -208,6 +209,13 @@ export function createFrontendPlugin< > { const pluginId = options.pluginId; + if (!ID_PATTERN.test(pluginId)) { + // eslint-disable-next-line no-console + console.warn( + `WARNING: The pluginId '${pluginId}' will be invalid soon, please change it to match the pattern ${ID_PATTERN} (letters, digits, and dashes only, starting with a letter)`, + ); + } + const extensions = new Array>(); const extensionDefinitionsById = new Map< string, diff --git a/packages/integration/src/azure/AzureUrl.test.ts b/packages/integration/src/azure/AzureUrl.test.ts index a56f800133..7382476da4 100644 --- a/packages/integration/src/azure/AzureUrl.test.ts +++ b/packages/integration/src/azure/AzureUrl.test.ts @@ -27,6 +27,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-project'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBeUndefined(); + expect(url.getPrefix()).toBeUndefined(); expect(url.toRepoUrl()).toBe( 'https://dev.azure.com/my-org/_git/my-project', @@ -52,6 +53,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-project'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBe('/test.yaml'); + expect(url.getPrefix()).toBeUndefined(); expect(url.toRepoUrl()).toBe( 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml', @@ -67,7 +69,7 @@ describe('AzureUrl', () => { ); }); - it('should work with the short URL form with a path and ref', () => { + it('should work with the short URL form with a path and branch ref', () => { const url = AzureUrl.fromRepoUrl( 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GBtest-branch', ); @@ -77,6 +79,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-project'); expect(url.getRef()).toBe('test-branch'); expect(url.getPath()).toBe('/test.yaml'); + expect(url.getPrefix()).toBe('GB'); expect(url.toRepoUrl()).toBe( 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GBtest-branch', @@ -92,6 +95,32 @@ describe('AzureUrl', () => { ); }); + it('should work with the short URL form with a path and tag ref', () => { + const url = AzureUrl.fromRepoUrl( + 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GTtest-tag', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-project'); + expect(url.getRef()).toBe('test-tag'); + expect(url.getPath()).toBe('/test.yaml'); + expect(url.getPrefix()).toBe('GT'); + + expect(url.toRepoUrl()).toBe( + 'https://dev.azure.com/my-org/_git/my-project?path=%2Ftest.yaml&version=GTtest-tag', + ); + expect(url.toFileUrl()).toBe( + 'https://dev.azure.com/my-org/my-project/_apis/git/repositories/my-project/items?api-version=6.0&path=%2Ftest.yaml&version=test-tag', + ); + expect(url.toArchiveUrl()).toBe( + 'https://dev.azure.com/my-org/my-project/_apis/git/repositories/my-project/items?recursionLevel=full&download=true&api-version=6.0&scopePath=%2Ftest.yaml&version=test-tag', + ); + expect(url.toCommitsUrl()).toBe( + 'https://dev.azure.com/my-org/my-project/_apis/git/repositories/my-project/commits?api-version=6.0&searchCriteria.itemVersion.version=test-tag', + ); + }); + it('should work with the long URL', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/my-org/my-project/_git/my-repo', @@ -102,6 +131,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBeUndefined(); + expect(url.getPrefix()).toBeUndefined(); expect(url.toRepoUrl()).toBe( 'http://my-host/my-org/my-project/_git/my-repo', @@ -117,7 +147,7 @@ describe('AzureUrl', () => { ); }); - it('should work with the long URL form with a path and ref', () => { + it('should work with the long URL form with a path and branch ref', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', ); @@ -127,6 +157,7 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBe('test-branch'); expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GB'); expect(url.toRepoUrl()).toBe( 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', @@ -142,6 +173,32 @@ describe('AzureUrl', () => { ); }); + it('should work with the long URL form with a path and tag ref', () => { + const url = AzureUrl.fromRepoUrl( + 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GTtest-tag', + ); + + expect(url.getOwner()).toBe('my-org'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBe('test-tag'); + expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GT'); + + expect(url.toRepoUrl()).toBe( + 'http://my-host/my-org/my-project/_git/my-repo?path=%2Ffolder&version=GTtest-tag', + ); + expect(url.toFileUrl()).toBe( + 'http://my-host/my-org/my-project/_apis/git/repositories/my-repo/items?api-version=6.0&path=%2Ffolder&version=test-tag', + ); + expect(url.toArchiveUrl()).toBe( + 'http://my-host/my-org/my-project/_apis/git/repositories/my-repo/items?recursionLevel=full&download=true&api-version=6.0&scopePath=%2Ffolder&version=test-tag', + ); + expect(url.toCommitsUrl()).toBe( + 'http://my-host/my-org/my-project/_apis/git/repositories/my-repo/commits?api-version=6.0&searchCriteria.itemVersion.version=test-tag', + ); + }); + it('should work with the old tfs long URL', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/tfs/projects/my-project/_git/my-repo', @@ -152,9 +209,10 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBeUndefined(); expect(url.getPath()).toBeUndefined(); + expect(url.getPrefix()).toBeUndefined(); }); - it('should work with the old tfs long URL form with a path and ref', () => { + it('should work with the old tfs long URL form with a path and branch ref', () => { const url = AzureUrl.fromRepoUrl( 'http://my-host/tfs/projects/my-project/_git/my-repo?path=%2Ffolder&version=GBtest-branch', ); @@ -164,14 +222,28 @@ describe('AzureUrl', () => { expect(url.getRepo()).toBe('my-repo'); expect(url.getRef()).toBe('test-branch'); expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GB'); }); - it('should reject non-branch refs', () => { + it('should work with the old tfs long URL form with a path and tag ref', () => { + const url = AzureUrl.fromRepoUrl( + 'http://my-host/tfs/projects/my-project/_git/my-repo?path=%2Ffolder&version=GTtest-tag', + ); + + expect(url.getOwner()).toBe('tfs/projects'); + expect(url.getProject()).toBe('my-project'); + expect(url.getRepo()).toBe('my-repo'); + expect(url.getRef()).toBe('test-tag'); + expect(url.getPath()).toBe('/folder'); + expect(url.getPrefix()).toBe('GT'); + }); + + it('should reject non-branch and non-tag refs', () => { expect(() => AzureUrl.fromRepoUrl( 'https://dev.azure.com/my-org/_git/my-project?version=GC6eead79870d998a3befd4bc7c72cc89e446f2970', ), - ).toThrow('Azure URL version must point to a git branch'); + ).toThrow('Azure URL version must point to a git branch or git tag'); }); it('should reject non-repo URLs', () => { diff --git a/packages/integration/src/azure/AzureUrl.ts b/packages/integration/src/azure/AzureUrl.ts index 1161ab7a1b..a271403554 100644 --- a/packages/integration/src/azure/AzureUrl.ts +++ b/packages/integration/src/azure/AzureUrl.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -const VERSION_PREFIX_GIT_BRANCH = 'GB'; - export class AzureUrl { /** * Parses an azure URL as copied from the browser address bar. @@ -50,16 +48,19 @@ export class AzureUrl { const path = url.searchParams.get('path') ?? undefined; let ref; + let prefix; const version = url.searchParams.get('version'); if (version) { - const prefix = version.slice(0, 2); - if (prefix !== 'GB') { - throw new Error('Azure URL version must point to a git branch'); + prefix = version.slice(0, 2); + if (prefix !== 'GB' && prefix !== 'GT') { + throw new Error( + 'Azure URL version must point to a git branch or git tag', + ); } ref = version.slice(2); } - return new AzureUrl(url.origin, owner, project, repo, path, ref); + return new AzureUrl(url.origin, owner, project, repo, path, ref, prefix); } #origin: string; @@ -68,6 +69,7 @@ export class AzureUrl { #repo: string; #path?: string; #ref?: string; + #prefix?: string; private constructor( origin: string, @@ -76,6 +78,7 @@ export class AzureUrl { repo: string, path?: string, ref?: string, + prefix?: string, ) { this.#origin = origin; this.#owner = owner; @@ -83,6 +86,7 @@ export class AzureUrl { this.#repo = repo; this.#path = path; this.#ref = ref; + this.#prefix = prefix; } #baseUrl = (...parts: string[]): URL => { @@ -108,7 +112,7 @@ export class AzureUrl { url.searchParams.set('path', this.#path); } if (this.#ref) { - url.searchParams.set('version', VERSION_PREFIX_GIT_BRANCH + this.#ref); + url.searchParams.set('version', this.#prefix + this.#ref); } return url.toString(); @@ -232,4 +236,11 @@ export class AzureUrl { getRef(): string | undefined { return this.#ref; } + + /** + * Returns the git prefix in the repo if the URL contains one. + */ + getPrefix(): string | undefined { + return this.#prefix; + } } diff --git a/packages/ui/css/styles.css b/packages/ui/css/styles.css index 1564855309..7538f53bbe 100644 --- a/packages/ui/css/styles.css +++ b/packages/ui/css/styles.css @@ -81,17 +81,17 @@ --bui-bg-info: #dbeafe; --bui-fg-primary: var(--bui-black); --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: #1f5493; - --bui-fg-link-hover: #1f2d5c; --bui-fg-disabled: #9e9e9e; --bui-fg-solid: var(--bui-white); --bui-fg-solid-disabled: #98a8bc; - --bui-fg-tint: #1f5493; - --bui-fg-tint-disabled: var(--bui-gray-5); - --bui-fg-danger: #991919; - --bui-fg-warning: #92310a; - --bui-fg-success: #116932; - --bui-fg-info: #173da6; + --bui-fg-danger-on-bg: #991919; + --bui-fg-warning-on-bg: #92310a; + --bui-fg-success-on-bg: #116932; + --bui-fg-info-on-bg: #173da6; + --bui-fg-danger: #ec3b18; + --bui-fg-warning: #ef7a32; + --bui-fg-success: #1ed760; + --bui-fg-info: #0d74ce; --bui-border: #0000001a; --bui-border-hover: #0003; --bui-border-pressed: #0006; @@ -146,17 +146,17 @@ --bui-bg-info: #132049; --bui-fg-primary: var(--bui-white); --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: #9cc9ff; - --bui-fg-link-hover: #7eb5f7; --bui-fg-disabled: var(--bui-gray-7); --bui-fg-solid: #101821; --bui-fg-solid-disabled: #6191cc; - --bui-fg-tint: #9cc9ff; - --bui-fg-tint-disabled: var(--bui-gray-5); - --bui-fg-danger: #fca5a5; - --bui-fg-warning: #fdba74; - --bui-fg-success: #86efac; - --bui-fg-info: #a3cfff; + --bui-fg-danger-on-bg: #fca5a5; + --bui-fg-warning-on-bg: #fdba74; + --bui-fg-success-on-bg: #86efac; + --bui-fg-info-on-bg: #a3cfff; + --bui-fg-danger: #ff5a30; + --bui-fg-warning: #ffa057; + --bui-fg-success: #1ed760; + --bui-fg-info: #70b8ff; --bui-border: #ffffff1f; --bui-border-hover: #fff6; --bui-border-pressed: #ffffff80; diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index b4211d2499..ae76c8a8d0 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1113,8 +1113,10 @@ export const LinkDefinition: { 'danger', 'warning', 'success', + 'info', ]; readonly truncate: readonly [true, false]; + readonly standalone: readonly [true, false]; }; }; @@ -1128,6 +1130,8 @@ export interface LinkProps extends LinkProps_2 { | TextColorStatus | Partial>; // (undocumented) + standalone?: boolean; + // (undocumented) title?: string; // (undocumented) truncate?: boolean; @@ -1906,7 +1910,7 @@ export { Text_2 as Text }; export type TextColors = 'primary' | 'secondary'; // @public (undocumented) -export type TextColorStatus = 'danger' | 'warning' | 'success'; +export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info'; // @public export const TextDefinition: { @@ -1922,6 +1926,7 @@ export const TextDefinition: { 'danger', 'warning', 'success', + 'info', ]; readonly truncate: readonly [true, false]; }; diff --git a/packages/ui/src/components/Alert/Alert.module.css b/packages/ui/src/components/Alert/Alert.module.css index 643a7d09b0..9c2fc6a586 100644 --- a/packages/ui/src/components/Alert/Alert.module.css +++ b/packages/ui/src/components/Alert/Alert.module.css @@ -36,22 +36,22 @@ .bui-Alert[data-status='info'] { --alert-bg: var(--bui-bg-info); - --alert-fg: var(--bui-fg-info); + --alert-fg: var(--bui-fg-info-on-bg); } .bui-Alert[data-status='success'] { --alert-bg: var(--bui-bg-success); - --alert-fg: var(--bui-fg-success); + --alert-fg: var(--bui-fg-success-on-bg); } .bui-Alert[data-status='warning'] { --alert-bg: var(--bui-bg-warning); - --alert-fg: var(--bui-fg-warning); + --alert-fg: var(--bui-fg-warning-on-bg); } .bui-Alert[data-status='danger'] { --alert-bg: var(--bui-bg-danger); - --alert-fg: var(--bui-fg-danger); + --alert-fg: var(--bui-fg-danger-on-bg); } .bui-AlertIcon { diff --git a/packages/ui/src/components/HeaderPage/HeaderPage.tsx b/packages/ui/src/components/HeaderPage/HeaderPage.tsx index efa7ea8d58..8b1ef31874 100644 --- a/packages/ui/src/components/HeaderPage/HeaderPage.tsx +++ b/packages/ui/src/components/HeaderPage/HeaderPage.tsx @@ -56,6 +56,7 @@ export const HeaderPage = (props: HeaderPageProps) => { color="secondary" truncate style={{ maxWidth: '240px' }} + standalone > {breadcrumb.label} diff --git a/packages/ui/src/components/Link/Link.module.css b/packages/ui/src/components/Link/Link.module.css index a319dfb66b..27ffe244fb 100644 --- a/packages/ui/src/components/Link/Link.module.css +++ b/packages/ui/src/components/Link/Link.module.css @@ -22,9 +22,14 @@ padding: 0; margin: 0; cursor: pointer; - text-decoration-line: none; display: inline-block; + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, 0.05em)); + text-underline-offset: calc(0.025em + 2px); + text-decoration-color: color-mix(in srgb, currentColor 30%, transparent); + &:hover { text-decoration-line: underline; text-decoration-style: solid; @@ -102,9 +107,25 @@ color: var(--bui-fg-success); } + .bui-Link[data-color='info'] { + color: var(--bui-fg-info); + } + .bui-Link[data-truncate] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + + .bui-Link[data-standalone] { + text-decoration-line: none; + + &:hover { + text-decoration-line: underline; + text-decoration-style: solid; + text-decoration-thickness: min(2px, max(1px, 0.05em)); + text-underline-offset: calc(0.025em + 2px); + text-decoration-color: color-mix(in srgb, currentColor 30%, transparent); + } + } } diff --git a/packages/ui/src/components/Link/Link.stories.tsx b/packages/ui/src/components/Link/Link.stories.tsx index 7d680e5616..fafb5948d9 100644 --- a/packages/ui/src/components/Link/Link.stories.tsx +++ b/packages/ui/src/components/Link/Link.stories.tsx @@ -102,6 +102,12 @@ export const AllColors = meta.story({ color="success" children="I am success" /> + ), }); @@ -235,6 +241,25 @@ export const Truncate = meta.story({ }, }); +export const Standalone = meta.story({ + args: { + href: '/', + children: 'Standalone link (no underline by default)', + standalone: true, + }, +}); + +export const StandaloneComparison = meta.story({ + render: () => ( + + Default link (underline by default): + + Standalone link (underline on hover only): + + + ), +}); + export const Responsive = meta.story({ args: { ...Default.input.args, @@ -244,27 +269,3 @@ export const Responsive = meta.story({ }, }, }); - -export const Playground = meta.story({ - args: { - ...Default.input.args, - }, - render: args => ( - - Title X Small - - Body X Small - - Body Small - - Body Medium - - Body Large - - Title Small - - Title Medium - - - ), -}); diff --git a/packages/ui/src/components/Link/Link.tsx b/packages/ui/src/components/Link/Link.tsx index fb9d25e805..efdca26750 100644 --- a/packages/ui/src/components/Link/Link.tsx +++ b/packages/ui/src/components/Link/Link.tsx @@ -44,6 +44,7 @@ const LinkInternal = forwardRef((props, ref) => { weight, color, truncate, + standalone, slot, ...restProps } = cleanedProps; diff --git a/packages/ui/src/components/Link/definition.ts b/packages/ui/src/components/Link/definition.ts index cece929f11..58b780c812 100644 --- a/packages/ui/src/components/Link/definition.ts +++ b/packages/ui/src/components/Link/definition.ts @@ -27,7 +27,15 @@ export const LinkDefinition = { dataAttributes: { variant: ['subtitle', 'body', 'caption', 'label'] as const, weight: ['regular', 'bold'] as const, - color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const, + color: [ + 'primary', + 'secondary', + 'danger', + 'warning', + 'success', + 'info', + ] as const, truncate: [true, false] as const, + standalone: [true, false] as const, }, } as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Link/types.ts b/packages/ui/src/components/Link/types.ts index 796676e593..9259a8a1d7 100644 --- a/packages/ui/src/components/Link/types.ts +++ b/packages/ui/src/components/Link/types.ts @@ -33,6 +33,7 @@ export interface LinkProps extends AriaLinkProps { | TextColorStatus | Partial>; truncate?: boolean; + standalone?: boolean; // This is used to set the title attribute on the link title?: string; diff --git a/packages/ui/src/components/Popover/Popover.module.css b/packages/ui/src/components/Popover/Popover.module.css index fe757c8dd4..5156b95ffc 100644 --- a/packages/ui/src/components/Popover/Popover.module.css +++ b/packages/ui/src/components/Popover/Popover.module.css @@ -122,20 +122,16 @@ } } - [data-theme='dark'] { - .bui-Popover { - background: var(--bui-bg-surface-2); - border: 1px solid var(--bui-gray-4); - } + [data-theme='dark'] .bui-Popover { + background: var(--bui-bg-surface-2); + border: 1px solid var(--bui-gray-4); + } - .bui-PopoverArrow { - svg path:nth-child(1) { - fill: var(--bui-bg-surface-2); - } + [data-theme='dark'] .bui-PopoverArrow svg path:nth-child(1) { + fill: var(--bui-bg-surface-2); + } - svg path:nth-child(2) { - fill: var(--bui-gray-4); - } - } + [data-theme='dark'] .bui-PopoverArrow svg path:nth-child(2) { + fill: var(--bui-gray-4); } } diff --git a/packages/ui/src/components/Text/Text.module.css b/packages/ui/src/components/Text/Text.module.css index 57de6f0ef8..b7b7b9f203 100644 --- a/packages/ui/src/components/Text/Text.module.css +++ b/packages/ui/src/components/Text/Text.module.css @@ -91,6 +91,10 @@ color: var(--bui-fg-success); } + .bui-Text[data-color='info'] { + color: var(--bui-fg-info); + } + .bui-Text[data-truncate] { overflow: hidden; text-overflow: ellipsis; diff --git a/packages/ui/src/components/Text/Text.stories.tsx b/packages/ui/src/components/Text/Text.stories.tsx index 09a317716a..e8dff19318 100644 --- a/packages/ui/src/components/Text/Text.stories.tsx +++ b/packages/ui/src/components/Text/Text.stories.tsx @@ -101,6 +101,7 @@ export const AllColors = meta.story({ + ), }); diff --git a/packages/ui/src/components/Text/definition.ts b/packages/ui/src/components/Text/definition.ts index dbbfd6b72a..ad676405c7 100644 --- a/packages/ui/src/components/Text/definition.ts +++ b/packages/ui/src/components/Text/definition.ts @@ -27,7 +27,14 @@ export const TextDefinition = { dataAttributes: { variant: ['subtitle', 'body', 'caption', 'label'] as const, weight: ['regular', 'bold'] as const, - color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const, + color: [ + 'primary', + 'secondary', + 'danger', + 'warning', + 'success', + 'info', + ] as const, truncate: [true, false] as const, }, } as const satisfies ComponentDefinition; diff --git a/packages/ui/src/components/Tooltip/Tooltip.module.css b/packages/ui/src/components/Tooltip/Tooltip.module.css index 5c9c27eb8d..b668952848 100644 --- a/packages/ui/src/components/Tooltip/Tooltip.module.css +++ b/packages/ui/src/components/Tooltip/Tooltip.module.css @@ -106,21 +106,17 @@ } } - [data-theme='dark'] { - .bui-Tooltip { - background: var(--bui-bg-surface-2); - box-shadow: none; - border: 1px solid var(--bui-gray-4); - } + [data-theme='dark'] .bui-Tooltip { + background: var(--bui-bg-surface-2); + box-shadow: none; + border: 1px solid var(--bui-gray-4); + } - .bui-TooltipArrow { - svg path:nth-child(1) { - fill: var(--bui-bg-surface-2); - } + [data-theme='dark'] .bui-TooltipArrow svg path:nth-child(1) { + fill: var(--bui-bg-surface-2); + } - svg path:nth-child(2) { - fill: var(--bui-gray-4); - } - } + [data-theme='dark'] .bui-TooltipArrow svg path:nth-child(2) { + fill: var(--bui-gray-4); } } diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index 992a49b2f2..9cd6ea98e5 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -122,17 +122,19 @@ /* Foreground colors */ --bui-fg-primary: var(--bui-black); --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: #1f5493; - --bui-fg-link-hover: #1f2d5c; --bui-fg-disabled: #9e9e9e; --bui-fg-solid: var(--bui-white); --bui-fg-solid-disabled: #98a8bc; - --bui-fg-tint: #1f5493; - --bui-fg-tint-disabled: var(--bui-gray-5); - --bui-fg-danger: #991919; - --bui-fg-warning: #92310a; - --bui-fg-success: #116932; - --bui-fg-info: #173da6; + + /* Foreground Statuses */ + --bui-fg-danger-on-bg: #991919; + --bui-fg-warning-on-bg: #92310a; + --bui-fg-success-on-bg: #116932; + --bui-fg-info-on-bg: #173da6; + --bui-fg-danger: #ec3b18; + --bui-fg-warning: #ef7a32; + --bui-fg-success: #1ed760; + --bui-fg-info: #0d74ce; /* Border colors */ --bui-border: rgba(0, 0, 0, 0.1); @@ -210,17 +212,19 @@ /* Foreground colors */ --bui-fg-primary: var(--bui-white); --bui-fg-secondary: var(--bui-gray-7); - --bui-fg-link: #9cc9ff; - --bui-fg-link-hover: #7eb5f7; --bui-fg-disabled: var(--bui-gray-7); --bui-fg-solid: #101821; --bui-fg-solid-disabled: #6191cc; - --bui-fg-tint: #9cc9ff; - --bui-fg-tint-disabled: var(--bui-gray-5); - --bui-fg-danger: #fca5a5; - --bui-fg-warning: #fdba74; - --bui-fg-success: #86efac; - --bui-fg-info: #a3cfff; + + /* Foreground statuses */ + --bui-fg-danger-on-bg: #fca5a5; + --bui-fg-warning-on-bg: #fdba74; + --bui-fg-success-on-bg: #86efac; + --bui-fg-info-on-bg: #a3cfff; + --bui-fg-danger: #ff5a30; + --bui-fg-warning: #ffa057; + --bui-fg-success: #1ed760; + --bui-fg-info: #70b8ff; /* Border colors */ --bui-border: rgba(255, 255, 255, 0.12); diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 0b23dfd10e..933e1d8aa9 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -131,7 +131,7 @@ export type TextVariants = export type TextColors = 'primary' | 'secondary'; /** @public */ -export type TextColorStatus = 'danger' | 'warning' | 'success'; +export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info'; /** @public */ export type TextWeights = 'regular' | 'bold'; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 4d137e524f..584fe79014 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -74,6 +74,7 @@ "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 54086a55a5..d1e13f4ef1 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -14,256 +14,302 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { EntityRefLink } from '../EntityRefLink'; -import Box from '@material-ui/core/Box'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; -import { useCallback, useState } from 'react'; -import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState'; +import { ReactNode, useCallback, useState } from 'react'; +import { + UseUnregisterEntityDialogState, + useUnregisterEntityDialogState, +} from './useUnregisterEntityDialogState'; import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { assertError } from '@backstage/errors'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { + Accordion, + AccordionPanel, + AccordionTrigger, + Alert, + Box, + Button, + Dialog, + DialogBody, + DialogFooter, + DialogHeader, + Text, +} from '@backstage/ui'; const useStyles = makeStyles({ - advancedButton: { - fontSize: '0.7em', - }, - dialogActions: { - display: 'inline-block', + bodyContent: { + overflowWrap: 'break-word', }, }); -const Contents = ({ - entity, - onConfirm, - onClose, -}: { - entity: Entity; - onConfirm: () => any; - onClose: () => any; -}) => { +type DialogHandlers = { + state: UseUnregisterEntityDialogState; + busyAction: 'unregister' | 'delete' | null; + onUnregister: () => Promise; + onDelete: () => Promise; + onClose: () => void; +}; + +function useUnregisterDialogHandlers( + entity: Entity, + onConfirm: () => void, + onClose: () => void, +): DialogHandlers { const alertApi = useApi(alertApiRef); - const configApi = useApi(configApiRef); - const classes = useStyles(); const state = useUnregisterEntityDialogState(entity); - const [showDelete, setShowDelete] = useState(false); - const [busy, setBusy] = useState(false); - const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + const [busyAction, setBusyAction] = useState<'unregister' | 'delete' | null>( + null, + ); const { t } = useTranslationRef(catalogReactTranslationRef); - const onUnregister = useCallback( - async function onUnregisterFn() { - if ('unregisterLocation' in state) { - setBusy(true); - try { - await state.unregisterLocation(); - onConfirm(); - } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); - } finally { - setBusy(false); - } + const onUnregister = useCallback(async () => { + if ('unregisterLocation' in state) { + setBusyAction('unregister'); + try { + await state.unregisterLocation(); + onConfirm(); + } catch (err) { + assertError(err); + alertApi.post({ message: err.message }); + } finally { + setBusyAction(null); } - }, - [alertApi, onConfirm, state], - ); + } + }, [alertApi, onConfirm, state]); - const onDelete = useCallback( - async function onDeleteFn() { - if ('deleteEntity' in state) { - setBusy(true); - try { - await state.deleteEntity(); - const entityName = entity.metadata.title ?? entity.metadata.name; - onConfirm(); - alertApi.post({ - message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { - entityName, - }), - severity: 'success', - display: 'transient', - }); - } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); - } finally { - setBusy(false); - } + const onDelete = useCallback(async () => { + if ('deleteEntity' in state) { + setBusyAction('delete'); + try { + await state.deleteEntity(); + const entityName = entity.metadata.title ?? entity.metadata.name; + onConfirm(); + alertApi.post({ + message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { + entityName, + }), + severity: 'success', + display: 'transient', + }); + } catch (err) { + assertError(err); + alertApi.post({ message: err.message }); + } finally { + setBusyAction(null); } - }, - [alertApi, onConfirm, state, entity, t], - ); + } + }, [alertApi, onConfirm, state, entity, t]); - const DialogActionsPanel = () => ( - - - - ); + return { state, busyAction, onUnregister, onDelete, onClose }; +} - if (state.type === 'loading') { - return ; - } +function AdvancedDeleteAccordion({ + triggerTitle, + description, + onDelete, + busyAction, +}: { + triggerTitle: string; + description: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); - if (state.type === 'error') { - return ; - } - - if (state.type === 'bootstrap') { - return ( - <> - - {t('unregisterEntityDialog.bootstrapState.title', { - appTitle, - location: state.location, - })} - - - - {!showDelete && ( - <> - - - - )} - - {showDelete && ( - <> - - {t('unregisterEntityDialog.bootstrapState.advancedDescription')} - - - - - )} - - - ); - } - - if (state.type === 'only-delete') { - return ( - <> - - {t('unregisterEntityDialog.onlyDeleteStateTitle')} - - - - - ); - } - - if (state.type === 'unregister') { - return ( - <> - - {t('unregisterEntityDialog.unregisterState.title')} - - - {state.colocatedEntities.map(e => ( -
  • - -
  • - ))} -
    - - {t('unregisterEntityDialog.unregisterState.subTitle')} - - -
  • {state.location}
  • -
    - - {t('unregisterEntityDialog.unregisterState.description', { - appTitle, - })} - - - - {!showDelete && ( - - - - )} - - - {showDelete && ( - <> - - - - - {t('unregisterEntityDialog.unregisterState.advancedDescription')} - + return ( + + + + + {description} + - - )} - - ); - } + + + + + ); +} + +function BootstrapBody({ + location, + appTitle, + onDelete, + busyAction, +}: { + location: string; + appTitle: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); return ( - - {t('unregisterEntityDialog.errorStateTitle')} - + <> + + + ); -}; +} + +function OnlyDeleteBody() { + const { t } = useTranslationRef(catalogReactTranslationRef); + + return {t('unregisterEntityDialog.onlyDeleteStateTitle')}; +} + +function UnregisterBody({ + state, + appTitle, + onDelete, + busyAction, +}: { + state: Extract; + appTitle: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); + + return ( + <> + {t('unregisterEntityDialog.unregisterState.title')} +
      + {state.colocatedEntities.map((e: CompoundEntityRef) => ( +
    • + +
    • + ))} +
    + {t('unregisterEntityDialog.unregisterState.subTitle')} +
      +
    • {state.location}
    • +
    + + {t('unregisterEntityDialog.unregisterState.description', { + appTitle, + })} + + + + ); +} + +function useDialogContent( + handlers: DialogHandlers, + appTitle: string, +): { body: ReactNode; actionButton: ReactNode | null } { + const { t } = useTranslationRef(catalogReactTranslationRef); + const { state, busyAction, onUnregister, onDelete } = handlers; + + switch (state.type) { + case 'loading': + return { body: , actionButton: null }; + case 'error': + return { + body: , + actionButton: null, + }; + case 'bootstrap': + return { + body: ( + + ), + actionButton: null, + }; + case 'only-delete': + return { + body: , + actionButton: ( + + ), + }; + case 'unregister': + return { + body: ( + + ), + actionButton: ( + + ), + }; + default: + return { + body: ( + + ), + actionButton: null, + }; + } +} /** @public */ export type UnregisterEntityDialogProps = { @@ -273,18 +319,54 @@ export type UnregisterEntityDialogProps = { entity: Entity; }; +function DialogContents({ + entity, + onConfirm, + onClose, +}: { + entity: Entity; + onConfirm: () => void; + onClose: () => void; +}) { + const classes = useStyles(); + const { t } = useTranslationRef(catalogReactTranslationRef); + const configApi = useApi(configApiRef); + const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + + const handlers = useUnregisterDialogHandlers(entity, onConfirm, onClose); + const { body, actionButton } = useDialogContent(handlers, appTitle); + + return ( + <> + {t('unregisterEntityDialog.title')} + {body} + + + {actionButton} + + + ); +} + /** @public */ export const UnregisterEntityDialog = (props: UnregisterEntityDialogProps) => { const { open, onConfirm, onClose, entity } = props; - const { t } = useTranslationRef(catalogReactTranslationRef); + return ( - - - {t('unregisterEntityDialog.title')} - - - - + !isOpen && onClose()} + width={600} + > + {open && ( + + )} ); }; diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index adfa7e15c0..75ecca801c 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -75,6 +75,7 @@ "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/plugin-techdocs-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index e83089b611..950b7a4383 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -33,10 +33,11 @@ import { TranslationRef } from '@backstage/frontend-plugin-api'; export const catalogTranslationRef: TranslationRef< 'catalog', { - readonly 'deleteEntity.description': 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.'; + readonly 'deleteEntity.description': 'This entity is not referenced by any location and is therefore not receiving updates.'; readonly 'deleteEntity.cancelButtonTitle': 'Cancel'; readonly 'deleteEntity.deleteButtonTitle': 'Delete'; readonly 'deleteEntity.dialogTitle': 'Are you sure you want to delete this entity?'; + readonly 'deleteEntity.actionButtonTitle': 'Delete entity'; readonly 'indexPage.title': '{{orgName}} Catalog'; readonly 'indexPage.createButtonTitle': 'Create'; readonly 'indexPage.supportButtonContent': 'All your software catalog entities'; diff --git a/plugins/catalog/src/alpha/translation.ts b/plugins/catalog/src/alpha/translation.ts index 2dc2f2d34e..498104db45 100644 --- a/plugins/catalog/src/alpha/translation.ts +++ b/plugins/catalog/src/alpha/translation.ts @@ -126,7 +126,8 @@ export const catalogTranslationRef = createTranslationRef({ deleteButtonTitle: 'Delete', cancelButtonTitle: 'Cancel', description: - 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.', + 'This entity is not referenced by any location and is therefore not receiving updates.', + actionButtonTitle: 'Delete entity', }, entityProcessingErrorsDescription: 'The error below originates from', entityRelationWarningDescription: diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 65094bc568..d2216149cd 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -174,7 +174,7 @@ export const columnFactories = Object.freeze({ label={t} size="small" variant="outlined" - style={{ marginBottom: '0px' }} + style={{ margin: '2px' }} /> ))} diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index e435ac2c48..2c350aebc5 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -16,15 +16,12 @@ import { Entity } from '@backstage/catalog-model'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogTitle from '@material-ui/core/DialogTitle'; import { useState } from 'react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { assertError } from '@backstage/errors'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { Button, Dialog, DialogFooter, DialogHeader } from '@backstage/ui'; interface DeleteEntityDialogProps { open: boolean; @@ -55,23 +52,16 @@ export function DeleteEntityDialog(props: DeleteEntityDialogProps) { }; return ( - - - {t('deleteEntity.dialogTitle')} - - - - - + + ); } diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx index bcf75ad08b..0b9f5889a0 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx @@ -61,8 +61,11 @@ describe('', () => { ); expect( screen.getByText( - 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.', + 'This entity is not referenced by any location and is therefore not receiving updates.', ), ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'Delete entity' }), + ).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx index 582e1a0a44..c179cd8662 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx @@ -16,7 +16,6 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; -import Alert from '@material-ui/lab/Alert'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { DeleteEntityDialog } from './DeleteEntityDialog'; @@ -24,6 +23,7 @@ import { useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { Alert, Button } from '@backstage/ui'; /** * Returns true if the given entity has the orphan annotation given by the @@ -55,9 +55,21 @@ export function EntityOrphanWarning() { return ( <> - setConfirmationDialogOpen(true)}> - {t('deleteEntity.description')} - + setConfirmationDialogOpen(true)} + > + {t('deleteEntity.actionButtonTitle')} + + } + /> { expect(result.css).toContain('--bui-bg-danger: #ffcdd2;'); expect(result.css).toContain('--bui-bg-warning: #ffe0b2;'); expect(result.css).toContain('--bui-bg-success: #c8e6c9;'); + // Info background may be hex or rgba depending on theme + expect(result.css).toMatch( + /--bui-bg-info:\s*(#[0-9a-f]{6}|rgba?\([^)]*\));/i, + ); }); it('should generate foreground colors correctly', () => { @@ -178,10 +182,8 @@ describe('convertMuiToBuiTheme', () => { const result = convertMuiToBuiTheme(theme); - expect(result.css).toContain('--bui-fg-link: #1976d2;'); - expect(result.css).toContain('--bui-fg-link-hover: #115293;'); expect(result.css).toContain('--bui-fg-disabled: #cccccc;'); - // Foreground danger/warning/success may be hex or rgb depending on theme + // Foreground status colors (standalone) expect(result.css).toMatch( /--bui-fg-danger:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, ); @@ -191,6 +193,22 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toMatch( /--bui-fg-success:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, ); + expect(result.css).toMatch( + /--bui-fg-info:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + // Foreground status colors (on background) + expect(result.css).toMatch( + /--bui-fg-danger-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + expect(result.css).toMatch( + /--bui-fg-warning-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + expect(result.css).toMatch( + /--bui-fg-success-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); + expect(result.css).toMatch( + /--bui-fg-info-on-bg:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); }); it('should generate border colors correctly', () => { @@ -215,6 +233,10 @@ describe('convertMuiToBuiTheme', () => { expect(result.css).toContain('--bui-border-danger: #f44336;'); expect(result.css).toContain('--bui-border-warning: #ff9800;'); expect(result.css).toContain('--bui-border-success: #4caf50;'); + // Info border may be hex or rgb depending on theme + expect(result.css).toMatch( + /--bui-border-info:\s*(#[0-9a-f]{6}|rgb\([^)]*\));/i, + ); }); it('should handle function-based spacing', () => { diff --git a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts index 029d70cec7..dc72e3d597 100644 --- a/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts +++ b/plugins/mui-to-bui/src/components/BuiThemerPage/convertMuiToBuiTheme.ts @@ -94,16 +94,17 @@ function generateBuiVariables(theme: Mui5Theme): Record { Object.entries({ primary: palette.text.primary, secondary: palette.textSubtle, - link: palette.link ?? palette.primary.main, - 'link-hover': palette.linkHover ?? palette.primary.dark, disabled: palette.text.disabled, solid: palette.primary.contrastText, 'solid-disabled': palette.text.disabled, - tint: palette.textSubtle, - 'tint-disabled': palette.textVerySubtle, - danger: palette.error.dark, - warning: palette.warning.dark, - success: palette.success.dark, + danger: palette.error.main, + warning: palette.warning.main, + success: palette.success.main, + info: palette.info?.main ?? palette.primary.main, + 'danger-on-bg': palette.error.dark, + 'warning-on-bg': palette.warning.dark, + 'success-on-bg': palette.success.dark, + 'info-on-bg': palette.info?.dark ?? palette.primary.dark, }).forEach(([key, value]) => { styleObject[`--bui-fg-${key}`] = value; }); @@ -118,13 +119,10 @@ function generateBuiVariables(theme: Mui5Theme): Record { 'solid-hover': blend(palette.primary.main, palette.primary.dark, 0.5), 'solid-pressed': palette.primary.dark, 'solid-disabled': palette.action.disabledBackground, - tint: 'transparent', - 'tint-hover': alpha(palette.primary.main, 0.4), - 'tint-pressed': alpha(palette.primary.main, 0.6), - 'tint-disabled': palette.action.disabledBackground, danger: palette.error.light, warning: palette.warning.light, success: palette.success.light, + info: palette.info?.light ?? alpha(palette.primary.main, 0.1), }).forEach(([key, value]) => { styleObject[`--bui-bg-${key}`] = value; }); @@ -134,6 +132,7 @@ function generateBuiVariables(theme: Mui5Theme): Record { danger: palette.error.main, warning: palette.warning.main, success: palette.success.main, + info: palette.info?.main ?? palette.primary.main, }).forEach(([key, value]) => { styleObject[`--bui-border-${key}`] = value; }); diff --git a/plugins/search-backend-module-elasticsearch/report.api.md b/plugins/search-backend-module-elasticsearch/report.api.md index 3590d6f433..afe06dd45e 100644 --- a/plugins/search-backend-module-elasticsearch/report.api.md +++ b/plugins/search-backend-module-elasticsearch/report.api.md @@ -127,6 +127,19 @@ export type ElasticSearchAuth = }; }; +// @public +export interface ElasticSearchAuthExtensionPoint { + setAuthProvider(provider: ElasticSearchAuthProvider): void; +} + +// @public +export const elasticsearchAuthExtensionPoint: ExtensionPoint; + +// @public +export interface ElasticSearchAuthProvider { + getAuthHeaders(): Promise>; +} + // @public export type ElasticSearchClientOptions = | ElasticSearchElasticSearchClientOptions @@ -314,6 +327,7 @@ export type ElasticSearchOptions = { aliasPostfix?: string; indexPrefix?: string; translator?: ElasticSearchQueryTranslator; + authProvider?: ElasticSearchAuthProvider; }; // @public (undocumented) diff --git a/plugins/search-backend-module-elasticsearch/src/auth.ts b/plugins/search-backend-module-elasticsearch/src/auth.ts new file mode 100644 index 0000000000..af7da316d4 --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/src/auth.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +/** + * A provider that supplies authentication headers for Elasticsearch/OpenSearch requests. + * + * @remarks + * + * This interface allows for dynamic authentication mechanisms such as bearer tokens + * that need to be refreshed or rotated. The `getAuthHeaders` method is called before + * each request to the Elasticsearch/OpenSearch cluster, allowing for just-in-time + * token retrieval and automatic rotation. + * + * @example + * + * ```ts + * const authProvider: ElasticSearchAuthProvider = { + * async getAuthHeaders() { + * const token = await myTokenService.getToken(); + * return { Authorization: `Bearer ${token}` }; + * }, + * }; + * ``` + * + * @public + */ +export interface ElasticSearchAuthProvider { + /** + * Returns authentication headers to be included in requests to Elasticsearch/OpenSearch. + * + * @remarks + * + * This method is called before each request, allowing for dynamic token refresh + * and rotation. Implementations should handle caching internally if needed to + * avoid excessive token generation. + * + * @returns A promise that resolves to a record of header names and values + */ + getAuthHeaders(): Promise>; +} + +/** + * Extension point for providing custom authentication to the Elasticsearch search engine. + * + * @remarks + * + * Use this extension point to provide dynamic authentication mechanisms such as + * bearer tokens with automatic rotation. When an auth provider is set, it takes + * precedence over any static authentication configured in app-config.yaml. + * + * @example + * + * ```ts + * import { createBackendModule } from '@backstage/backend-plugin-api'; + * import { elasticsearchAuthExtensionPoint } from '@backstage/plugin-search-backend-module-elasticsearch'; + * + * export default createBackendModule({ + * pluginId: 'search', + * moduleId: 'elasticsearch-custom-auth', + * register(env) { + * env.registerInit({ + * deps: { + * elasticsearchAuth: elasticsearchAuthExtensionPoint, + * }, + * async init({ elasticsearchAuth }) { + * elasticsearchAuth.setAuthProvider({ + * async getAuthHeaders() { + * const token = await fetchTokenFromIdentityService(); + * return { Authorization: `Bearer ${token}` }; + * }, + * }); + * }, + * }); + * }, + * }); + * ``` + * + * @public + */ +export interface ElasticSearchAuthExtensionPoint { + /** + * Sets the authentication provider for the Elasticsearch search engine. + * + * @remarks + * + * This method can only be called once. Subsequent calls will throw an error. + * The auth provider takes precedence over static authentication configuration. + * + * @param provider - The authentication provider to use + */ + setAuthProvider(provider: ElasticSearchAuthProvider): void; +} + +/** + * Extension point used to customize Elasticsearch/OpenSearch authentication. + * + * @public + */ +export const elasticsearchAuthExtensionPoint = + createExtensionPoint({ + id: 'search.elasticsearchEngine.auth', + }); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.test.ts new file mode 100644 index 0000000000..0264f9b93c --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.test.ts @@ -0,0 +1,194 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createOpenSearchAuthTransport, + createElasticSearchAuthTransport, +} from './ElasticSearchAuthTransport'; +import { ElasticSearchAuthProvider } from '../auth'; + +describe('ElasticSearchAuthTransport', () => { + describe('createOpenSearchAuthTransport', () => { + it('should return a Transport class', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createOpenSearchAuthTransport(authProvider); + + expect(Transport).toBeDefined(); + expect(typeof Transport).toBe('function'); + }); + + it('should call getAuthHeaders when request is made', async () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const Transport = createOpenSearchAuthTransport(authProvider); + + // Create a mock parent class request method + const mockParentRequest = jest.fn().mockResolvedValue({ body: {} }); + + // Create an instance with mocked internals + const transportInstance = Object.create(Transport.prototype); + Object.getPrototypeOf(Object.getPrototypeOf(transportInstance)).request = + mockParentRequest; + + // Call request method (promise style) + const requestPromise = transportInstance.request( + { method: 'GET', path: '/' }, + {}, + ); + + // The auth provider should be called + await requestPromise.catch(() => {}); // Ignore errors from incomplete mock + expect(authProvider.getAuthHeaders).toHaveBeenCalled(); + }); + + it('should be usable as Transport option', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createOpenSearchAuthTransport(authProvider); + + // Verify the Transport has the expected static properties + expect(Transport).toHaveProperty('sniffReasons'); + }); + }); + + describe('createElasticSearchAuthTransport', () => { + it('should return a Transport class', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createElasticSearchAuthTransport(authProvider); + + expect(Transport).toBeDefined(); + expect(typeof Transport).toBe('function'); + }); + + it('should call getAuthHeaders when request is made', async () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const Transport = createElasticSearchAuthTransport(authProvider); + + // Create a mock parent class request method + const mockParentRequest = jest.fn().mockResolvedValue({ body: {} }); + + // Create an instance with mocked internals + const transportInstance = Object.create(Transport.prototype); + Object.getPrototypeOf(Object.getPrototypeOf(transportInstance)).request = + mockParentRequest; + + // Call request method (promise style) + const requestPromise = transportInstance.request( + { method: 'GET', path: '/' }, + {}, + ); + + // The auth provider should be called + await requestPromise.catch(() => {}); // Ignore errors from incomplete mock + expect(authProvider.getAuthHeaders).toHaveBeenCalled(); + }); + + it('should be usable as Transport option', () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test' }), + }; + + const Transport = createElasticSearchAuthTransport(authProvider); + + // Verify the Transport has the expected static properties + expect(Transport).toHaveProperty('sniffReasons'); + }); + }); + + describe('auth provider integration', () => { + it('should support async token retrieval', async () => { + let tokenRetrievalCount = 0; + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest.fn().mockImplementation(async () => { + tokenRetrievalCount++; + // Simulate async token fetch + await new Promise(resolve => setTimeout(resolve, 10)); + return { Authorization: `Bearer token-${tokenRetrievalCount}` }; + }), + }; + + // Just verify the provider works correctly + const headers1 = await authProvider.getAuthHeaders(); + const headers2 = await authProvider.getAuthHeaders(); + + expect(headers1.Authorization).toBe('Bearer token-1'); + expect(headers2.Authorization).toBe('Bearer token-2'); + expect(authProvider.getAuthHeaders).toHaveBeenCalledTimes(2); + }); + + it('should support token rotation pattern', async () => { + const tokens = ['initial-token', 'rotated-token', 'final-token']; + let currentTokenIndex = 0; + + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest.fn().mockImplementation(async () => { + const token = tokens[currentTokenIndex]; + currentTokenIndex = Math.min( + currentTokenIndex + 1, + tokens.length - 1, + ); + return { Authorization: `Bearer ${token}` }; + }), + }; + + // Simulate multiple requests that would trigger token rotation + const headers1 = await authProvider.getAuthHeaders(); + const headers2 = await authProvider.getAuthHeaders(); + const headers3 = await authProvider.getAuthHeaders(); + + expect(headers1.Authorization).toBe('Bearer initial-token'); + expect(headers2.Authorization).toBe('Bearer rotated-token'); + expect(headers3.Authorization).toBe('Bearer final-token'); + }); + + it('should handle errors in getAuthHeaders gracefully', async () => { + const authProvider: ElasticSearchAuthProvider = { + getAuthHeaders: jest + .fn() + .mockRejectedValue(new Error('Token fetch failed')), + }; + + await expect(authProvider.getAuthHeaders()).rejects.toThrow( + 'Token fetch failed', + ); + }); + }); +}); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.ts new file mode 100644 index 0000000000..991fdac71a --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchAuthTransport.ts @@ -0,0 +1,153 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transport as OpenSearchTransport } from '@opensearch-project/opensearch'; +import { Transport as ElasticSearchTransport } from '@elastic/elasticsearch'; +import { ElasticSearchAuthProvider } from '../auth'; +import { ElasticSearchTransportConstructor } from './ElasticSearchClientOptions'; + +/** + * Creates a custom OpenSearch Transport class that injects authentication headers + * from the provided auth provider before each request. + * + * @param authProvider - The authentication provider to use for getting headers + * @returns A Transport class constructor that can be used with the OpenSearch client + * + * @internal + */ +export function createOpenSearchAuthTransport( + authProvider: ElasticSearchAuthProvider, +): ElasticSearchTransportConstructor { + class AuthTransport extends OpenSearchTransport { + request( + params: any, + optionsOrCallback: any = {}, + maybeCallback?: any, + ): any { + // options is optional, so if it's a function, it's the callback + const isOptionsCallback = typeof optionsOrCallback === 'function'; + const options = isOptionsCallback ? {} : optionsOrCallback; + const callback = isOptionsCallback ? optionsOrCallback : maybeCallback; + + // If callback style, wrap it to inject headers + if (callback !== undefined) { + authProvider + .getAuthHeaders() + .then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions, callback); + }) + .catch(callback); + + // Return an abort-able object similar to the AWS transport + return { + abort: () => {}, + }; + } + + // Promise style + return authProvider.getAuthHeaders().then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions); + }); + } + } + + return AuthTransport as unknown as ElasticSearchTransportConstructor; +} + +/** + * Creates a custom Elasticsearch Transport class that injects authentication headers + * from the provided auth provider before each request. + * + * @param authProvider - The authentication provider to use for getting headers + * @returns A Transport class constructor that can be used with the Elasticsearch client + * + * @internal + */ +export function createElasticSearchAuthTransport( + authProvider: ElasticSearchAuthProvider, +): ElasticSearchTransportConstructor { + class AuthTransport extends ElasticSearchTransport { + request(params: any, options?: any, callback?: any): any { + // Handle overloaded signatures + if (typeof options === 'function') { + // Callback style without options + const cb = options; + authProvider + .getAuthHeaders() + .then(authHeaders => { + const mergedOptions = { + headers: authHeaders, + }; + return super.request(params, mergedOptions, cb); + }) + .catch(cb); + + return { abort: () => {} }; + } + + if (callback !== undefined) { + // Callback style with options + authProvider + .getAuthHeaders() + .then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options?.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions, callback); + }) + .catch(callback); + + return { abort: () => {} }; + } + + // Promise style + const result = authProvider.getAuthHeaders().then(authHeaders => { + const mergedOptions = { + ...options, + headers: { + ...options?.headers, + ...authHeaders, + }, + }; + return super.request(params, mergedOptions); + }); + + // Add abort method to match TransportRequestPromise interface + (result as any).abort = () => {}; + return result; + } + } + + return AuthTransport as unknown as ElasticSearchTransportConstructor; +} diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts index 9b1f32c0ba..a17d6850ab 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts @@ -996,6 +996,114 @@ describe('ElasticSearchSearchEngine', () => { }), ).not.toThrow(); }); + + it('should accept an authProvider and not require auth config', async () => { + const config = new ConfigReader({ + search: { + elasticsearch: { + node: 'http://test-node', + // No auth config - using authProvider instead + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const engine = await ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }); + + expect(engine).toBeDefined(); + }); + + it('should accept an authProvider with opensearch provider', async () => { + const config = new ConfigReader({ + search: { + elasticsearch: { + provider: 'opensearch', + node: 'http://test-node', + // No auth config - using authProvider instead + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const engine = await ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }); + + expect(engine).toBeDefined(); + }); + + it('should accept an authProvider with elastic provider', async () => { + // cloudId format: : + // The base64 part decodes to: $ + const cloudId = + 'test:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFiY2QkZWZnaA=='; + const config = new ConfigReader({ + search: { + elasticsearch: { + provider: 'elastic', + cloudId, + // No auth config - using authProvider instead + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + const engine = await ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }); + + expect(engine).toBeDefined(); + }); + + it('should throw error when using authProvider with aws provider', async () => { + const config = new ConfigReader({ + search: { + elasticsearch: { + provider: 'aws', + node: 'http://test-node.us-east-1.es.amazonaws.com', + }, + }, + }); + + const authProvider = { + getAuthHeaders: jest + .fn() + .mockResolvedValue({ Authorization: 'Bearer test-token' }), + }; + + await expect( + ElasticSearchSearchEngine.fromConfig({ + logger: mockServices.logger.mock(), + config, + authProvider, + }), + ).rejects.toThrow( + 'Custom auth provider is not supported with AWS provider', + ); + }); }); }); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index c03541d9d0..96c2b99d84 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -41,6 +41,11 @@ import { DefaultAwsCredentialsManager, } from '@backstage/integration-aws-node'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { ElasticSearchAuthProvider } from '../auth'; +import { + createOpenSearchAuthTransport, + createElasticSearchAuthTransport, +} from './ElasticSearchAuthTransport'; export type { ElasticSearchClientOptions }; @@ -82,6 +87,19 @@ export type ElasticSearchOptions = { aliasPostfix?: string; indexPrefix?: string; translator?: ElasticSearchQueryTranslator; + /** + * An optional authentication provider for dynamic authentication. + * + * @remarks + * + * When provided, this auth provider will be used to inject authentication + * headers into each request to Elasticsearch/OpenSearch. This is useful + * for scenarios requiring dynamic tokens (e.g., bearer tokens with rotation). + * + * The auth provider takes precedence over static authentication configured + * in app-config.yaml. + */ + authProvider?: ElasticSearchAuthProvider; }; /** @@ -180,11 +198,13 @@ export class ElasticSearchSearchEngine implements SearchEngine { aliasPostfix = `search`, indexPrefix = ``, translator, + authProvider, } = options; const credentialProvider = DefaultAwsCredentialsManager.fromConfig(config); const clientOptions = await this.createElasticSearchClientOptions( await credentialProvider?.getCredentialProvider(), config.getConfig('search.elasticsearch'), + authProvider, ); if (clientOptions.provider === 'elastic') { logger.info('Initializing Elastic.co ElasticSearch search engine.'); @@ -504,6 +524,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { private static async createElasticSearchClientOptions( credentialProvider: AwsCredentialProvider, config?: Config, + authProvider?: ElasticSearchAuthProvider, ): Promise { if (!config) { throw new Error('No elastic search config found'); @@ -512,16 +533,26 @@ export class ElasticSearchSearchEngine implements SearchEngine { const sslConfig = clientOptionsConfig?.getOptionalConfig('ssl'); if (config.getOptionalString('provider') === 'elastic') { - const authConfig = config.getConfig('auth'); + const authConfig = authProvider ? undefined : config.getConfig('auth'); return { provider: 'elastic', cloud: { id: config.getString('cloudId'), }, - auth: { - username: authConfig.getString('username'), - password: authConfig.getString('password'), - }, + // When using authProvider, we inject auth via custom Transport, not static auth + ...(authConfig + ? { + auth: { + username: authConfig.getString('username'), + password: authConfig.getString('password'), + }, + } + : {}), + ...(authProvider + ? { + Transport: createElasticSearchAuthTransport(authProvider), + } + : {}), ...(sslConfig ? { ssl: { @@ -533,6 +564,12 @@ export class ElasticSearchSearchEngine implements SearchEngine { }; } if (config.getOptionalString('provider') === 'aws') { + // AWS provider uses SigV4 signing; authProvider is not applicable here + if (authProvider) { + throw new Error( + 'Custom auth provider is not supported with AWS provider. AWS uses SigV4 signing.', + ); + } const requestSigner = new RequestSigner(config.getString('node')); const service = config.getOptionalString('service') ?? requestSigner.service; @@ -560,14 +597,26 @@ export class ElasticSearchSearchEngine implements SearchEngine { }; } if (config.getOptionalString('provider') === 'opensearch') { - const authConfig = config.getConfig('auth'); + const authConfig = authProvider + ? undefined + : config.getOptionalConfig('auth'); return { provider: 'opensearch', node: config.getString('node'), - auth: { - username: authConfig.getString('username'), - password: authConfig.getString('password'), - }, + // When using authProvider, we inject auth via custom Transport, not static auth + ...(authConfig + ? { + auth: { + username: authConfig.getString('username'), + password: authConfig.getString('password'), + }, + } + : {}), + ...(authProvider + ? { + Transport: createOpenSearchAuthTransport(authProvider), + } + : {}), ...(sslConfig ? { ssl: { @@ -578,7 +627,10 @@ export class ElasticSearchSearchEngine implements SearchEngine { : {}), }; } - const authConfig = config.getOptionalConfig('auth'); + // Default provider (standard Elasticsearch) + const authConfig = authProvider + ? undefined + : config.getOptionalConfig('auth'); const auth = authConfig && (authConfig.has('apiKey') @@ -591,7 +643,12 @@ export class ElasticSearchSearchEngine implements SearchEngine { }); return { node: config.getString('node'), - auth, + ...(auth ? { auth } : {}), + ...(authProvider + ? { + Transport: createElasticSearchAuthTransport(authProvider), + } + : {}), ...(sslConfig ? { ssl: { diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts index 0ba2882c61..c36d81eb14 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts @@ -60,10 +60,11 @@ describe('ElasticSearchSearchEngineIndexer', () => { bulkSpy, ); refreshSpy = jest.fn().mockReturnValue({}); + // Use specific pattern to avoid conflict with /some-type-index__* wildcard mock mock.add( { method: 'GET', - path: '/:index/_refresh', + path: '/some-type-index__:ts/_refresh', }, refreshSpy, ); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index 4f7eda9e77..30e19093a7 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -97,7 +97,8 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { : {}), }; }, - refreshOnCompletion: options.skipRefresh !== true, + refreshOnCompletion: + options.skipRefresh !== true ? that.indexName : false, }); // Safely catch errors thrown by the bulk helper client, e.g. HTTP timeouts diff --git a/plugins/search-backend-module-elasticsearch/src/module.ts b/plugins/search-backend-module-elasticsearch/src/module.ts index d4ce249093..d4db5ce90a 100644 --- a/plugins/search-backend-module-elasticsearch/src/module.ts +++ b/plugins/search-backend-module-elasticsearch/src/module.ts @@ -23,6 +23,15 @@ import { ElasticSearchQueryTranslator, ElasticSearchSearchEngine, } from './engines/ElasticSearchSearchEngine'; +import { + type ElasticSearchAuthProvider, + type ElasticSearchAuthExtensionPoint, + elasticsearchAuthExtensionPoint, +} from './auth'; + +// Re-export auth types and extension point +export type { ElasticSearchAuthProvider, ElasticSearchAuthExtensionPoint }; +export { elasticsearchAuthExtensionPoint }; /** @public */ export interface ElasticSearchQueryTranslatorExtensionPoint { @@ -49,6 +58,7 @@ export default createBackendModule({ moduleId: 'elasticsearch-engine', register(env) { let translator: ElasticSearchQueryTranslator | undefined; + let authProvider: ElasticSearchAuthProvider | undefined; env.registerExtensionPoint(elasticsearchTranslatorExtensionPoint, { setTranslator(newTranslator) { @@ -61,6 +71,15 @@ export default createBackendModule({ }, }); + env.registerExtensionPoint(elasticsearchAuthExtensionPoint, { + setAuthProvider(provider) { + if (authProvider) { + throw new Error('ElasticSearch auth provider may only be set once'); + } + authProvider = provider; + }, + }); + env.registerInit({ deps: { searchEngineRegistry: searchEngineRegistryExtensionPoint, @@ -82,11 +101,17 @@ export default createBackendModule({ if (indexPrefix) { logger.info(`Index prefix will be used for indices: ${indexPrefix}`); } + if (authProvider) { + logger.info( + 'Using custom auth provider for ElasticSearch authentication.', + ); + } searchEngineRegistry.setSearchEngine( await ElasticSearchSearchEngine.fromConfig({ logger, config, translator, + authProvider, indexPrefix, }), ); diff --git a/plugins/search-backend-module-pg/report.api.md b/plugins/search-backend-module-pg/report.api.md index 67e60b7822..317547cbdc 100644 --- a/plugins/search-backend-module-pg/report.api.md +++ b/plugins/search-backend-module-pg/report.api.md @@ -89,6 +89,7 @@ export interface DocumentResultRow { document: IndexableDocument; // (undocumented) highlight: IndexableDocument; + total_count: string; // (undocumented) type: string; } diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts index bc3df76692..9c77a04e44 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts @@ -193,6 +193,7 @@ describe('PgSearchEngine', () => { database.transaction.mockImplementation(fn => fn(tx)); database.query.mockResolvedValue([ { + total_count: '1', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -212,6 +213,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 1, results: [ { document: { @@ -251,6 +253,7 @@ describe('PgSearchEngine', () => { Array(30) .fill(0) .map((_, i) => ({ + total_count: '30', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -270,6 +273,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 30, results: Array(25) .fill(0) .map((_, i) => ({ @@ -309,6 +313,7 @@ describe('PgSearchEngine', () => { Array(30) .fill(0) .map((_, i) => ({ + total_count: '30', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -330,6 +335,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 30, results: Array(30) .fill(0) .map((_, i) => ({ diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 1a024be33c..ea75b1a739 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -269,7 +269,12 @@ export class PgSearchEngine implements SearchEngine { }), ); - return { results, nextPageCursor, previousPageCursor }; + return { + results, + numberOfResults: rows.length > 0 ? parseInt(rows[0].total_count, 10) : 0, + nextPageCursor, + previousPageCursor, + }; } } diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts index ef249fa33d..7ef48d8398 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts @@ -274,6 +274,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { location: 'LOCATION-1', text: 'Hello World', @@ -320,6 +321,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { location: 'LOCATION-1', text: 'Around the world', @@ -329,6 +331,7 @@ describe('DatabaseDocumentStore', () => { type: 'test', }, { + total_count: '2', document: { location: 'LOCATION-1', text: 'Hello World', @@ -382,6 +385,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -436,6 +440,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -503,6 +508,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -513,6 +519,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -523,6 +530,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -533,6 +541,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -585,6 +594,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -635,6 +645,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { title: 'Lorem Ipsum', text: 'Hello World', @@ -645,6 +656,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '2', document: { title: 'Dolor sit amet', text: 'Hello World', diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts index 21512361a9..bd63756b06 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts @@ -215,6 +215,7 @@ export class DatabaseDocumentStore implements DatabaseStore { } query.select('type', 'document'); + query.select(tx.raw('COUNT(*) OVER() AS total_count')); if (pgTerm && options.useHighlight) { const headlineOptions = `MaxWords=${options.maxWords}, MinWords=${options.minWords}, ShortWord=${options.shortWord}, HighlightAll=${options.highlightAll}, MaxFragments=${options.maxFragments}, FragmentDelimiter=${options.fragmentDelimiter}, StartSel=${options.preTag}, StopSel=${options.postTag}`; diff --git a/plugins/search-backend-module-pg/src/database/types.ts b/plugins/search-backend-module-pg/src/database/types.ts index f8529fbebe..5012e1d792 100644 --- a/plugins/search-backend-module-pg/src/database/types.ts +++ b/plugins/search-backend-module-pg/src/database/types.ts @@ -58,6 +58,10 @@ export interface RawDocumentRow { /** @public */ export interface DocumentResultRow { + /** + * Total number of documents matching the query, regardless of pagination. + */ + total_count: string; document: IndexableDocument; type: string; highlight: IndexableDocument; diff --git a/plugins/search/README.md b/plugins/search/README.md index beab698b2e..d0b1aa78e3 100644 --- a/plugins/search/README.md +++ b/plugins/search/README.md @@ -15,6 +15,7 @@ Configure the search query values via `app-config.yaml` to define how it behaves ```yaml # app-config.yaml search: + defaultType: 'software-catalog' query: pageLimit: 50 ``` diff --git a/plugins/search/config.d.ts b/plugins/search/config.d.ts index d9a5ce4a1b..0c35082e58 100644 --- a/plugins/search/config.d.ts +++ b/plugins/search/config.d.ts @@ -17,6 +17,11 @@ export interface Config { /** Configuration options for the search plugin */ search?: { + /** + * The default search type to be used when no specific type is selected. + * @visibility frontend + */ + defaultType?: string; /** * An object representing the default search query configuration. * By configuring and modifying the values of this object, diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 2efe9978e0..c36ae54d33 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -39,6 +39,7 @@ import { createExtensionInput, PageBlueprint, NavItemBlueprint, + configApiRef, } from '@backstage/frontend-plugin-api'; import { @@ -139,6 +140,7 @@ export const searchPage = PageBlueprint.makeWithOverrides({ const { isMobile } = useSidebarPinState(); const { types } = useSearch(); const catalogApi = useApi(catalogApiRef); + const configApi = useApi(configApiRef); return ( @@ -152,7 +154,9 @@ export const searchPage = PageBlueprint.makeWithOverrides({ { }; expect(parsedYml.plugins).toHaveLength(4); expect(parsedYml.plugins).toContain('techdocs-core'); - // we want our original object with its properties to be preserved, and for the basic string form of the plugin - // to NOT be added as well. expect(parsedYml.plugins).not.toContain('custom-plugin'); expect(parsedYml.plugins).toContainEqual({ 'custom-plugin': { with: { configuration: 1 } }, @@ -727,4 +745,310 @@ describe('helpers', () => { ).resolves.toBeUndefined(); }); }); + + describe('sanitizeMkdocsYml', () => { + beforeEach(() => { + warn.mockClear(); + mockDir.setContent({ + 'mkdocs_with_hooks.yml': mkdocsYmlWithHooks, + 'mkdocs.yml': mkdocsYml, + }); + }); + + it('should remove disallowed keys from mkdocs.yml and log a warning', async () => { + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_with_hooks.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_with_hooks.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as { + hooks?: string[]; + site_name: string; + }; + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test site name'); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: hooks', + ), + ); + }); + + it('should not modify mkdocs.yml when no disallowed keys are present', async () => { + await sanitizeMkdocsYml(mockDir.resolve('mkdocs.yml'), mockLogger); + + const updatedMkdocsYml = await fs.readFile(mockDir.resolve('mkdocs.yml')); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as { + hooks?: string[]; + site_name: string; + }; + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test site name'); + expect(warn).not.toHaveBeenCalled(); + }); + + it('should remove multiple disallowed keys and list them all in the warning', async () => { + const mkdocsWithMultipleDisallowed = `site_name: Test +hooks: + - hook.py +some_unknown_key: value +another_unknown: true +`; + mockDir.setContent({ + 'mkdocs_multiple.yml': mkdocsWithMultipleDisallowed, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_multiple.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_multiple.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.some_unknown_key).toBeUndefined(); + expect(parsedYml.another_unknown).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(warn).toHaveBeenCalledWith( + expect.stringMatching( + /Removed the following unsupported configuration keys.*hooks.*some_unknown_key.*another_unknown|Removed the following unsupported configuration keys.*hooks.*another_unknown.*some_unknown_key/, + ), + ); + }); + + it('should remove hooks introduced via YAML merge keys', async () => { + mockDir.setContent({ + 'mkdocs_merge_keys.yml': mkdocsYmlWithMergeKeyHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_merge_keys.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_merge_keys.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(parsedYml.plugins).toEqual(['techdocs-core']); + expect(updatedMkdocsYml.toString()).not.toContain('<<:'); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: hooks', + ), + ); + }); + + it('should remove hooks when parsers interpret duplicate merge keys differently', async () => { + mockDir.setContent({ + 'mkdocs_parser_diff.yml': mkdocsYmlWithParserDifferentialHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_parser_diff.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_parser_diff.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(parsedYml.plugins).toEqual(['techdocs-core']); + expect(parsedYml.extra).toBeDefined(); + expect(updatedMkdocsYml.toString()).not.toContain('<<:'); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Removed the following unsupported configuration keys from mkdocs.yml: hooks', + ), + ); + }); + + it('should remove hooks with duplicate merge keys and top-level anchors', async () => { + mockDir.setContent({ + 'mkdocs_duplicate_merge.yml': mkdocsYmlWithDuplicateMergeHooks, + }); + + await sanitizeMkdocsYml( + mockDir.resolve('mkdocs_duplicate_merge.yml'), + mockLogger, + ); + + const updatedMkdocsYml = await fs.readFile( + mockDir.resolve('mkdocs_duplicate_merge.yml'), + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as Record< + string, + unknown + >; + + expect(parsedYml.hooks).toBeUndefined(); + expect(parsedYml.site_name).toBe('Test'); + expect(parsedYml.plugins).toEqual(['techdocs-core']); + expect(updatedMkdocsYml.toString()).not.toContain('<<:'); + + expect(warn).toHaveBeenCalledWith(expect.stringContaining('hooks')); + }); + }); + + describe('validateDocsDirectory', () => { + it('should pass for a valid docs directory with no symlinks', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + 'guide.md': 'Guide content', + }, + }); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).resolves.toBeUndefined(); + }); + + it('should pass for symlinks pointing within the input directory', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + 'other.md': 'Other content', + }); + + // Create a symlink within the input directory + await fs.symlink( + mockDir.resolve('other.md'), + mockDir.resolve('docs/link.md'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).resolves.toBeUndefined(); + }); + + it('should reject symlinks pointing outside the input directory', async () => { + const anotherMockDir = createMockDirectory(); + + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + }); + + anotherMockDir.setContent({ + tmp: { + secret: 'password', + }, + }); + + // Create a symlink pointing outside the input directory + await fs.symlink( + anotherMockDir.resolve('tmp/secret'), + mockDir.resolve('docs/escape.md'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should reject symlinks to sensitive files like /etc/passwd', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + }); + + // Create a symlink to /etc/passwd + await fs.symlink('/etc/passwd', mockDir.resolve('docs/passwd.md')); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should reject symlinks in nested directories', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + nested: { + 'page.md': 'Nested page', + }, + }, + }); + + // Create a symlink in a nested directory pointing outside + await fs.symlink('/etc/passwd', mockDir.resolve('docs/nested/escape.md')); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should reject directory symlinks pointing outside', async () => { + const anotherMockDir = createMockDirectory(); + + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + }); + + anotherMockDir.setContent({ + tmp: { + secret: 'password', + }, + }); + + // Create a directory symlink pointing outside + await fs.symlink( + anotherMockDir.path, + mockDir.resolve('docs/external-dir'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).rejects.toThrow(/not allowed to refer to a location outside/i); + }); + + it('should pass for directory symlinks within input directory', async () => { + mockDir.setContent({ + docs: { + 'index.md': 'Hello', + }, + assets: { + 'image.png': 'binary content', + }, + }); + + // Create a directory symlink within input directory + await fs.symlink( + mockDir.resolve('assets'), + mockDir.resolve('docs/assets'), + ); + + await expect( + validateDocsDirectory(mockDir.resolve('docs'), mockDir.path), + ).resolves.toBeUndefined(); + }); + }); }); diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index fa0311b47c..1b0813f18d 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -15,6 +15,7 @@ */ import { isChildPath, LoggerService } from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; import { Entity } from '@backstage/catalog-model'; import { assertError, ForwardedError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -266,6 +267,51 @@ export const getMkdocsYml = async ( }; }; +/** + * Allowlist of MkDocs configuration keys supported by TechDocs. + * + * @see https://www.mkdocs.org/user-guide/configuration/ + */ +export const ALLOWED_MKDOCS_KEYS = new Set([ + // Site information + 'site_name', + 'site_url', + 'site_description', + 'site_author', + // Repository + 'repo_url', + 'repo_name', + 'edit_uri', + 'edit_uri_template', + // Build directories + 'docs_dir', + 'site_dir', + // Documentation layout + 'nav', + 'exclude_docs', + 'not_in_nav', + // Build settings + 'theme', + 'plugins', + 'markdown_extensions', + 'extra', + 'extra_css', + 'extra_templates', + // Preview controls + 'use_directory_urls', + 'strict', + 'dev_addr', + 'watch', + // Metadata + 'copyright', + 'remote_branch', + 'remote_name', + 'validation', + // Deprecated + 'google_analytics', + 'INHERIT', +]); + /** * Validating mkdocs config file for incorrect/insecure values * Throws on invalid configs @@ -288,6 +334,7 @@ export const validateMkdocsYaml = async ( } const parsedMkdocsYml: Record = mkdocsYml; + if ( parsedMkdocsYml.docs_dir && !isChildPath(inputDir, resolvePath(inputDir, parsedMkdocsYml.docs_dir)) @@ -300,6 +347,29 @@ export const validateMkdocsYaml = async ( return parsedMkdocsYml.docs_dir; }; +/** + * Validates that the docs directory doesn't contain symlinks pointing outside + * the input directory. This prevents path traversal attacks where malicious + * symlinks could be used to read arbitrary files from the host filesystem. + * + * @param docsDir - The docs directory to validate (absolute path) + * @param inputDir - The root input directory that symlinks must stay within + */ +export const validateDocsDirectory = async ( + docsDir: string, + inputDir: string, +): Promise => { + const files = await getFileTreeRecursively(docsDir); + + for (const file of files) { + if (!isChildPath(inputDir, file)) { + throw new NotAllowedError( + `Path ${file} is not allowed to refer to a location outside ${inputDir}`, + ); + } + } +}; + /** * Update docs/index.md file before TechDocs generator uses it to generate docs site, * falling back to docs/README.md or README.md in case a default docs/index.md diff --git a/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts b/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts index 804c06f129..cd506d3b28 100644 --- a/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts +++ b/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts @@ -16,7 +16,11 @@ import fs from 'fs-extra'; import yaml from 'js-yaml'; import { ParsedLocationAnnotation } from '../../helpers'; -import { getRepoUrlFromLocationAnnotation, MKDOCS_SCHEMA } from './helpers'; +import { + ALLOWED_MKDOCS_KEYS, + getRepoUrlFromLocationAnnotation, + MKDOCS_SCHEMA, +} from './helpers'; import { assertError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -180,3 +184,55 @@ export const patchMkdocsYmlWithPlugins = async ( return changesMade; }); }; + +/** + * Sanitize mkdocs.yml by keeping only allowed configuration keys. + * + * TechDocs only supports a subset of MkDocs configuration options. + * This function reconstructs the config with only allowed keys, + * discarding everything else. This approach ensures that any unknown + * or potentially dangerous configuration options are not passed to MkDocs. + * + * The file is always rewritten to ensure YAML features like merge keys + * and anchors are resolved into plain configuration. + * + * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site + * @param logger - A logger instance + */ +export const sanitizeMkdocsYml = async ( + mkdocsYmlPath: string, + logger: LoggerService, +) => { + await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => { + // Identify keys that will be removed for logging + const removedKeys = Object.keys(mkdocsYml).filter( + key => !ALLOWED_MKDOCS_KEYS.has(key), + ); + + if (removedKeys.length > 0) { + logger.warn( + `Removed the following unsupported configuration keys from mkdocs.yml: ${removedKeys.join( + ', ', + )}. ` + + `TechDocs only supports a subset of MkDocs configuration options.`, + ); + } + + // Build a new object with only allowed keys + const sanitized: Record = {}; + for (const key of ALLOWED_MKDOCS_KEYS) { + if (key in mkdocsYml) { + sanitized[key] = (mkdocsYml as Record)[key]; + } + } + + // Clear the original object and copy sanitized values back + for (const key of Object.keys(mkdocsYml)) { + delete (mkdocsYml as Record)[key]; + } + Object.assign(mkdocsYml, sanitized); + + // Always rewrite to ensure clean YAML output (resolves merge keys, anchors, etc.) + return true; + }); +}; diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index 077963b6c7..ed357c5c81 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -26,12 +26,14 @@ import { patchIndexPreBuild, runCommand, storeEtagMetadata, + validateDocsDirectory, validateMkdocsYaml, } from './helpers'; import { patchMkdocsYmlPreBuild, patchMkdocsYmlWithPlugins, + sanitizeMkdocsYml, } from './mkdocsPatchers'; import { GeneratorBase, @@ -112,6 +114,14 @@ export class TechdocsGenerator implements GeneratorBase { // validate the docs_dir first const docsDir = await validateMkdocsYaml(inputDir, content); + // Remove unsupported configuration keys + await sanitizeMkdocsYml(mkdocsYmlPath, childLogger); + + // Validate that no symlinks in the docs directory point outside the input directory + // This prevents path traversal attacks where malicious symlinks could leak host files + const resolvedDocsDir = path.join(inputDir, docsDir ?? 'docs'); + await validateDocsDirectory(resolvedDocsDir, inputDir); + if (parsedLocationAnnotation) { await patchMkdocsYmlPreBuild( mkdocsYmlPath, diff --git a/yarn.lock b/yarn.lock index a9c7d9235b..00462f7c47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -765,7 +765,7 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-sesv2@npm:^3.839.0, @aws-sdk/client-sesv2@npm:^3.911.0": +"@aws-sdk/client-sesv2@npm:^3.911.0": version: 3.946.0 resolution: "@aws-sdk/client-sesv2@npm:3.946.0" dependencies: @@ -2969,6 +2969,7 @@ __metadata: "@aws-sdk/credential-providers": "npm:^3.350.0" "@aws-sdk/types": "npm:^3.347.0" "@aws-sdk/util-stream-node": "npm:^3.350.0" + "@azure/identity": "npm:^4.0.0" "@azure/storage-blob": "npm:^12.5.0" "@backstage/backend-app-api": "workspace:^" "@backstage/backend-dev-utils": "workspace:^" @@ -5399,6 +5400,7 @@ __metadata: "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" @@ -5506,6 +5508,7 @@ __metadata: "@backstage/plugin-techdocs-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" @@ -8577,6 +8580,15 @@ __metadata: languageName: node linkType: hard +"@devcontainers/cli@npm:^0.81.1": + version: 0.81.1 + resolution: "@devcontainers/cli@npm:0.81.1" + bin: + devcontainer: devcontainer.js + checksum: 10/269c88697af36823d6d5d128dd8c08f28714ffd7050d76965da90907bbf579411162a2d499cf3d7ff2d37f442b5419db58d936e5a828b812d4b3d555c923a578 + languageName: node + linkType: hard + "@elastic/elasticsearch-mock@npm:^1.0.0": version: 1.0.0 resolution: "@elastic/elasticsearch-mock@npm:1.0.0" @@ -14289,13 +14301,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.57.0 - resolution: "@playwright/test@npm:1.57.0" + version: 1.58.0 + resolution: "@playwright/test@npm:1.58.0" dependencies: - playwright: "npm:1.57.0" + playwright: "npm:1.58.0" bin: playwright: cli.js - checksum: 10/07f5ba4841b2db1dea70d821004c5156b692488e13523c096ce3487d30f95f34ccf30ba6467ece60c86faac27ae382213b7eacab48a695550981b2e811e5e579 + checksum: 10/1ab8c4d408c919e1357bb43e5682d1ce66bb792516fd6269dd5cff9c9b7cc82e026b3fc864ba723714337dc610ac46110ed8307d610feb92f5002abbb03a6392 languageName: node linkType: hard @@ -17044,92 +17056,92 @@ __metadata: languageName: node linkType: hard -"@rspack/binding-darwin-arm64@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-darwin-arm64@npm:1.7.3" +"@rspack/binding-darwin-arm64@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-darwin-arm64@npm:1.7.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-darwin-x64@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-darwin-x64@npm:1.7.3" +"@rspack/binding-darwin-x64@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-darwin-x64@npm:1.7.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rspack/binding-linux-arm64-gnu@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.3" +"@rspack/binding-linux-arm64-gnu@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-linux-arm64-gnu@npm:1.7.4" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-arm64-musl@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.3" +"@rspack/binding-linux-arm64-musl@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-linux-arm64-musl@npm:1.7.4" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rspack/binding-linux-x64-gnu@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.3" +"@rspack/binding-linux-x64-gnu@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-linux-x64-gnu@npm:1.7.4" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rspack/binding-linux-x64-musl@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-linux-x64-musl@npm:1.7.3" +"@rspack/binding-linux-x64-musl@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-linux-x64-musl@npm:1.7.4" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rspack/binding-wasm32-wasi@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-wasm32-wasi@npm:1.7.3" +"@rspack/binding-wasm32-wasi@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-wasm32-wasi@npm:1.7.4" dependencies: "@napi-rs/wasm-runtime": "npm:1.0.7" conditions: cpu=wasm32 languageName: node linkType: hard -"@rspack/binding-win32-arm64-msvc@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.3" +"@rspack/binding-win32-arm64-msvc@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-win32-arm64-msvc@npm:1.7.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rspack/binding-win32-ia32-msvc@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.3" +"@rspack/binding-win32-ia32-msvc@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-win32-ia32-msvc@npm:1.7.4" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rspack/binding-win32-x64-msvc@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.3" +"@rspack/binding-win32-x64-msvc@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding-win32-x64-msvc@npm:1.7.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rspack/binding@npm:1.7.3": - version: 1.7.3 - resolution: "@rspack/binding@npm:1.7.3" +"@rspack/binding@npm:1.7.4": + version: 1.7.4 + resolution: "@rspack/binding@npm:1.7.4" dependencies: - "@rspack/binding-darwin-arm64": "npm:1.7.3" - "@rspack/binding-darwin-x64": "npm:1.7.3" - "@rspack/binding-linux-arm64-gnu": "npm:1.7.3" - "@rspack/binding-linux-arm64-musl": "npm:1.7.3" - "@rspack/binding-linux-x64-gnu": "npm:1.7.3" - "@rspack/binding-linux-x64-musl": "npm:1.7.3" - "@rspack/binding-wasm32-wasi": "npm:1.7.3" - "@rspack/binding-win32-arm64-msvc": "npm:1.7.3" - "@rspack/binding-win32-ia32-msvc": "npm:1.7.3" - "@rspack/binding-win32-x64-msvc": "npm:1.7.3" + "@rspack/binding-darwin-arm64": "npm:1.7.4" + "@rspack/binding-darwin-x64": "npm:1.7.4" + "@rspack/binding-linux-arm64-gnu": "npm:1.7.4" + "@rspack/binding-linux-arm64-musl": "npm:1.7.4" + "@rspack/binding-linux-x64-gnu": "npm:1.7.4" + "@rspack/binding-linux-x64-musl": "npm:1.7.4" + "@rspack/binding-wasm32-wasi": "npm:1.7.4" + "@rspack/binding-win32-arm64-msvc": "npm:1.7.4" + "@rspack/binding-win32-ia32-msvc": "npm:1.7.4" + "@rspack/binding-win32-x64-msvc": "npm:1.7.4" dependenciesMeta: "@rspack/binding-darwin-arm64": optional: true @@ -17151,38 +17163,61 @@ __metadata: optional: true "@rspack/binding-win32-x64-msvc": optional: true - checksum: 10/295c59c22370f71e0f8543fca9819d7d13741e02ef340f1fdda84789d800e324a91d7e54c5119ec0e2a64d4074efff7d2a7f989522bef2d8cb12fb7abffbb5d5 + checksum: 10/0d082e962980ace986c546ac359e7d4b5fada21ce38980db792c70ef97257841257b64a0b65c5d3c2e2660d58ab05b1d7302defc752f325c616507f5837fb98d languageName: node linkType: hard "@rspack/core@npm:^1.4.11": - version: 1.7.3 - resolution: "@rspack/core@npm:1.7.3" + version: 1.7.4 + resolution: "@rspack/core@npm:1.7.4" dependencies: "@module-federation/runtime-tools": "npm:0.22.0" - "@rspack/binding": "npm:1.7.3" + "@rspack/binding": "npm:1.7.4" "@rspack/lite-tapable": "npm:1.1.0" peerDependencies: "@swc/helpers": ">=0.5.1" peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 10/cd6a4d8dea6eb2fe8fd97b1266ef3ab0bdefd02970f70f0154b20c7ba3b4235cb92e712cf7f9f42bf64786663422ee46e85917b15fe06b563da22bc39ca5f35e + checksum: 10/ecce39e64993ba9f5792c737b6d61960946c59399254f5903651a53b6e61761f8bc2a20dfdf81b1b819e1b0f1e26257456d902244485fd035e5a585b5a28a623 languageName: node linkType: hard "@rspack/dev-server@npm:^1.1.4": - version: 1.1.5 - resolution: "@rspack/dev-server@npm:1.1.5" + version: 1.2.1 + resolution: "@rspack/dev-server@npm:1.2.1" dependencies: + "@types/bonjour": "npm:^3.5.13" + "@types/connect-history-api-fallback": "npm:^1.5.4" + "@types/express": "npm:^4.17.25" + "@types/express-serve-static-core": "npm:^4.17.21" + "@types/serve-index": "npm:^1.9.4" + "@types/serve-static": "npm:^1.15.5" + "@types/sockjs": "npm:^0.3.36" + "@types/ws": "npm:^8.5.10" + ansi-html-community: "npm:^0.0.8" + bonjour-service: "npm:^1.2.1" chokidar: "npm:^3.6.0" + colorette: "npm:^2.0.10" + compression: "npm:^1.8.1" + connect-history-api-fallback: "npm:^2.0.0" + express: "npm:^4.22.1" + graceful-fs: "npm:^4.2.6" http-proxy-middleware: "npm:^2.0.9" + ipaddr.js: "npm:^2.1.0" + launch-editor: "npm:^2.6.1" + open: "npm:^10.0.3" p-retry: "npm:^6.2.0" - webpack-dev-server: "npm:5.2.2" + schema-utils: "npm:^4.2.0" + selfsigned: "npm:^2.4.1" + serve-index: "npm:^1.9.1" + sockjs: "npm:^0.3.24" + spdy: "npm:^4.0.2" + webpack-dev-middleware: "npm:^7.4.2" ws: "npm:^8.18.0" peerDependencies: "@rspack/core": "*" - checksum: 10/67a747d998f9a2449cb1c6c5791ffc812c9d99a7219595359ce960063f344fde9f8f2000bbc9633dc490082f69b74a20b8f319697bd19beca65bd108f5dff5e5 + checksum: 10/154808faef8079dc1d6eae1712455864cc7bc1ec686f3020f7117ad3e5f2906940f27ec514eb40230276132371570ecdf6b47f7ab117ad209462bcba7c2b0692 languageName: node linkType: hard @@ -21579,12 +21614,11 @@ __metadata: linkType: hard "@types/nodemailer@npm:^7.0.0": - version: 7.0.5 - resolution: "@types/nodemailer@npm:7.0.5" + version: 7.0.9 + resolution: "@types/nodemailer@npm:7.0.9" dependencies: - "@aws-sdk/client-sesv2": "npm:^3.839.0" "@types/node": "npm:*" - checksum: 10/2defc792256092b18ee7ca2ef18ba47343ce5239da32ad9d5219f8f6d57279fa1540415219105ce690f5fb9bf343288e81c315d41deb3e3e8f293d91c382ab4f + checksum: 10/6bfca388af668a05a68e750a7acbd5736a3dcdf96f34f23bf4211ab152572d55008fd813e38bf03f944c6edbd002a7f98d6cee048343b8e840b3b91a00befac9 languageName: node linkType: hard @@ -25209,10 +25243,10 @@ __metadata: languageName: node linkType: hard -"aws-ssl-profiles@npm:^1.1.1": - version: 1.1.1 - resolution: "aws-ssl-profiles@npm:1.1.1" - checksum: 10/7293640f29d7b9baf62bcc4e36228c0b7cd435053bbe5535a367dc7fcd76ffdf7f1c7078181147f5d666cc316abb65ce098e64e403f882a49937a4942080a8ea +"aws-ssl-profiles@npm:^1.1.2": + version: 1.1.2 + resolution: "aws-ssl-profiles@npm:1.1.2" + checksum: 10/af9e5c5e6e343e0f299106acaf03106a7458be69772d004f3e4cf0e3649bb41131b594126fcbc997ad89d73752d9e1d72886c72fcc8649ac5d590459d6b75827 languageName: node linkType: hard @@ -25251,7 +25285,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:1.13.2, axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.7.4": +"axios@npm:1.13.2": version: 1.13.2 resolution: "axios@npm:1.13.2" dependencies: @@ -25262,6 +25296,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^1.0.0, axios@npm:^1.11.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.7.4": + version: 1.13.4 + resolution: "axios@npm:1.13.4" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.4" + proxy-from-env: "npm:^1.1.0" + checksum: 10/54b7ef71c64837f9d52475832337f520cf6fa85c94612e03a3a2aad7082804a2544741267122696662147e90e6d2746601346984cf531ae715ecdb56d586a04c + languageName: node + linkType: hard + "axobject-query@npm:^4.1.0": version: 4.1.0 resolution: "axobject-query@npm:4.1.0" @@ -27624,9 +27669,9 @@ __metadata: linkType: hard "core-js@npm:^3.6.5": - version: 3.47.0 - resolution: "core-js@npm:3.47.0" - checksum: 10/c02dc6a091c7e6799e3527dc06a428c44bbcff7f8f6ee700ff818b90aa2ebaf1f17b0234146e692811da97cda5a39a6095ecadec9fd1a74b1135103eb0e96cb1 + version: 3.48.0 + resolution: "core-js@npm:3.48.0" + checksum: 10/08bb3cc9b3225b905e72370c18257a14bb5563946d9eb7496799e0ee4f13231768b980ffe98434df7dbd0f8209bd2c19519938a2fa94846b2c82c2d5aa804037 languageName: node linkType: hard @@ -27645,12 +27690,12 @@ __metadata: linkType: hard "cors@npm:^2.8.4, cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" + version: 2.8.6 + resolution: "cors@npm:2.8.6" dependencies: object-assign: "npm:^4" vary: "npm:^1" - checksum: 10/66e88e08edee7cbce9d92b4d28a2028c88772a4c73e02f143ed8ca76789f9b59444eed6b1c167139e76fa662998c151322720093ba229f9941365ada5a6fc2c6 + checksum: 10/aa7174305b21ceb90f9c84f4eaa32f04432d333addbfdc0d1eb7310393c48902e5364aada5ac2f5d054528d63b3179238444475426fcb74e1e345077de485727 languageName: node linkType: hard @@ -31040,7 +31085,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.14.0, express@npm:^4.21.0, express@npm:^4.21.2, express@npm:^4.22.0, express@npm:^4.22.1": +"express@npm:^4.14.0, express@npm:^4.21.0, express@npm:^4.22.0, express@npm:^4.22.1": version: 4.22.1 resolution: "express@npm:4.22.1" dependencies: @@ -33903,12 +33948,12 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.7.0": - version: 0.7.0 - resolution: "iconv-lite@npm:0.7.0" +"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.2": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" dependencies: safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10/5bfc897fedfb7e29991ae5ef1c061ed4f864005f8c6d61ef34aba6a3885c04bd207b278c0642b041383aeac2d11645b4319d0ca7b863b0be4be0cde1c9238ca7 + checksum: 10/24c937b532f868e938386b62410b303b7c767ce3d08dc2829cbe59464d5a26ef86ae5ad1af6b34eec43ddfea39e7d101638644b0178d67262fa87015d59f983a languageName: node linkType: hard @@ -35198,8 +35243,8 @@ __metadata: linkType: hard "isomorphic-git@npm:^1.23.0": - version: 1.36.1 - resolution: "isomorphic-git@npm:1.36.1" + version: 1.36.3 + resolution: "isomorphic-git@npm:1.36.3" dependencies: async-lock: "npm:^1.4.1" clean-git-ref: "npm:^2.0.1" @@ -35214,7 +35259,7 @@ __metadata: simple-get: "npm:^4.0.1" bin: isogit: cli.cjs - checksum: 10/f68670b53c5216284afe785f5327dcedffb99dcbe947b338e1f3b20338382506561ef178b77391d2ce95f16912eb8035b2b4d3d52d832cf1cd86ceabc9b9174a + checksum: 10/b6295326773ed3ba4b0f7abb7470c65f11de8b9e19ff0b8e58d85575491a461fae600a579e5de91c15398144eefec3f95c99082ac2c866abbc66e0dd225273c8 languageName: node linkType: hard @@ -36670,11 +36715,11 @@ __metadata: linkType: hard "keyv@npm:*, keyv@npm:^5.2.1": - version: 5.5.5 - resolution: "keyv@npm:5.5.5" + version: 5.6.0 + resolution: "keyv@npm:5.6.0" dependencies: "@keyv/serialize": "npm:^1.1.1" - checksum: 10/4bbc2119151d67bfc04d3a08fc98f4efac5e171e8c99519fc7bc9ae9e05df4d1c6c835a5d133d563ed109a20db4688f3f9738400cbd3674f6303cdfc4490d429 + checksum: 10/f1de999fdf635d703d091981a0a3844fb20081e01d45c6743ada7d01f3a6570dc47d1882e10d0a98d31075b342db8e62b4ebe4f5bf473dcd0903064d718709c1 languageName: node linkType: hard @@ -37688,10 +37733,10 @@ __metadata: languageName: node linkType: hard -"long@npm:^5.0.0, long@npm:^5.2.1": - version: 5.2.3 - resolution: "long@npm:5.2.3" - checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 +"long@npm:^5.0.0, long@npm:^5.3.2": + version: 5.3.2 + resolution: "long@npm:5.3.2" + checksum: 10/b6b55ddae56fcce2864d37119d6b02fe28f6dd6d9e44fd22705f86a9254b9321bd69e9ffe35263b4846d54aba197c64882adcb8c543f2383c1e41284b321ea64 languageName: node linkType: hard @@ -37815,7 +37860,7 @@ __metadata: languageName: node linkType: hard -"lru.min@npm:^1.0.0, lru.min@npm:^1.1.0": +"lru.min@npm:^1.1.0, lru.min@npm:^1.1.3": version: 1.1.3 resolution: "lru.min@npm:1.1.3" checksum: 10/b741bf51e6f2620f35f66657c31a37bb4e6aaa3d7f4dfd8a657cd4e6a4d75be881057d71f54a907a4938d5993677d92cdfa71203054a73af6978514ba73767ee @@ -39618,19 +39663,19 @@ __metadata: linkType: hard "mysql2@npm:^3.0.0": - version: 3.16.1 - resolution: "mysql2@npm:3.16.1" + version: 3.16.2 + resolution: "mysql2@npm:3.16.2" dependencies: - aws-ssl-profiles: "npm:^1.1.1" + aws-ssl-profiles: "npm:^1.1.2" denque: "npm:^2.1.0" generate-function: "npm:^2.3.1" - iconv-lite: "npm:^0.7.0" - long: "npm:^5.2.1" - lru.min: "npm:^1.0.0" + iconv-lite: "npm:^0.7.2" + long: "npm:^5.3.2" + lru.min: "npm:^1.1.3" named-placeholders: "npm:^1.1.6" seq-queue: "npm:^0.0.5" - sqlstring: "npm:^2.3.2" - checksum: 10/c4e61df1e7855161966f2efd83f08322403e876194d9bc514606e1be2a91326e94d7d18b9d398c625df91a5df1e5ba73ff44c2a54c5238e1b04c00b876072215 + sqlstring: "npm:^2.3.3" + checksum: 10/b44cff80bb804b56eec422cce65555ea3879d8bb1ccc0e1e26df09c8300167a123396e8591da09c1f8769d16d498f9aa454d0b0a11e53235cfd403f0f6597ba2 languageName: node linkType: hard @@ -40184,9 +40229,9 @@ __metadata: linkType: hard "nodemailer@npm:^7.0.7": - version: 7.0.12 - resolution: "nodemailer@npm:7.0.12" - checksum: 10/31a2713be588fb6849d05698cb355a88902a64e17e63b1a5c8a10db3acc4349c329c3bbd83bc5bdefda92f8df5e0297364502f93042c0bae87c48e5652b663b7 + version: 7.0.13 + resolution: "nodemailer@npm:7.0.13" + checksum: 10/4ae37bfb6896f1555de150c1098a7c1b28eb1da240a4c7adede81a8af0dcc184298cfbf804930599c076ea574428e0c18c6b28f0dc2072cebb30c1da5fd0b688 languageName: node linkType: hard @@ -42179,27 +42224,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.57.0": - version: 1.57.0 - resolution: "playwright-core@npm:1.57.0" +"playwright-core@npm:1.58.0": + version: 1.58.0 + resolution: "playwright-core@npm:1.58.0" bin: playwright-core: cli.js - checksum: 10/ec066602f0196f036006caee14a30d0a57533a76673bb9a0c609ef56e21decf018f0e8d402ba2fb18251393be6a1c9e193c83266f1670fe50838c5340e220de0 + checksum: 10/718549cdcd22e55f42887e7b832276c9a50e112b278d22a2242bb00c401021623fedf9554c7090f132e389b5bbe11a987ce71e3c877e767eed4dd9bfe573019c languageName: node linkType: hard -"playwright@npm:1.57.0": - version: 1.57.0 - resolution: "playwright@npm:1.57.0" +"playwright@npm:1.58.0": + version: 1.58.0 + resolution: "playwright@npm:1.58.0" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.57.0" + playwright-core: "npm:1.58.0" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/241559210f98ef11b6bd6413f2d29da7ef67c7865b72053192f0d164fab9e0d3bd47913b3351d5de6433a8aff2d8424d4b8bd668df420bf4dda7ae9fcd37b942 + checksum: 10/5ef5d0977906046400cee9a359f18c87eafb3e3193a33a22351cc84740c0dffb3cff7b9f2320d1e200817dbf24e63b747a0546b6c671a9fd95937e2402682ad9 languageName: node linkType: hard @@ -45491,6 +45536,7 @@ __metadata: "@backstage/eslint-plugin": "workspace:*" "@backstage/repo-tools": "workspace:*" "@changesets/cli": "npm:^2.14.0" + "@devcontainers/cli": "npm:^0.81.1" "@jest/environment-jsdom-abstract": "npm:^30" "@manypkg/get-packages": "npm:^1.1.3" "@octokit/rest": "npm:^19.0.3" @@ -46836,10 +46882,10 @@ __metadata: languageName: node linkType: hard -"sqlstring@npm:^2.3.2": - version: 2.3.2 - resolution: "sqlstring@npm:2.3.2" - checksum: 10/56fd2851ce6054f1185722bf7da4dbbd1ed24af64073ce58f142d5f6c62451d4a6dbad80999880debcdf764b37340f8f93155d641983e619a13abe5531849255 +"sqlstring@npm:^2.3.3": + version: 2.3.3 + resolution: "sqlstring@npm:2.3.3" + checksum: 10/4e5a25af2d77a031fe00694034bf9fd822ddc3a483c9383124b120aa6b9ae9ab71e173cd29fba9c653998ebfef9e97be668957839960b9b3dc1afcb45f1ddb64 languageName: node linkType: hard @@ -47936,15 +47982,15 @@ __metadata: linkType: hard "tar@npm:^7.4.3, tar@npm:^7.5.6": - version: 7.5.6 - resolution: "tar@npm:7.5.6" + version: 7.5.7 + resolution: "tar@npm:7.5.7" dependencies: "@isaacs/fs-minipass": "npm:^4.0.0" chownr: "npm:^3.0.0" minipass: "npm:^7.1.2" minizlib: "npm:^3.1.0" yallist: "npm:^5.0.0" - checksum: 10/cf4a84d79b9327fcf765f2ea16de4702b9b8dd7dc6b1840b16e0f999628d96b81b2c7efbf83d4eb42b0164856f1db887a5a61ffef97d36cdb77cac742219f9ee + checksum: 10/0d6938dd32fe5c0f17c8098d92bd9889ee0ed9d11f12381b8146b6e8c87bb5aa49feec7abc42463f0597503d8e89e4c4c0b42bff1a5a38444e918b4878b7fd21 languageName: node linkType: hard @@ -49295,9 +49341,9 @@ __metadata: linkType: hard "undici@npm:^7.16.0, undici@npm:^7.2.3": - version: 7.19.0 - resolution: "undici@npm:7.19.0" - checksum: 10/5a60b11ed23b2eea74e71123595ffe2b91005b80295051e1ba89119ee9b547ea09c395fd992d7609e0507452177853a8c0405d1a9e7938b2e17292b7aeaba6b0 + version: 7.19.2 + resolution: "undici@npm:7.19.2" + checksum: 10/26a01804402e5f09d02a5d09607e42096828698dddbcf32132dd313af4fda99f3c04ed84d884e28528f945a5b77d87df5c01ec6c1bdc918fd28878cb35f1cb4a languageName: node linkType: hard @@ -50392,51 +50438,6 @@ __metadata: languageName: node linkType: hard -"webpack-dev-server@npm:5.2.2": - version: 5.2.2 - resolution: "webpack-dev-server@npm:5.2.2" - dependencies: - "@types/bonjour": "npm:^3.5.13" - "@types/connect-history-api-fallback": "npm:^1.5.4" - "@types/express": "npm:^4.17.21" - "@types/express-serve-static-core": "npm:^4.17.21" - "@types/serve-index": "npm:^1.9.4" - "@types/serve-static": "npm:^1.15.5" - "@types/sockjs": "npm:^0.3.36" - "@types/ws": "npm:^8.5.10" - ansi-html-community: "npm:^0.0.8" - bonjour-service: "npm:^1.2.1" - chokidar: "npm:^3.6.0" - colorette: "npm:^2.0.10" - compression: "npm:^1.7.4" - connect-history-api-fallback: "npm:^2.0.0" - express: "npm:^4.21.2" - graceful-fs: "npm:^4.2.6" - http-proxy-middleware: "npm:^2.0.9" - ipaddr.js: "npm:^2.1.0" - launch-editor: "npm:^2.6.1" - open: "npm:^10.0.3" - p-retry: "npm:^6.2.0" - schema-utils: "npm:^4.2.0" - selfsigned: "npm:^2.4.1" - serve-index: "npm:^1.9.1" - sockjs: "npm:^0.3.24" - spdy: "npm:^4.0.2" - webpack-dev-middleware: "npm:^7.4.2" - ws: "npm:^8.18.0" - peerDependencies: - webpack: ^5.0.0 - peerDependenciesMeta: - webpack: - optional: true - webpack-cli: - optional: true - bin: - webpack-dev-server: bin/webpack-dev-server.js - checksum: 10/59517409cd38c01a875a03b9658f3d20d492b5b8bead9ded4a0f3d33e6857daf2d352fe89f0181dcaea6d0fbe84b0494cb4750a87120fe81cdbb3c32b499451c - languageName: node - linkType: hard - "webpack-dev-server@npm:^5.0.0": version: 5.2.3 resolution: "webpack-dev-server@npm:5.2.3"