diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c8325337e6f4..16e6a3ca298bb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -107,6 +107,13 @@ airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/ @Lee-W @jason810496 @guan # Generated metadata /generated/provider_metadata.json @potiuk @eladkal @shahar1 @vincbeck @jscheffl +# Provider Registry +/registry/ @kaxil +/dev/registry/ @kaxil +/dev/breeze/src/airflow_breeze/commands/registry_commands*.py @kaxil +/dev/breeze/src/airflow_breeze/utils/publish_registry_versions.py @kaxil +/dev/breeze/tests/test_publish_registry_versions.py @kaxil + # Dev tools /.github/workflows/ @potiuk @ashb @gopidesupavan @amoghrajesh @jscheffl @bugraoz93 @kaxil @jason810496 /dev/ @potiuk @ashb @gopidesupavan @amoghrajesh @jscheffl @bugraoz93 @jason810496 @jedcunningham @ephraimbuddy diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml index f8f6123ff3011..d754e1be0e33b 100644 --- a/.github/boring-cyborg.yml +++ b/.github/boring-cyborg.yml @@ -557,6 +557,14 @@ labelPRBasedOnFilePath: area:kubernetes-tests: - kubernetes-tests/**/* + area:registry: + - registry/**/* + - dev/registry/**/* + - dev/breeze/src/airflow_breeze/commands/registry_commands*.py + - dev/breeze/src/airflow_breeze/utils/publish_registry_versions.py + - dev/breeze/tests/test_publish_registry_versions.py + - .github/workflows/registry-* + # Various Flags to control behaviour of the "Labeler" labelerFlags: # If this flag is changed to 'false', labels would only be added when the PR is first created diff --git a/.github/workflows/publish-docs-to-s3.yml b/.github/workflows/publish-docs-to-s3.yml index 5be3095b8d75c..4e8d798d57a6f 100644 --- a/.github/workflows/publish-docs-to-s3.yml +++ b/.github/workflows/publish-docs-to-s3.yml @@ -93,6 +93,7 @@ jobs: # yamllint disable rule:line-length skip-write-to-stable-folder: ${{ inputs.skip-write-to-stable-folder && '--skip-write-to-stable-folder' || '' }} default-python-version: "3.10" + registry-providers: ${{ steps.parameters.outputs.registry-providers }} if: contains(fromJSON('[ "ashb", "bugraoz93", @@ -160,6 +161,19 @@ jobs: echo "airflow-version=no-airflow" >> ${GITHUB_OUTPUT} echo "airflow-base-version=no-airflow" >> ${GITHUB_OUTPUT} fi + # Extract provider IDs for registry update + # include-docs contains package names like "apache-airflow-providers-amazon" + # Strip the "apache-airflow-providers-" prefix to get provider IDs + REGISTRY_PROVIDERS="" + for pkg in ${INCLUDE_DOCS}; do + case "${pkg}" in + apache-airflow-providers-*) + PROVIDER_ID="${pkg#apache-airflow-providers-}" + REGISTRY_PROVIDERS="${REGISTRY_PROVIDERS:+${REGISTRY_PROVIDERS} }${PROVIDER_ID}" + ;; + esac + done + echo "registry-providers=${REGISTRY_PROVIDERS}" >> ${GITHUB_OUTPUT} build-docs: needs: [build-info] @@ -304,7 +318,7 @@ jobs: with: name: airflow-docs path: /mnt/_build - - name: "Move doscs to generated folder" + - name: "Move docs to generated folder" run: mv /mnt/_build generated/_build - name: "Make sure SBOM dir exists and has the right permissions" run: | @@ -377,3 +391,15 @@ jobs: breeze release-management publish-docs-to-s3 --source-dir-path ${SOURCE_DIR_PATH} \ --destination-location ${DESTINATION_LOCATION} --stable-versions \ --exclude-docs "${EXCLUDE_DOCS}" --overwrite ${SKIP_WRITE_TO_STABLE_FOLDER} + + update-registry: + needs: [publish-docs-to-s3, build-info] + if: needs.build-info.outputs.registry-providers != '' + name: "Update Provider Registry" + uses: ./.github/workflows/registry-build.yml + with: + destination: ${{ needs.build-info.outputs.destination }} + provider: ${{ needs.build-info.outputs.registry-providers }} + secrets: + DOCS_AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_AWS_ACCESS_KEY_ID }} + DOCS_AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/registry-build.yml b/.github/workflows/registry-build.yml new file mode 100644 index 0000000000000..27a1713629ca6 --- /dev/null +++ b/.github/workflows/registry-build.yml @@ -0,0 +1,232 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +--- +name: Build & Publish Registry +on: # yamllint disable-line rule:truthy + workflow_dispatch: + inputs: + destination: + description: "Publish to live or staging S3 bucket" + required: true + type: choice + options: + - staging + - live + default: staging + provider: + description: "Provider ID(s) for incremental build (space-separated, empty = full build)" + required: false + type: string + default: "" + workflow_call: + inputs: + destination: + description: "Publish to live or staging S3 bucket" + required: false + type: string + default: staging + provider: + description: "Provider ID(s) for incremental build (space-separated, empty = full build)" + required: false + type: string + default: "" + secrets: + DOCS_AWS_ACCESS_KEY_ID: + required: true + DOCS_AWS_SECRET_ACCESS_KEY: + required: true + +permissions: + contents: read + +jobs: + build-and-publish-registry: + timeout-minutes: 30 + name: "Build & Publish Registry" + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + if: > + github.event_name == 'workflow_call' || + contains(fromJSON('[ + "ashb", + "bugraoz93", + "eladkal", + "ephraimbuddy", + "jedcunningham", + "jscheffl", + "kaxil", + "pierrejeambrun", + "shahar1", + "potiuk", + "utkarsharma2", + "vincbeck" + ]'), github.event.sender.login) + steps: + - name: "Checkout repository" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + # --- Breeze setup --- + # All three extraction scripts run inside breeze so that + # extract_parameters.py and extract_connections.py can import provider + # classes at runtime. extract_metadata.py also runs in breeze for + # consistency — it writes to dev/registry/ (mounted) so the other two + # scripts can read providers.json / modules.json from there. + - name: "Install Breeze" + uses: ./.github/actions/breeze + with: + python-version: "3.12" + + - name: "Build CI image" + # Fallback to raw docker buildx when breeze cache is stale — same + # pattern as publish-docs-to-s3.yml. + run: > + breeze ci-image build --python 3.12 || + docker buildx build --load --builder default --progress=auto --pull + --build-arg AIRFLOW_EXTRAS=devel-ci --build-arg AIRFLOW_PRE_CACHED_PIP_PACKAGES=false + --build-arg AIRFLOW_USE_UV=true --build-arg BUILD_PROGRESS=auto + --build-arg INSTALL_MYSQL_CLIENT_TYPE=mariadb + --build-arg VERSION_SUFFIX_FOR_PYPI=dev0 + -t ghcr.io/apache/airflow/main/ci/python3.12:latest --target main . + -f Dockerfile.ci --platform linux/amd64 + + - name: "Install AWS CLI v2" + run: | + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip + unzip -q /tmp/awscliv2.zip -d /tmp + rm /tmp/awscliv2.zip + sudo /tmp/aws/install --update + rm -rf /tmp/aws/ + + - name: "Configure AWS credentials" + uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 + with: + aws-access-key-id: ${{ secrets.DOCS_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.DOCS_AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-2 + + - name: "Determine S3 destination" + id: destination + env: + DESTINATION: ${{ inputs.destination || 'staging' }} + run: | + if [[ "${DESTINATION}" == "live" ]]; then + echo "bucket=s3://live-docs-airflow-apache-org/registry/" >> "${GITHUB_OUTPUT}" + else + echo "bucket=s3://staging-docs-airflow-apache-org/registry/" >> "${GITHUB_OUTPUT}" + fi + echo "Publishing registry to ${DESTINATION}" + + # --- Incremental: download existing data from S3 --- + - name: "Download existing registry data from S3" + id: download-existing + if: inputs.provider != '' + env: + S3_BUCKET: ${{ steps.destination.outputs.bucket }} + run: | + mkdir -p /tmp/existing-registry + PROVIDERS_URL="${S3_BUCKET}api/providers.json" + MODULES_URL="${S3_BUCKET}api/modules.json" + if aws s3 cp "${PROVIDERS_URL}" /tmp/existing-registry/providers.json 2>/dev/null; then + echo "found=true" >> "${GITHUB_OUTPUT}" + aws s3 cp "${MODULES_URL}" /tmp/existing-registry/modules.json || true + else + echo "found=false" >> "${GITHUB_OUTPUT}" + echo "No existing registry data on S3" + fi + + # --- Extract provider metadata --- + - name: "Extract registry data (breeze)" + env: + PROVIDER: ${{ inputs.provider }} + run: | + if [[ -n "${PROVIDER}" ]]; then + breeze registry extract-data --python 3.12 --provider "${PROVIDER}" + else + breeze registry extract-data --python 3.12 + fi + + # --- Incremental: merge new data with existing --- + - name: "Merge with existing registry data" + if: inputs.provider != '' && steps.download-existing.outputs.found == 'true' + run: | + uv run dev/registry/merge_registry_data.py \ + --existing-providers /tmp/existing-registry/providers.json \ + --existing-modules /tmp/existing-registry/modules.json \ + --new-providers dev/registry/providers.json \ + --new-modules dev/registry/modules.json \ + --output dev/registry/ + + - name: "Copy breeze output to registry data" + run: | + mkdir -p registry/src/_data/versions + cp dev/registry/providers.json registry/src/_data/providers.json + cp dev/registry/modules.json registry/src/_data/modules.json + if [ -d dev/registry/output/versions ]; then + cp -r dev/registry/output/versions/* registry/src/_data/versions/ + fi + # Copy provider logos extracted from providers/*/docs/integration-logos/ + if [ -d dev/registry/logos ]; then + mkdir -p registry/public/logos + cp -r dev/registry/logos/* registry/public/logos/ + fi + + - name: "Setup pnpm" + uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + with: + version: 9 + + - name: "Setup Node.js" + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 20 + cache: 'pnpm' + cache-dependency-path: 'registry/pnpm-lock.yaml' + + - name: "Install Node.js dependencies" + working-directory: registry + run: pnpm install --frozen-lockfile + + - name: "Build registry site" + working-directory: registry + env: + REGISTRY_PATH_PREFIX: "/registry/" + run: pnpm build + + - name: "Upload registry artifact" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: registry-site + path: registry/_site + retention-days: 7 + if-no-files-found: error + + - name: "Sync registry to S3" + env: + S3_BUCKET: ${{ steps.destination.outputs.bucket }} + run: | + aws s3 sync registry/_site/ "${S3_BUCKET}" \ + --cache-control "public, max-age=300" + + - name: "Publish version metadata" + env: + S3_BUCKET: ${{ steps.destination.outputs.bucket }} + run: breeze registry publish-versions --s3-bucket "${S3_BUCKET}" diff --git a/.github/workflows/registry-tests.yml b/.github/workflows/registry-tests.yml new file mode 100644 index 0000000000000..e305d088655c9 --- /dev/null +++ b/.github/workflows/registry-tests.yml @@ -0,0 +1,64 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +--- +name: Registry Tests +on: # yamllint disable-line rule:truthy + pull_request: + branches: [main] + paths: + - 'dev/registry/**' + - 'registry/**' + - 'providers/*/provider.yaml' + - 'providers/*/*/provider.yaml' + - '.github/workflows/registry-tests.yml' + push: + branches: [main] + paths: + - 'dev/registry/**' + +permissions: + contents: read + +concurrency: + group: registry-tests-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + registry-tests: + name: "Registry extraction tests" + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: "Checkout repository" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Setup Python" + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.12" # 3.11+ required for stdlib tomllib + + - name: "Install uv" + run: python -m pip install uv + + - name: "Install test dependencies" + run: uv pip install --system pytest pyyaml + + - name: "Run registry extraction tests" + run: pytest dev/registry/tests/ -v -o "addopts=" diff --git a/.gitignore b/.gitignore index f6b75398a752f..44075d793b8f5 100644 --- a/.gitignore +++ b/.gitignore @@ -284,3 +284,16 @@ _e2e_test_report.json # Allow logs folder in task-sdk-integration-tests (they logs folders are ignored above in general) !/task-sdk-integration-tests/logs/ + +# Generated registry data (versions/{provider}/{version}/{metadata,parameters,connections}.json) +# Generated by dev/registry/extract_metadata.py +registry/src/_data/modules.json +registry/src/_data/providers.json +registry/src/_data/search-index.json +# Generated by dev/registry/extract_versions.py, dev/registry/extract_parameters.py, +# dev/registry/extract_connections.py +registry/src/_data/versions/ +dev/registry/output/ +dev/registry/logos/ +dev/registry/modules.json +dev/registry/providers.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97968c25c5c8c..f5b78f1c5d81c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -614,7 +614,7 @@ repos: git| ^helm-tests/tests/chart_utils/helm_template_generator\.py$| package-lock\.json$| - ^.*\.(png|gif|jp[e]?g|svg|tgz|lock)$| + ^.*\.(png|gif|jp[e]?g|svg|tgz|lock|woff2?)$| ^\.pre-commit-config\.yaml$| ^.*/provider_conf\.py$| ^providers/\.pre-commit-config\.yaml$| diff --git a/.rat-excludes b/.rat-excludes index bd3ef2e77633e..afee79c2ed0f3 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -240,6 +240,9 @@ www-hash.txt **/dist/** **/www/index.html +# Nunjucks template files (frontmatter must be at line 1, no room for license header) +**/*.njk + # PNG files **/*.png diff --git a/dev/README_RELEASE_PROVIDERS.md b/dev/README_RELEASE_PROVIDERS.md index 50f3f2898e507..2d6e2fda5ceb1 100644 --- a/dev/README_RELEASE_PROVIDERS.md +++ b/dev/README_RELEASE_PROVIDERS.md @@ -1332,6 +1332,13 @@ Or if you just want to publish a few selected providers, you can run: There is also a manual way of running the workflows (see at the end of the document, this should normally not be needed unless there is some problem with workflow automation above) +> [!NOTE] +> The **Provider Registry** at `airflow.apache.org/registry/` is rebuilt automatically as part of the +> `publish-docs-to-s3.yml` workflow (it calls `registry-build.yml` as a post-publish job). The registry +> extracts metadata from `provider.yaml` files and PyPI, so it picks up new/updated providers without +> manual intervention. If you need to rebuild the registry independently, trigger the `registry-build.yml` +> workflow manually. See [`registry/README.md`](../registry/README.md) for details. + ## Update providers metadata Create PR and open it to be merged: diff --git a/dev/breeze/doc/10_ui_tasks.rst b/dev/breeze/doc/10_ui_tasks.rst index 904fb5f289115..974a21dfccf2f 100644 --- a/dev/breeze/doc/10_ui_tasks.rst +++ b/dev/breeze/doc/10_ui_tasks.rst @@ -89,5 +89,5 @@ Example usage: ----- -Next step: Follow the `Issues tasks <11_issues_tasks.rst>`__ instructions to learn about -Breeze commands for managing GitHub issues. +Next step: Follow the `Registry tasks <11_registry_tasks.rst>`__ instructions to learn about +the provider registry commands. diff --git a/dev/breeze/doc/11_registry_tasks.rst b/dev/breeze/doc/11_registry_tasks.rst new file mode 100644 index 0000000000000..acbd90ffeacd6 --- /dev/null +++ b/dev/breeze/doc/11_registry_tasks.rst @@ -0,0 +1,85 @@ + .. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + +Registry tasks +-------------- + +Breeze commands for building the Apache Airflow Provider Registry. + +These are all of the available registry commands: + +.. image:: ./images/output_registry.svg + :target: https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/doc/images/output_registry.svg + :width: 100% + :alt: Breeze registry commands + +Extracting registry data +........................ + +The ``breeze registry extract-data`` command runs the three extraction scripts +(``extract_metadata.py``, ``extract_parameters.py``, ``extract_connections.py``) +inside a breeze CI container where all providers are installed. This is the same +command used by the ``registry-build.yml`` CI workflow. + +.. image:: ./images/output_registry_extract-data.svg + :target: https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/doc/images/output_registry_extract-data.svg + :width: 100% + :alt: Breeze registry extract-data + +Example usage: + +.. code-block:: bash + + # Extract all registry data with default Python version + breeze registry extract-data + + # Extract with a specific Python version + breeze registry extract-data --python 3.12 + +Publishing version metadata +.......................... + +The ``breeze registry publish-versions`` command lists S3 directories under +``providers/{id}/`` to discover every deployed version, then writes +``api/providers/{id}/versions.json`` for each provider. It also invalidates +the CloudFront cache for the staging or live distribution. + +This is the same command used by the ``registry-build.yml`` CI workflow after +syncing the built site to S3. + +.. image:: ./images/output_registry_publish-versions.svg + :target: https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/doc/images/output_registry_publish-versions.svg + :width: 100% + :alt: Breeze registry publish-versions + +Example usage: + +.. code-block:: bash + + # Publish to staging + breeze registry publish-versions --s3-bucket s3://staging-docs-airflow-apache-org/registry/ + + # Publish to live + breeze registry publish-versions --s3-bucket s3://live-docs-airflow-apache-org/registry/ + + # With a custom providers.json + breeze registry publish-versions --s3-bucket s3://bucket/registry/ --providers-json path/to/providers.json + +----- + +Next step: Follow the `Advanced Breeze topics <13_advanced_breeze_topics.rst>`__ instructions to learn more +about advanced Breeze topics and internals. diff --git a/dev/breeze/doc/11_issues_tasks.rst b/dev/breeze/doc/12_issues_tasks.rst similarity index 100% rename from dev/breeze/doc/11_issues_tasks.rst rename to dev/breeze/doc/12_issues_tasks.rst diff --git a/dev/breeze/doc/12_advanced_breeze_topics.rst b/dev/breeze/doc/13_advanced_breeze_topics.rst similarity index 100% rename from dev/breeze/doc/12_advanced_breeze_topics.rst rename to dev/breeze/doc/13_advanced_breeze_topics.rst diff --git a/dev/breeze/doc/README.rst b/dev/breeze/doc/README.rst index 03df38bae1c4a..473c679b790d0 100644 --- a/dev/breeze/doc/README.rst +++ b/dev/breeze/doc/README.rst @@ -49,8 +49,9 @@ The following documents describe how to use the Breeze environment: * `CI tasks <08_ci_tasks.rst>`_ - describes how Breeze commands are used in our CI. * `Release management tasks <09_release_management_tasks.rst>`_ - describes how to use Breeze for release management tasks. * `UI tasks <10_ui_tasks.rst>`_ - describes how Breeze commands are used to support Apache Airflow project UI. -* `Issues tasks <11_issues_tasks.rst>`_ - describes how Breeze commands are used to manage GitHub issues. -* `Advanced Breeze topics <12_advanced_breeze_topics.rst>`_ - describes advanced Breeze topics/internals of Breeze. +* `Registry tasks <11_registry_tasks.rst>`_ - describes how to use Breeze for provider registry data extraction. +* `Issues tasks <12_issues_tasks.rst>`_ - describes how Breeze commands are used to manage GitHub issues. +* `Advanced Breeze topics <13_advanced_breeze_topics.rst>`_ - describes advanced Breeze topics/internals of Breeze. You can also learn more context and Architecture Decisions taken when developing Breeze in the `Architecture Decision Records `_. diff --git a/dev/breeze/doc/images/output-commands.svg b/dev/breeze/doc/images/output-commands.svg index f970ad85743ea..263431946505b 100644 --- a/dev/breeze/doc/images/output-commands.svg +++ b/dev/breeze/doc/images/output-commands.svg @@ -1,4 +1,4 @@ - +
...
+
...
+ +``` + +```css +.technical { /* section styles */ } +.technical h3 { /* heading styles */ } +.technical .python-version { /* specific section */ } +.technical code { /* all code in technical section */ } +``` + +**Bad - redundant prefixes:** + +```html +
+
...
+
...
+
+``` + +```css +.technical { /* section styles */ } +.technical-heading { /* redundant prefix */ } +.technical-python { /* redundant prefix */ } +.technical-code { /* redundant prefix */ } +``` + +**Why this matters:** + +- Less typing, less repetition +- Clearer hierarchy in CSS +- If `.python-version` only appears in `.technical`, the prefix adds no information + +#### 2.3 Leverage Semantic HTML5 Elements + +Use the right HTML element for the job. This improves accessibility, SEO, and reduces need for classes. + +**Semantic elements and their uses:** + +- `
` - Page or section header +- `
` - Page or section footer +- `
+ +
+ + + +
+
+ + + +

Start typing to search...

+
+
+ + + + + + + + + + + diff --git a/registry/src/_includes/provider-card.njk b/registry/src/_includes/provider-card.njk new file mode 100644 index 0000000000000..029a0319f2dfb --- /dev/null +++ b/registry/src/_includes/provider-card.njk @@ -0,0 +1,62 @@ + + + +
+
+

{{ provider.name }}

+ {% set lc = provider.lifecycle or "production" %} + {% set lcDisplay = "incubation" if lc == "incubation" else ("deprecated" if lc == "deprecated" else "stable") %} + {% if lcDisplay != "stable" %} + {{ lcDisplay }} + {% endif %} +
+ +

{{ provider.package_name }}

+

{{ provider.description }}

+ +
+ {% set counts = provider.module_counts or {} %} + {% set totalMods = (counts.operator or 0) + (counts.hook or 0) + (counts.sensor or 0) + (counts.trigger or 0) + (counts.transfer or 0) + (counts.notifier or 0) + (counts.secret or 0) + (counts.logging or 0) + (counts.executor or 0) + (counts.bundle or 0) + (counts.decorator or 0) %} + {{ totalMods }} module{{ "s" if totalMods != 1 }} + {% if totalMods > 0 %} +
+ {% for mtype in ["operator", "hook", "sensor", "trigger", "transfer", "executor", "notifier", "secret", "logging", "bundle", "decorator"] %} + {% if counts[mtype] %} +
+ {% endif %} + {% endfor %} +
+ {% endif %} +
+ +
+
+ + + + {{ provider.pypi_downloads.monthly | formatDownloads }}/mo +
+ + {% if provider.airflow_versions %} +
+ {% for version in provider.airflow_versions | slice(0, 2) %} + {{ version }} + {% endfor %} + {% if provider.airflow_versions.length > 2 %} + +{{ provider.airflow_versions.length - 2 }} + {% endif %} +
+ {% endif %} +
+
+ + + + +
diff --git a/registry/src/api/modules-json.njk b/registry/src/api/modules-json.njk new file mode 100644 index 0000000000000..31425dc465de0 --- /dev/null +++ b/registry/src/api/modules-json.njk @@ -0,0 +1,5 @@ +--- +permalink: "/api/modules.json" +eleventyExcludeFromCollections: true +--- +{{ modules | dump | safe }} diff --git a/registry/src/api/provider-connections.njk b/registry/src/api/provider-connections.njk new file mode 100644 index 0000000000000..39e9b361d78af --- /dev/null +++ b/registry/src/api/provider-connections.njk @@ -0,0 +1,13 @@ +--- +pagination: + data: providers.providers + size: 1 + alias: provider +permalink: "/api/providers/{{ provider.id }}/connections.json" +eleventyExcludeFromCollections: true +--- +{%- if latestVersionData[provider.id].connections -%} +{{ latestVersionData[provider.id].connections | dump | safe }} +{%- else -%} +{"provider_id":"{{ provider.id }}","provider_name":"{{ provider.name }}","version":"{{ provider.version }}","generated_at":null,"connection_types":[]} +{%- endif -%} diff --git a/registry/src/api/provider-modules.njk b/registry/src/api/provider-modules.njk new file mode 100644 index 0000000000000..cfce0bd28dbd4 --- /dev/null +++ b/registry/src/api/provider-modules.njk @@ -0,0 +1,15 @@ +--- +pagination: + data: providers.providers + size: 1 + alias: provider +permalink: "/api/providers/{{ provider.id }}/modules.json" +eleventyExcludeFromCollections: true +--- +{%- set providerModules = [] -%} +{%- for module in modules.modules -%} + {%- if module.provider_id == provider.id -%} + {%- set providerModules = (providerModules.push(module), providerModules) -%} + {%- endif -%} +{%- endfor -%} +{{ { "provider_id": provider.id, "provider_name": provider.name, "version": provider.version, "modules": providerModules } | dump | safe }} diff --git a/registry/src/api/provider-parameters.njk b/registry/src/api/provider-parameters.njk new file mode 100644 index 0000000000000..4557fd1253b5a --- /dev/null +++ b/registry/src/api/provider-parameters.njk @@ -0,0 +1,13 @@ +--- +pagination: + data: providers.providers + size: 1 + alias: provider +permalink: "/api/providers/{{ provider.id }}/parameters.json" +eleventyExcludeFromCollections: true +--- +{%- if latestVersionData[provider.id].parameters -%} +{{ latestVersionData[provider.id].parameters | dump | safe }} +{%- else -%} +{"provider_id":"{{ provider.id }}","provider_name":"{{ provider.name }}","version":"{{ provider.version }}","generated_at":null,"classes":{}} +{%- endif -%} diff --git a/registry/src/api/provider-version-connections.njk b/registry/src/api/provider-version-connections.njk new file mode 100644 index 0000000000000..f21d72afb6204 --- /dev/null +++ b/registry/src/api/provider-version-connections.njk @@ -0,0 +1,13 @@ +--- +pagination: + data: providerVersions + size: 1 + alias: pv +permalink: "/api/providers/{{ pv.provider.id }}/{{ pv.version }}/connections.json" +eleventyExcludeFromCollections: true +--- +{%- if pv.connections -%} +{{ pv.connections | dump | safe }} +{%- else -%} +{"provider_id":"{{ pv.provider.id }}","provider_name":"{{ pv.provider.name }}","version":"{{ pv.version }}","generated_at":null,"connection_types":[]} +{%- endif -%} diff --git a/registry/src/api/provider-version-modules.njk b/registry/src/api/provider-version-modules.njk new file mode 100644 index 0000000000000..0a9e545112530 --- /dev/null +++ b/registry/src/api/provider-version-modules.njk @@ -0,0 +1,9 @@ +--- +pagination: + data: providerVersions + size: 1 + alias: pv +permalink: "/api/providers/{{ pv.provider.id }}/{{ pv.version }}/modules.json" +eleventyExcludeFromCollections: true +--- +{{ { "provider_id": pv.provider.id, "provider_name": pv.provider.name, "version": pv.version, "modules": pv.modules } | dump | safe }} diff --git a/registry/src/api/provider-version-parameters.njk b/registry/src/api/provider-version-parameters.njk new file mode 100644 index 0000000000000..a3aca35b5b582 --- /dev/null +++ b/registry/src/api/provider-version-parameters.njk @@ -0,0 +1,13 @@ +--- +pagination: + data: providerVersions + size: 1 + alias: pv +permalink: "/api/providers/{{ pv.provider.id }}/{{ pv.version }}/parameters.json" +eleventyExcludeFromCollections: true +--- +{%- if pv.parameters -%} +{{ pv.parameters | dump | safe }} +{%- else -%} +{"provider_id":"{{ pv.provider.id }}","provider_name":"{{ pv.provider.name }}","version":"{{ pv.version }}","generated_at":null,"classes":{}} +{%- endif -%} diff --git a/registry/src/api/providers-json.njk b/registry/src/api/providers-json.njk new file mode 100644 index 0000000000000..3d0db30d15512 --- /dev/null +++ b/registry/src/api/providers-json.njk @@ -0,0 +1,5 @@ +--- +permalink: "/api/providers.json" +eleventyExcludeFromCollections: true +--- +{{ providers | dump | safe }} diff --git a/registry/src/css/base/reset.css b/registry/src/css/base/reset.css new file mode 100644 index 0000000000000..946026cdeae57 --- /dev/null +++ b/registry/src/css/base/reset.css @@ -0,0 +1,72 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* Modern CSS Reset */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + min-height: 100vh; + line-height: var(--leading-normal); +} + +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +input, +button, +textarea, +select { + font: inherit; +} + +p, h1, h2, h3, h4, h5, h6 { + overflow-wrap: break-word; +} + +button { + cursor: pointer; + border: none; + background: none; +} + +a { + text-decoration: none; + color: inherit; +} + +ul, ol { + list-style: none; +} diff --git a/registry/src/css/base/typography.css b/registry/src/css/base/typography.css new file mode 100644 index 0000000000000..87b8e51a712ac --- /dev/null +++ b/registry/src/css/base/typography.css @@ -0,0 +1,84 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* Typography */ +:root { + color-scheme: light dark; +} + +body { + font-family: var(--font-sans); + font-size: var(--text-base); + font-size-adjust: 0.5; + color: var(--text-primary); + background-color: var(--bg-primary); +} + +h1, h2, h3, h4, h5, h6 { + font-weight: var(--font-bold); + line-height: var(--leading-tight); + color: var(--text-primary); +} + +h1 { + font-size: var(--text-4xl); +} + +h2 { + font-size: var(--text-2xl); +} + +h3 { + font-size: var(--text-xl); +} + +h4 { + font-size: var(--text-lg); +} + +@media (min-width: 640px) { + h1 { + font-size: var(--text-5xl); + } +} + +@media (min-width: 1024px) { + h1 { + font-size: var(--text-6xl); + } +} + +/* No utility classes - use semantic CSS instead */ + +.font-mono { + font-family: var(--font-mono); +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/registry/src/css/main.css b/registry/src/css/main.css new file mode 100644 index 0000000000000..11dfe1f06a333 --- /dev/null +++ b/registry/src/css/main.css @@ -0,0 +1,4368 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* Tokens & Base — @import must precede all style declarations */ +@import './tokens.css'; +@import './base/reset.css'; +@import './base/typography.css'; + +/* Self-hosted fonts (previously loaded from Google Fonts CDN) */ +@font-face { + font-family: 'Plus Jakarta Sans'; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url('../fonts/plus-jakarta-sans-latin.woff2') format('woff2'); +} + +@font-face { + font-family: 'JetBrains Mono'; + font-style: normal; + font-weight: 400 600; + font-display: swap; + src: url('../fonts/jetbrains-mono-latin.woff2') format('woff2'); +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 0.5rem; +} + +::-webkit-scrollbar-track { + background: var(--color-scrollbar-track); +} + +::-webkit-scrollbar-thumb { + background: var(--color-scrollbar-thumb); + border-radius: var(--radius-full); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-scrollbar-thumb-hover); +} + +/* Layout */ +.container { + width: 100%; + max-width: 1280px; + margin-left: auto; + margin-right: auto; + padding-left: var(--space-4); + padding-right: var(--space-4); +} + +@media (min-width: 640px) { + .container { + padding-left: var(--space-6); + padding-right: var(--space-6); + } +} + +@media (min-width: 1024px) { + .container { + padding-left: var(--space-8); + padding-right: var(--space-8); + } +} + +/* Header */ +#site-header { + position: sticky; + top: 0; + z-index: 50; + background: var(--color-header-bg); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--color-header-border); +} + +#site-header .container { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); + height: 4rem; +} + +#site-header > .container > a { + display: flex; + align-items: center; + gap: var(--space-2); + transition: opacity var(--transition-base); +} + +#site-header > .container > a:hover { + opacity: 0.8; +} + +#site-header img { + height: 2rem; +} + +#site-header .logo-dark { + display: none; +} + +:root[style*="color-scheme: dark"] #site-header .logo-light { + display: none; +} + +:root[style*="color-scheme: dark"] #site-header .logo-dark { + display: block; +} + +@media (prefers-color-scheme: dark) { + #site-header .logo-light { + display: none; + } + #site-header .logo-dark { + display: block; + } +} + +:root[style*="color-scheme: light"] #site-header .logo-light { + display: block; +} + +:root[style*="color-scheme: light"] #site-header .logo-dark { + display: none; +} + +#site-header .wordmark::before { + content: "|"; + margin-right: var(--space-2); + font-weight: var(--font-medium); + opacity: 0.6; +} + +#site-header .wordmark { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +#site-header nav { + display: none; + align-items: center; + gap: var(--space-6); + margin-left: auto; + margin-right: auto; +} + +@media (min-width: 768px) { + #site-header nav { + display: flex; + } +} + +#site-header nav a { + color: var(--color-nav-link); + transition: color var(--transition-base); +} + +#site-header nav a:hover { + color: var(--text-primary); +} + +/* Search Buttons */ +.search { + display: flex; + align-items: center; + gap: var(--space-2); + border: 1px solid; + border-radius: var(--radius-lg); + color: var(--text-secondary); + transition: all var(--transition-base); +} + +.search svg { + flex-shrink: 0; + color: var(--text-muted); + transition: color var(--transition-base); +} + +/* Header Search */ +#search-trigger { + display: none; + padding: 0.375rem var(--space-2); + background: var(--color-input-bg); + border-color: var(--color-input-border); +} + +.js #search-trigger { + display: flex; +} + +@media (min-width: 640px) { + #search-trigger { + padding: 0.375rem var(--space-3); + } +} + +#search-trigger:hover { + border-color: light-dark(var(--color-gray-400), rgba(6, 182, 212, 0.5)); +} + +#search-trigger svg { + width: 1rem; + height: 1rem; +} + +#search-trigger > span:not(.kbd) { + display: none; + font-size: var(--text-sm); +} + +@media (min-width: 640px) { + #search-trigger > span:not(.kbd) { + display: inline; + } +} + +/* Keyboard shortcut badge */ +.kbd { + display: none; + padding: 0.125rem 0.375rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-primary-light); + border-color: light-dark(var(--border-primary-light), var(--color-navy-600)); + border-radius: var(--radius); + font-family: var(--font-mono); + font-size: var(--text-xs); + line-height: 1rem; + color: var(--text-secondary); +} + +@media (min-width: 640px) { + .kbd { + display: inline; + } +} + +/* Theme Toggle */ +#theme-toggle { + padding: var(--space-2); + background: var(--bg-tertiary); + border: 1px solid var(--border-primary-light); + border-color: light-dark(var(--border-primary-light), var(--color-gray-700)); + border-radius: var(--radius-lg); + color: var(--text-secondary); + transition: all var(--transition-base); +} + +#theme-toggle:hover { + color: var(--text-primary); + border-color: light-dark(var(--border-primary-light), rgba(6, 182, 212, 0.5)); +} + +#theme-toggle svg { + width: 1.25rem; + height: 1.25rem; +} + +/* Mobile Menu Button */ +#mobile-menu-btn { + display: block; + padding: var(--space-2); + background: var(--bg-tertiary); + border: 1px solid light-dark(var(--border-primary-light), var(--color-gray-700)); + border-radius: var(--radius-lg); + color: var(--text-secondary); +} + +@media (min-width: 768px) { + #mobile-menu-btn { + display: none; + } +} + +#mobile-menu-btn svg { + width: 1.25rem; + height: 1.25rem; +} + +/* Mobile Nav */ +#mobile-nav { + display: none; + flex-direction: column; + gap: var(--space-3); + padding-bottom: var(--space-4); + border-top: 1px solid var(--border-primary); + margin-top: var(--space-4); + padding-top: var(--space-4); +} + +@media (min-width: 768px) { + #mobile-nav { + display: none !important; + } +} + +#mobile-nav.active { + display: flex; +} + +#mobile-nav a { + padding: var(--space-2) 0; + color: var(--text-primary); +} + +/* Footer */ +footer { + background: var(--bg-primary); + border-top: 1px solid var(--border-primary); + padding: var(--space-8) 0; +} + +footer .container { + display: flex; + flex-direction: column; + gap: var(--space-6); +} + +footer .footer-top { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-4); +} + +@media (min-width: 768px) { + footer .footer-top { + flex-direction: row; + justify-content: space-between; + } +} + +footer .footer-bottom { + border-top: 1px solid var(--border-primary); + padding-top: var(--space-4); + text-align: center; +} + +footer p { + font-size: var(--text-sm); + color: var(--text-secondary); + margin: 0; +} + +footer .trademark { + font-size: var(--text-xs); + color: var(--text-tertiary); + margin-top: var(--space-2); + max-width: 60rem; + margin-inline: auto; +} + +footer .links { + display: flex; + align-items: center; + flex-wrap: wrap; + justify-content: center; + gap: var(--space-4); +} + +@media (min-width: 768px) { + footer .links { + gap: var(--space-6); + } +} + +footer a { + font-size: var(--text-sm); + color: var(--text-secondary); + transition: color var(--transition-base); +} + +footer a:hover { + color: var(--text-primary); +} + +/* Cards */ +.card { background: var(--color-card-bg); + border: 1px solid var(--border-primary-light); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + backdrop-filter: blur(4px); + transition: all var(--transition-base); +} + + +/* Buttons styled contextually where they appear */ + +/* Badges */ +.badge { + display: inline-flex; + align-items: center; + padding: var(--space-1) var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-medium); + border-radius: var(--radius-full); + border: 1px solid; +} + + +/* Lifecycle badges (AIP-95) */ +.lifecycle-incubation { + background: color-mix(in srgb, var(--color-amber-500) 15%, transparent); + color: var(--color-amber-500); + color: light-dark(var(--color-amber-600), var(--color-amber-400)); + border-color: color-mix(in srgb, var(--color-amber-500) 40%, transparent); +} + +.lifecycle-deprecated { + background: color-mix(in srgb, var(--color-gray-500) 15%, transparent); + color: var(--color-gray-500); + color: light-dark(var(--color-gray-500), var(--color-gray-400)); + border-color: color-mix(in srgb, var(--color-gray-500) 40%, transparent); +} + + +/* Hero Section */ +#hero { + position: relative; + padding: var(--space-20) 0; + overflow: hidden; + text-align: center; + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 50%, #f0fdfa 100%); +} + +@media (prefers-color-scheme: dark) { + #hero { + background: var(--bg-primary-dark); + } +} + +:root[style*="color-scheme: light"] #hero { + background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 50%, #f0fdfa 100%); +} + +:root[style*="color-scheme: dark"] #hero { + background: var(--bg-primary-dark); +} + +#hero .gradient { + position: absolute; + inset: 0; + background: linear-gradient(to bottom, color-mix(in srgb, var(--color-cyan-500) 10%, transparent), transparent); + pointer-events: none; +} + +@media (prefers-color-scheme: dark) { + #hero .gradient { + background: linear-gradient(to bottom, rgba(15, 23, 42, 0.5), transparent); + } +} + +:root[style*="color-scheme: light"] #hero .gradient { + background: linear-gradient(to bottom, color-mix(in srgb, var(--color-cyan-500) 10%, transparent), transparent); +} + +:root[style*="color-scheme: dark"] #hero .gradient { + background: linear-gradient(to bottom, rgba(15, 23, 42, 0.5), transparent); +} + +#hero .particles { + position: absolute; + inset: 0; + overflow: hidden; + pointer-events: none; +} + +#hero .particle { + position: absolute; + width: 24rem; + height: 24rem; + border-radius: var(--radius-full); + filter: blur(64px); + animation: pulse-slow 3s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse-slow { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +#hero .particle:nth-child(1) { + top: 25%; + left: 25%; + background: color-mix(in srgb, var(--color-cyan-500) 15%, transparent); +} + +@media (prefers-color-scheme: dark) { + #hero .particle:nth-child(1) { + background: color-mix(in srgb, var(--color-cyan-500) 5%, transparent); + } +} + +:root[style*="color-scheme: light"] #hero .particle:nth-child(1) { + background: color-mix(in srgb, var(--color-cyan-500) 15%, transparent); +} + +:root[style*="color-scheme: dark"] #hero .particle:nth-child(1) { + background: color-mix(in srgb, var(--color-cyan-500) 5%, transparent); +} + +#hero .particle:nth-child(2) { + bottom: 25%; + right: 25%; + background: color-mix(in srgb, var(--color-indigo-500) 15%, transparent); + animation-delay: 1.5s; +} + +@media (prefers-color-scheme: dark) { + #hero .particle:nth-child(2) { + background: color-mix(in srgb, var(--color-indigo-500) 5%, transparent); + } +} + +:root[style*="color-scheme: light"] #hero .particle:nth-child(2) { + background: color-mix(in srgb, var(--color-indigo-500) 15%, transparent); +} + +:root[style*="color-scheme: dark"] #hero .particle:nth-child(2) { + background: color-mix(in srgb, var(--color-indigo-500) 5%, transparent); +} + +#hero .container { + position: relative; + max-width: 48rem; +} + +#hero .badge { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-1) var(--space-3); + background: var(--color-widget-bg); + border: 1px solid var(--border-primary); + border-radius: var(--radius-full); + font-size: var(--text-sm); + color: var(--text-secondary); + margin-bottom: var(--space-6); +} + +#hero .badge .dot { + width: 0.5rem; + height: 0.5rem; + background: var(--color-cyan-400); + border-radius: var(--radius-full); + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +#hero h1 { + font-size: 2.25rem; + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-6); + line-height: var(--leading-none); +} + +@media (min-width: 640px) { + #hero h1 { + font-size: 3rem; + } +} + +@media (min-width: 1024px) { + #hero h1 { + font-size: 3.75rem; + } +} + +#hero h1 .highlight { + background: linear-gradient(to right, var(--color-cyan-400), var(--color-cyan-600)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +#hero .subtitle { + font-size: var(--text-lg); + color: var(--text-secondary); + margin-bottom: var(--space-8); + max-width: 32rem; + margin-left: auto; + margin-right: auto; +} + +/* Hero Search */ +#hero .search { + display: none; + width: 100%; + max-width: 36rem; + margin: 0 auto 2rem; + gap: var(--space-3); + padding: 1rem 1.25rem; + background: var(--color-widget-bg); + border-color: var(--color-gray-300); + border-color: var(--border-secondary); + border-radius: var(--radius-xl); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); + box-shadow: light-dark(0 4px 6px -1px rgba(0, 0, 0, 0.05), none); +} + +.js #hero .search { + display: flex; +} + +#hero .search:hover { + border-color: var(--color-cyan-600); + border-color: var(--color-border-focus); + box-shadow: 0 4px 12px -2px rgba(6, 182, 212, 0.2); + box-shadow: light-dark(0 4px 12px -2px rgba(6, 182, 212, 0.2), none); +} + +#hero .search:hover svg { + color: var(--color-cyan-400); +} + +#hero .search svg { + width: 1.25rem; + height: 1.25rem; +} + +#hero .search > span:not(.shortcuts) { + flex: 1; + text-align: left; +} + +#hero .search .shortcuts { + display: flex; + gap: var(--space-1); +} + +/* Stats Section */ +#stats { + padding: var(--space-12) 0; + background: linear-gradient(180deg, var(--bg-primary-light) 0%, var(--bg-secondary-light) 100%); + border-block: 1px solid var(--border-primary); +} + +/* light-dark() doesn't work for gradient, so we have to use media selectors and explicit color-scheme */ +@media (prefers-color-scheme: dark) { + #stats { + background: rgb(from var(--color-navy-800) r g b / 0.3); + } +} + +/* Need explicit light mode to override media query when user manually selects light */ +:root[style*="color-scheme: light"] #stats { + background: linear-gradient(180deg, var(--bg-primary-light) 0%, var(--bg-secondary-light) 100%); +} + +:root[style*="color-scheme: dark"] #stats { + background: rgb(from var(--color-navy-800) r g b / 0.3); +} + +#stats dl { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-8); +} + +@media (min-width: 768px) { + #stats dl { + grid-template-columns: repeat(4, 1fr); + } +} + +#stats dl > div { + text-align: center; +} + +#stats dt { + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--accent-primary); + margin-bottom: var(--space-1); +} + +#stats dd { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Provider Cards - Reusable */ +.provider-card, +#featured-providers .provider { + display: flex; + gap: var(--space-4); + padding: var(--space-5); + background: var(--color-card-bg); + border: 1px solid var(--border-primary-light); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + backdrop-filter: blur(4px); + transition: all var(--transition-base); + cursor: pointer; + position: relative; + text-decoration: none; + color: inherit; +} + +.provider-card:hover, +#featured-providers .provider:hover { + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); + box-shadow: 0 10px 40px -10px rgba(6, 182, 212, 0.15); + box-shadow: 0 10px 40px -10px light-dark(rgba(6, 182, 212, 0.15), rgba(6, 182, 212, 0.1)); +} + +.provider-card .logo, +#featured-providers .logo { + flex-shrink: 0; + width: 3rem; + height: 3rem; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-secondary); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.provider-card .logo img, +#featured-providers .logo img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.provider-card .logo .initial, +#featured-providers .logo .initial { + font-size: var(--text-xl); + font-weight: var(--font-bold); + color: var(--color-cyan-400); +} + +.provider-card .details, +#featured-providers .details { + flex: 1; + min-width: 0; +} + +.provider-card .title-row, +#featured-providers .title-row { + display: flex; + align-items: center; + gap: var(--space-2); + margin-bottom: var(--space-1); +} + +.provider-card h3, +#featured-providers h3 { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.provider-card .package, +#featured-providers .package { + font-family: var(--font-mono); + font-size: var(--text-sm); + color: var(--text-muted); + margin: 0 0 var(--space-2) 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.provider-card .description, +#featured-providers .description { + font-size: var(--text-sm); + color: var(--text-secondary); + margin: 0 0 var(--space-4) 0; + line-height: var(--leading-relaxed); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.provider-card .modules, +#featured-providers .modules { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-3); +} + +.provider-card .modules .count, +#featured-providers .modules .count { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--text-primary); + flex-shrink: 0; + min-width: var(--space-24); +} + +.provider-card .modules .breakdown, +#featured-providers .modules .breakdown { + display: flex; + flex: 1; + height: var(--space-2-5); + border-radius: var(--radius-full); + overflow: visible; + background: var(--bg-secondary); + position: relative; +} + +.provider-card .modules .breakdown > div, +#featured-providers .modules .breakdown > div { + position: relative; + cursor: default; + transition: filter var(--transition-fast); +} + +.provider-card .modules .breakdown > div:first-child, +#featured-providers .modules .breakdown > div:first-child { + border-radius: var(--radius-full) 0 0 var(--radius-full); +} + +.provider-card .modules .breakdown > div:last-child, +#featured-providers .modules .breakdown > div:last-child { + border-radius: 0 var(--radius-full) var(--radius-full) 0; +} + +.provider-card .modules .breakdown > div:only-child, +#featured-providers .modules .breakdown > div:only-child { + border-radius: var(--radius-full); +} + +.provider-card .modules .breakdown > div:hover, +#featured-providers .modules .breakdown > div:hover { + filter: brightness(1.25); +} + +.provider-card .modules .breakdown > div::after, +#featured-providers .modules .breakdown > div::after { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + var(--space-2)); + left: 50%; + transform: translateX(-50%); + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); + font-weight: var(--font-semibold); + white-space: nowrap; + border-radius: var(--radius-md); + background: var(--bg-tertiary); + border: 1px solid var(--border-primary); + color: var(--text-primary); + opacity: 0; + visibility: hidden; + transition: opacity var(--transition-fast), visibility var(--transition-fast); + z-index: 10; + pointer-events: none; +} + +.provider-card .modules .breakdown > div:hover::after, +#featured-providers .modules .breakdown > div:hover::after { + opacity: 1; + visibility: visible; +} + +.provider-card .modules .operator { background: var(--color-operator); } +.provider-card .modules .hook { background: var(--color-hook); } +.provider-card .modules .sensor { background: var(--color-sensor); } +.provider-card .modules .trigger { background: var(--color-trigger); } +.provider-card .modules .transfer { background: var(--color-transfer); } +.provider-card .modules .executor { background: var(--color-executor); } +.provider-card .modules .notifier { background: var(--color-notifier); } +.provider-card .modules .secret { background: var(--color-secret); } +.provider-card .modules .logging { background: var(--color-logging); } +.provider-card .modules .bundle { background: var(--color-bundle); } +.provider-card .modules .decorator { background: var(--color-decorator); } + +.provider-card .meta, +#featured-providers .meta { + display: flex; + align-items: center; + gap: var(--space-4); + font-size: var(--text-sm); +} + +.provider-card .downloads, +#featured-providers .downloads { + display: flex; + align-items: center; + gap: var(--space-2); + color: var(--text-secondary); +} + +.provider-card .downloads .icon, +#featured-providers .downloads .icon { + width: 1rem; + height: 1rem; +} + +.provider-card .versions, +#featured-providers .versions { + display: flex; + align-items: center; + gap: 0.375rem; + margin-left: auto; +} + +.provider-card .version, +#featured-providers .version { + padding: 0.125rem 0.375rem; + background: var(--color-gray-100); + background: light-dark(var(--color-gray-100), var(--color-navy-700)); + border: 1px solid var(--color-gray-300); + border-color: light-dark(var(--color-gray-300), var(--color-navy-600)); + border-radius: var(--radius); + font-size: var(--text-xs); + font-weight: var(--font-medium); + color: var(--color-gray-500); + color: light-dark(var(--color-gray-500), var(--color-gray-400)); +} + +.provider-card .more, +#featured-providers .more { + font-size: var(--text-xs); + color: var(--text-muted); +} + +.provider-card .chevron, +#featured-providers .chevron { + flex-shrink: 0; + width: var(--space-5); + height: var(--space-5); + color: var(--text-muted); + align-self: center; + transition: all var(--transition-base); +} + +.provider-card:hover .chevron, +#featured-providers .provider:hover .chevron { + color: var(--accent-primary); + transform: translateX(var(--space-1)); +} + +/* Providers Listing Page */ +.providers-page { + padding: var(--space-16) 0; + background: var(--bg-primary); +} + +.providers-page .page-header { + text-align: left; + margin-bottom: var(--space-8); +} + +.providers-page .page-header h1 { + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.providers-page .page-header p { + color: var(--text-secondary); +} + +/* Filters */ +.filters { + display: none; + flex-direction: column; + gap: var(--space-4); + margin-bottom: var(--space-8); + padding: 0; + background: transparent; + border: none; + border-radius: 0; +} + +.js .filters { + display: flex; +} + +@media (min-width: 768px) { + .filters { + flex-direction: row; + align-items: center; + } +} + +.search-wrapper { + position: relative; + flex: 1; + min-width: 0; +} + +.search-wrapper .search-icon { + position: absolute; + left: var(--space-3); + top: 50%; + transform: translateY(-50%); + width: 1.25rem; + height: 1.25rem; + color: var(--text-muted); + pointer-events: none; +} + +.search-wrapper input { + width: 100%; + padding: var(--space-3) var(--space-3) var(--space-3) 2.5rem; + background: var(--bg-primary-light); + background: light-dark(var(--bg-primary-light), var(--color-navy-800)); + border: 1px solid var(--border-primary-light); + border: 1px solid light-dark(var(--border-primary-light), var(--color-navy-600)); + border-radius: var(--radius-lg); + color: var(--text-primary); + font-size: var(--text-base); + transition: all var(--transition-base); +} + +.search-wrapper input:focus { + outline: none; + border-color: var(--color-cyan-400); +} + +.search-wrapper input::placeholder { + color: var(--text-muted); +} + +.filter-group { + display: flex; + align-items: center; + gap: var(--space-2); + flex-shrink: 0; +} + +.filter-group label { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-secondary); + white-space: nowrap; +} + +.lifecycle-buttons { + display: flex; + gap: var(--space-1); + background: var(--color-gray-100); + background: light-dark(var(--color-gray-100), var(--color-navy-800)); + border: 1px solid var(--color-gray-300); + border: 1px solid light-dark(var(--color-gray-300), var(--color-navy-600)); + border-radius: var(--radius-lg); + padding: var(--space-1); + outline: none; +} + +.lifecycle-btn { + padding: var(--space-2) var(--space-3); + border-radius: var(--radius-md); + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-secondary); + background: transparent; + border: none; + outline: none; + transition: all var(--transition-base); + cursor: pointer; +} + +.lifecycle-btn:hover { + color: var(--text-primary); + background: var(--bg-secondary); +} + +.lifecycle-btn:focus-visible { + outline: 2px solid var(--color-cyan-500); + outline-offset: 2px; +} + +.lifecycle-btn.active { + background: var(--color-cyan-500); + color: var(--color-navy-900); + font-weight: var(--font-medium); +} + +.filter-group select { + padding: var(--space-2) var(--space-3); + background: var(--bg-primary-light); + background: light-dark(var(--bg-primary-light), var(--color-navy-800)); + border: 1px solid var(--border-primary-light); + border: 1px solid light-dark(var(--border-primary-light), var(--color-navy-600)); + border-radius: var(--radius-lg); + color: var(--text-primary); + font-size: var(--text-sm); + cursor: pointer; + transition: all var(--transition-base); +} + +.filter-group select:focus { + outline: none; + border-color: var(--color-cyan-400); +} + +/* Provider Grid */ +.provider-grid { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: var(--space-4); + grid-template-columns: 1fr; +} + +@media (min-width: 768px) { + .provider-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.provider-item { + display: flex; +} + +.provider-item .provider-card { + flex: 1; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: var(--space-20) var(--space-4); +} + +.empty-state .empty-icon { + width: 4rem; + height: 4rem; + margin: 0 auto var(--space-4); + color: var(--text-muted); +} + +.empty-state h3 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +.empty-state p { + font-size: var(--text-base); + color: var(--text-secondary); +} + +/* Featured / New Provider Sections */ +#featured-providers, +#new-providers { + padding: var(--space-20) 0; + background: var(--bg-secondary-light); + background: light-dark(var(--bg-secondary-light), transparent); +} + +#featured-providers header, +#new-providers header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: var(--space-8); +} + +#featured-providers h2, +#new-providers h2 { + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +#featured-providers header p, +#new-providers header p { + color: var(--text-secondary); + font-size: var(--text-base); +} + +#featured-providers .view-all, +#new-providers .view-all { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + background: var(--color-gray-100); + background: light-dark(var(--color-gray-100), rgba(15, 23, 42, 0.5)); + border: 1px solid var(--color-gray-200); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + color: var(--text-primary); + font-size: var(--text-sm); + font-weight: var(--font-medium); + text-decoration: none; + transition: all var(--transition-base); +} + +#featured-providers .view-all:hover, +#new-providers .view-all:hover { + border-color: var(--color-gray-300); + border-color: light-dark(var(--color-gray-300), rgba(6, 182, 212, 0.5)); + background: var(--color-gray-200); + background: light-dark(var(--color-gray-200), rgba(15, 23, 42, 0.8)); +} + +#featured-providers .view-all .icon, +#new-providers .view-all .icon { + width: 1rem; + height: 1rem; +} + +#featured-providers .providers, +#new-providers .providers { + list-style: none; + padding: 0; + margin: 0; + display: grid; + gap: var(--space-6); + grid-template-columns: 1fr; +} + +@media (min-width: 768px) { + #featured-providers .providers, + #new-providers .providers { + grid-template-columns: repeat(2, 1fr); + } +} + +.version-badge { + padding: var(--space-1) var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-medium); + background: var(--bg-tertiary); + color: var(--text-secondary); + border: 1px solid var(--border-primary); + border-radius: var(--radius); +} + +/* Section Headers */ +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-8); +} + +/* Quick filter buttons */ +#hero .popular-providers { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: var(--space-2); +} + +#hero .popular-providers .label { + font-size: var(--text-sm); + color: var(--text-muted); +} + +#hero .popular-providers a { + padding: var(--space-2) var(--space-4); + background: var(--color-gray-100); + background: light-dark(var(--color-gray-100), var(--bg-tertiary-dark)); + border: 1px solid var(--color-gray-200); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + font-size: 0.875rem; + color: var(--color-navy-800); + color: var(--text-primary); + transition: all var(--transition-base); +} + +#hero .popular-providers a:hover { + background: var(--color-gray-200); + background: light-dark(var(--color-gray-200), var(--color-navy-600)); + border-color: var(--color-gray-300); + border-color: light-dark(var(--color-gray-300), var(--color-navy-500)); +} + +/* (legacy #newly-added removed — replaced by #new-providers) */ + +.new-modules { + list-style: none; + padding: 0; + margin: 0; + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); +} + +@media (min-width: 768px) { + .new-modules { + grid-template-columns: repeat(2, 1fr); + } +} + +a.new-module { + display: flex; + gap: var(--space-3); + padding: var(--space-4); + text-decoration: none; + color: inherit; +} + +.new-module .type-icon { + flex-shrink: 0; +} + +.new-module h4 { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin: 0 0 var(--space-1); +} + +.new-module .provider-label { + font-size: var(--text-xs); + color: var(--text-muted); +} + +.new-module p { + font-size: var(--text-sm); + color: var(--text-secondary); + margin: var(--space-2) 0 0; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Categories Section */ +#categories { + padding: var(--space-20) 0; +} + +#categories header { + text-align: center; + margin-bottom: var(--space-12); +} + +#categories h2 { + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-2); +} + +#categories header p { + color: var(--text-secondary); +} + +#categories ul { + list-style: none; + padding: 0; + margin: 0; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); + font-size: var(--text-sm); + color: var(--text-muted); +} + +@media (min-width: 768px) { + #categories ul { + grid-template-columns: repeat(4, 1fr); + } +} + +#categories .category { + padding: var(--space-6); + text-align: center; background: var(--color-card-bg); + border: 1px solid var(--border-primary-light); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + backdrop-filter: blur(4px); + transition: all var(--transition-base); + cursor: pointer; + text-decoration: none; + display: block; +} + +#categories .category:hover { border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); + box-shadow: 0 10px 40px -10px rgba(6, 182, 212, 0.15); + box-shadow: 0 10px 40px -10px light-dark(rgba(6, 182, 212, 0.15), rgba(6, 182, 212, 0.1)); +} + +#categories .icon { + width: 3rem; + height: 3rem; + margin: 0 auto var(--space-4); + border-radius: var(--radius-xl); + display: flex; + align-items: center; + justify-content: center; +} + +#categories .icon svg { + width: 1.5rem; + height: 1.5rem; +} + +#categories .category.databases .icon { + background: rgba(59, 130, 246, 0.2); + color: var(--color-blue-400); +} + +#categories .category.cloud .icon { + background: rgba(6, 182, 212, 0.2); + color: var(--color-cyan-400); +} + +#categories .category.ml .icon { + background: rgba(168, 85, 247, 0.2); + color: var(--color-purple-400); +} + +#categories .category.data-processing .icon { + background: rgba(34, 197, 94, 0.2); + color: var(--color-green-400); +} + +#categories h3 { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-1); +} + +/* Install Widget Section */ +.install { + padding: var(--space-16) 0; + background: linear-gradient(to right, rgba(6, 182, 212, 0.1), rgba(99, 102, 241, 0.1)); + border-top: 1px solid var(--border-primary); +} + +.install .container { + max-width: 56rem; + text-align: center; +} + +.install header { + margin-bottom: var(--space-8); +} + +.install h2 { + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-4); +} + +.install header p { + color: var(--text-secondary); +} + +.install .command-box { + display: flex; + align-items: stretch; + background: var(--bg-tertiary-light); + background: light-dark(var(--bg-tertiary-light), var(--color-navy-900)); + border: 1px solid var(--border-primary); + border-radius: var(--radius-xl); + padding: 0; + text-align: left; + overflow: hidden; + max-width: 32rem; + margin: 0 auto; +} + +.install .command-box code { + flex: 1; + font-size: var(--text-sm); + font-family: var(--font-mono); + color: var(--color-gray-800); + color: light-dark(var(--color-gray-800), var(--color-gray-300)); + padding: 0.5rem 0.5rem 0.5rem 1rem; + display: flex; + align-items: center; +} + + +/* Main content area */ +main { + flex: 1; +} + +/* Explore Page */ +.explore-page { + padding: var(--space-16) 0; + background: var(--bg-surface-alt); + min-height: calc(100vh - var(--header-height, 0)); +} + +.explore-page .page-header { + text-align: center; + margin-bottom: var(--space-12); +} + +.explore-page .page-header h1 { + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-4); +} + +.explore-page .page-header p { + font-size: var(--text-lg); + color: var(--text-secondary); +} + +/* Category Grid */ +.category-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); + margin-bottom: var(--space-16); +} + +@media (min-width: 768px) { + .category-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .category-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Category Card */ +.category-card { + padding: var(--space-6); + background: #ffffff; + background: light-dark(#ffffff, var(--color-card-bg)); + border: 1px solid var(--border-primary); + border-radius: var(--radius-xl); + transition: all var(--transition-base); +} + +.category-card:hover { + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); + box-shadow: 0 10px 40px -10px rgba(6, 182, 212, 0.15); + box-shadow: 0 10px 40px -10px light-dark(rgba(6, 182, 212, 0.15), rgba(6, 182, 212, 0.1)); +} + +.category-header { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-4); +} + +.category-header > div { + line-height: 1.2; +} + +.category-icon { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.category-icon svg { + width: 1.5rem; + height: 1.5rem; +} + +.category-card h2 { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin: 0; +} + +.provider-count { + font-size: var(--text-xs); + color: var(--text-muted); + margin: 0; + line-height: 1.5; +} + +.category-description { + font-size: var(--text-sm); + color: var(--text-secondary); + margin-bottom: var(--space-4); +} + +/* Category Color Variants */ +.category-card.color-cyan .category-icon { + background: rgba(6, 182, 212, 0.2); + color: var(--color-cyan-400); +} + +.category-card.color-blue .category-icon { + background: rgba(59, 130, 246, 0.2); + color: var(--color-blue-400); +} + +.category-card.color-purple .category-icon { + background: rgba(168, 85, 247, 0.2); + color: var(--color-purple-400); +} + +.category-card.color-green .category-icon { + background: rgba(34, 197, 94, 0.2); + color: var(--color-green-400); +} + +.category-card.color-amber .category-icon { + background: rgba(251, 191, 36, 0.2); + color: var(--color-amber-400); +} + +.category-card.color-rose .category-icon { + background: rgba(244, 63, 94, 0.2); + color: var(--color-rose-400); +} + +/* Category-specific border colors */ +.category-card.color-cyan { + border-color: rgba(6, 182, 212, 0.3); + border-color: light-dark(rgba(6, 182, 212, 0.3), rgba(6, 182, 212, 0.2)); +} + +.category-card.color-blue { + border-color: rgba(59, 130, 246, 0.3); + border-color: light-dark(rgba(59, 130, 246, 0.3), rgba(59, 130, 246, 0.2)); +} + +.category-card.color-purple { + border-color: rgba(168, 85, 247, 0.3); + border-color: light-dark(rgba(168, 85, 247, 0.3), rgba(168, 85, 247, 0.2)); +} + +.category-card.color-green { + border-color: rgba(34, 197, 94, 0.3); + border-color: light-dark(rgba(34, 197, 94, 0.3), rgba(34, 197, 94, 0.2)); +} + +.category-card.color-amber { + border-color: rgba(251, 191, 36, 0.3); + border-color: light-dark(rgba(251, 191, 36, 0.3), rgba(251, 191, 36, 0.2)); +} + +.category-card.color-rose { + border-color: rgba(244, 63, 94, 0.3); + border-color: light-dark(rgba(244, 63, 94, 0.3), rgba(244, 63, 94, 0.2)); +} + +/* Category-specific badge colors */ +.category-card.color-cyan .provider-badge { + background: rgba(6, 182, 212, 0.2); + color: var(--color-cyan-600); + color: light-dark(var(--color-cyan-600), var(--color-cyan-400)); +} + +.category-card.color-blue .provider-badge { + background: rgba(59, 130, 246, 0.2); + color: var(--color-blue-600); + color: light-dark(var(--color-blue-600), var(--color-blue-400)); +} + +.category-card.color-purple .provider-badge { + background: rgba(168, 85, 247, 0.2); + color: var(--color-purple-600); + color: light-dark(var(--color-purple-600), var(--color-purple-400)); +} + +.category-card.color-green .provider-badge { + background: rgba(34, 197, 94, 0.2); + color: var(--color-green-600); + color: light-dark(var(--color-green-600), var(--color-green-400)); +} + +.category-card.color-amber .provider-badge { + background: rgba(251, 191, 36, 0.2); + color: var(--color-amber-600); + color: light-dark(var(--color-amber-600), var(--color-amber-400)); +} + +.category-card.color-rose .provider-badge { + background: rgba(244, 63, 94, 0.2); + color: var(--color-rose-600); + color: light-dark(var(--color-rose-600), var(--color-rose-400)); +} + +/* Provider Badges */ +.provider-badges { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-4); +} + +.provider-badge { + display: flex; + align-items: center; + gap: 0.375rem; + padding: 0.25rem 0.625rem; + background: var(--bg-tertiary); + border: 1px solid transparent; + border-radius: var(--radius-lg); + font-size: var(--text-sm); + color: var(--text-secondary); + transition: all var(--transition-base); + text-decoration: none; +} + +.provider-badge:hover { + background: var(--color-card-bg-hover); + border-color: var(--color-card-border-hover); + color: var(--text-primary); +} + +.provider-badge img { + width: 1rem; + height: 1rem; + border-radius: var(--radius); +} + +/* Category and Section Continuation Links */ +.category-card > a, +.featured-section > a { + display: inline-flex; + align-items: center; + gap: var(--space-1); + font-size: var(--text-sm); + color: var(--text-secondary); + transition: color var(--transition-base); + text-decoration: none; +} + +.category-card > a:hover, +.featured-section > a:hover { + color: var(--color-cyan-400); +} + +.category-card > a svg, +.featured-section > a svg { + width: 1rem; + height: 1rem; +} + +/* Featured Sections */ +.featured-sections { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); + margin-bottom: var(--space-16); +} + +@media (min-width: 1024px) { + .featured-sections { + grid-template-columns: repeat(2, 1fr); + } +} + +.featured-section { + min-width: 0; +} + +.featured-section .section-header { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-6); +} + +.featured-section h2 { + font-size: var(--text-xl); + font-weight: var(--font-bold); + color: var(--text-primary); +} + +.lifecycle-badge { + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); + font-weight: var(--font-medium); + border-radius: var(--radius-lg); +} + +.lifecycle-badge.lifecycle-stable { + background: rgba(6, 182, 212, 0.15); + background: light-dark(rgba(6, 182, 212, 0.15), rgba(6, 182, 212, 0.1)); + color: var(--color-cyan-400); +} + +.lifecycle-badge.lifecycle-incubation { + background: color-mix(in srgb, var(--color-amber-500) 15%, transparent); + color: light-dark(var(--color-amber-600), var(--color-amber-400)); +} + +.lifecycle-badge.lifecycle-deprecated { + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +/* Provider List */ +.provider-list { + display: flex; + flex-direction: column; + gap: var(--space-3); + margin-bottom: var(--space-4); +} + +.provider-row { + display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-4); + background: var(--color-card-bg); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + transition: all var(--transition-base); + text-decoration: none; +} + +.provider-row:hover { + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); +} + +.provider-logo { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius); + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-tertiary); +} + +.provider-logo img { + width: 2rem; + height: 2rem; + object-fit: contain; +} + +.provider-logo .initial { + font-size: var(--text-lg); + font-weight: var(--font-semibold); + color: var(--color-cyan-400); +} + +.provider-info { + flex: 1; + min-width: 0; + overflow: hidden; +} + +.provider-name { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-1); +} + +.provider-desc { + font-size: var(--text-xs); + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; + display: block; +} + +.module-count { + font-size: var(--text-sm); + color: var(--text-muted); + white-space: nowrap; +} + +/* Quick Links */ +.quick-links { + text-align: center; + padding: var(--space-12) 0; +} + +.quick-links p { + font-size: var(--text-lg); + color: var(--text-secondary); + margin-bottom: var(--space-6); +} + +.link-buttons { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: var(--space-4); +} + +/* Quick Links Actions */ +.quick-links button, +.quick-links a { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-6); + border-radius: var(--radius-lg); + font-size: var(--text-base); + font-weight: var(--font-medium); + transition: all var(--transition-base); + text-decoration: none; + border: 1px solid transparent; + cursor: pointer; +} + +.quick-links button { + background: var(--color-cyan-500); + color: white; +} + +.quick-links button:hover { + background: var(--color-cyan-600); +} + +.quick-links button svg { + width: 1.25rem; + height: 1.25rem; +} + +.quick-links a { + background: var(--bg-tertiary); + color: var(--text-primary); + border-color: var(--border-primary); +} + +.quick-links a:hover { + background: var(--color-card-bg-hover); + border-color: var(--color-card-border-hover); +} + +/* Statistics Page */ +.statistics-page { + padding: var(--space-16) 0; + background: var(--bg-surface-alt); + min-height: calc(100vh - var(--header-height, 0)); +} + +.statistics-page .page-header { + text-align: center; + margin-bottom: var(--space-12); +} + +.statistics-page .page-header h1 { + font-size: var(--text-3xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin-bottom: var(--space-4); +} + +.statistics-page .page-header p { + font-size: var(--text-lg); + color: var(--text-secondary); +} + +/* Stats Overview */ +.stats-overview { + margin-bottom: var(--space-12); +} + +.stats-overview dl { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); +} + +@media (min-width: 768px) { + .stats-overview dl { + grid-template-columns: repeat(4, 1fr); + } +} + +.stats-overview dl > div { + padding: var(--space-6); + background: var(--color-card-bg); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + text-align: center; + transition: all var(--transition-base); +} + +.stats-overview dl > div:hover { + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); +} + +.stats-overview dt { + font-size: var(--text-4xl); + font-weight: var(--font-bold); + color: var(--accent-primary); + margin-bottom: var(--space-2); + display: block; +} + +.stats-overview dd { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-secondary); + margin: 0; +} + +/* Module Breakdown */ +.module-breakdown { + margin-bottom: var(--space-12); +} + +.module-breakdown header { + margin-bottom: var(--space-6); +} + +.module-breakdown h2 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.breakdown-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-4); +} + +@media (min-width: 640px) { + .breakdown-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 768px) { + .breakdown-grid { + grid-template-columns: repeat(5, 1fr); + } +} + +.module-type { + padding: var(--space-5); + background: var(--color-card-bg); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + transition: all var(--transition-base); +} + +.module-type:hover { + border-color: var(--color-card-border-hover); + background: var(--color-card-bg-hover); +} + +.type-header { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-3); +} + +.type-icon { + width: 2.5rem; + height: 2.5rem; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.type-icon span { + font-size: var(--text-lg); + font-weight: var(--font-bold); +} + +.type-icon.operator { + background: rgba(34, 197, 94, 0.2); + color: var(--color-green-400); +} + +.type-icon.hook { + background: rgba(59, 130, 246, 0.2); + color: var(--color-blue-400); +} + +.type-icon.sensor { + background: rgba(245, 158, 11, 0.2); + color: var(--color-amber-400); +} + +.type-icon.trigger { + background: rgba(168, 85, 247, 0.2); + color: var(--color-purple-400); +} + +.type-icon.transfer { + background: rgba(236, 72, 153, 0.2); + color: var(--color-pink-400); +} + +.type-icon.executor { + background: rgba(249, 115, 22, 0.2); + color: var(--color-executor); +} + +.type-icon.notifier { + background: rgba(99, 102, 241, 0.2); + color: var(--color-notifier); +} + +.type-icon.secret { + background: rgba(244, 63, 94, 0.2); + color: var(--color-secret); +} + +.type-icon.logging { + background: rgba(20, 184, 166, 0.2); + color: var(--color-logging); +} + +.type-icon.bundle { + background: rgba(14, 165, 233, 0.2); + color: var(--color-bundle); +} + +.type-icon.decorator { + background: rgba(217, 70, 239, 0.2); + color: var(--color-decorator); +} + + +.type-count { + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: var(--text-primary); +} + +.type-label { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-secondary); + margin-bottom: var(--space-3); +} + +.share-bar { + width: 100%; + height: 0.5rem; + border-radius: var(--radius-full); + margin-bottom: var(--space-2); + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + border: none; + background: var(--bg-secondary); +} + +.share-bar::-webkit-meter-bar { + background: var(--bg-secondary); + border-radius: var(--radius-full); + border: none; +} + +.share-bar::-webkit-meter-optimum-value { + background: var(--meter-color, var(--color-gray-500)); + border-radius: var(--radius-full); + transition: width var(--transition-base); +} + +.share-bar::-moz-meter-bar { + background: var(--meter-color, var(--color-gray-500)); + border-radius: var(--radius-full); + border: none; +} + +.share-bar.operator { + --meter-color: var(--color-operator); +} + +.share-bar.hook { + --meter-color: var(--color-hook); +} + +.share-bar.sensor { + --meter-color: var(--color-sensor); +} + +.share-bar.trigger { + --meter-color: var(--color-trigger); +} + +.share-bar.transfer { + --meter-color: var(--color-transfer); +} + +.share-bar.executor { + --meter-color: var(--color-executor); +} + +.share-bar.notifier { + --meter-color: var(--color-notifier); +} + +.share-bar.secret { + --meter-color: var(--color-secret); +} + +.share-bar.logging { + --meter-color: var(--color-logging); +} + +.share-bar.bundle { + --meter-color: var(--color-bundle); +} + +.share-bar.decorator { + --meter-color: var(--color-decorator); +} + + +.share-label { + font-size: var(--text-xs); + color: var(--text-muted); +} + +/* Two Column Section */ +.stats-columns { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); + margin-bottom: var(--space-12); +} + +@media (min-width: 768px) { + .stats-columns { + grid-template-columns: repeat(2, 1fr); + } +} + +/* Tier Distribution */ +.lifecycle-distribution { + padding: var(--space-6); +} + +.lifecycle-distribution header { + margin-bottom: var(--space-6); +} + +.lifecycle-distribution h2 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.lifecycle-stats { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.lifecycle-stat { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.lifecycle-info { + display: flex; + justify-content: space-between; + align-items: center; +} + +.lifecycle-name { + font-weight: var(--font-medium); + text-transform: capitalize; +} + +.lifecycle-name.lifecycle-stable { + color: var(--color-cyan-400); +} + +.lifecycle-name.lifecycle-incubation { + color: light-dark(var(--color-amber-600), var(--color-amber-400)); +} + +.lifecycle-name.lifecycle-deprecated { + color: var(--text-secondary); +} + +.lifecycle-count { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.lifecycle-bar { + width: 100%; + height: 0.75rem; + border-radius: var(--radius-full); + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + border: none; + background: var(--bg-secondary); +} + +.lifecycle-bar::-webkit-meter-bar { + background: var(--bg-secondary); + border-radius: var(--radius-full); + border: none; +} + +.lifecycle-bar::-webkit-meter-optimum-value { + background: var(--meter-color, var(--color-gray-500)); + border-radius: var(--radius-full); + transition: width var(--transition-base); +} + +.lifecycle-bar::-moz-meter-bar { + background: var(--meter-color, var(--color-gray-500)); + border-radius: var(--radius-full); + border: none; +} + +.lifecycle-bar.lifecycle-stable { + --meter-color: var(--color-cyan-500); +} + +.lifecycle-bar.lifecycle-incubation { + --meter-color: var(--color-amber-500); +} + +.lifecycle-bar.lifecycle-deprecated { + --meter-color: var(--color-gray-500); +} + +/* Quick Access */ +.quick-access { + padding: var(--space-6); +} + +.quick-access header { + margin-bottom: var(--space-6); +} + +.quick-access h2 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.access-links { + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.access-link { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-3); + background: var(--bg-tertiary); + border-radius: var(--radius-lg); + color: var(--text-primary); + text-decoration: none; + transition: all var(--transition-base); +} + +.access-link:hover { + background: var(--bg-secondary); +} + +.access-link:hover .chevron { + color: var(--color-cyan-400); + transform: translateX(0.25rem); +} + +.access-link .chevron { + width: 1.25rem; + height: 1.25rem; + color: var(--text-muted); + transition: all var(--transition-base); +} + +/* Provider Ranking */ +.provider-ranking { + margin-bottom: var(--space-12); +} + +.provider-ranking header { + margin-bottom: var(--space-6); +} + +.provider-ranking h2 { + font-size: var(--text-xl); + font-weight: var(--font-semibold); + color: var(--text-primary); +} + +.ranking-tabs { + display: flex; + gap: var(--space-2); +} + +.ranking-tab { + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-secondary); + background: transparent; + border: 1px solid var(--border-primary); + border-radius: var(--radius); + cursor: pointer; + transition: all var(--transition-base); +} + +.ranking-tab:hover { + color: var(--text-primary); + border-color: var(--text-muted); +} + +.ranking-tab.active { + color: var(--color-cyan-400); + border-color: var(--color-cyan-400); + background: color-mix(in srgb, var(--color-cyan-400) 10%, transparent); +} + +.ranking-panel[hidden] { + display: none; +} + +.downloads-column { + text-align: right; +} + +.downloads-count { + text-align: right; + font-weight: var(--font-medium); + font-variant-numeric: tabular-nums; + color: var(--text-primary); +} + +.ranking-table { + width: 100%; + border-collapse: collapse; +} + +.ranking-header { + background: var(--bg-tertiary); +} + +.ranking-header th { + padding: var(--space-3) var(--space-4); + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--text-secondary); + text-align: left; +} + +.ranking-header .module-column, +.ranking-header .total-column { + text-align: center; +} + +.ranking-header .full-label { + display: none; +} + +@media (min-width: 640px) { + .ranking-header .full-label { + display: inline; + } + + .ranking-header .abbr-label { + display: none; + } +} + +.ranking-row { + border-top: 1px solid var(--border-primary); + transition: background-color var(--transition-base); +} + +.ranking-row:hover { + background: var(--bg-secondary); +} + +.ranking-row td { + padding: var(--space-3) var(--space-4); +} + +.rank-cell { + color: var(--text-muted); +} + +.provider-cell { + min-width: 200px; +} + +.provider-link { + display: flex; + align-items: center; + gap: var(--space-2); + color: var(--text-primary); + text-decoration: none; + transition: color var(--transition-base); +} + +.provider-link:hover { + color: var(--color-cyan-400); +} + +.provider-link .provider-logo { + width: 2rem; + height: 2rem; + border-radius: var(--radius); + background: var(--bg-tertiary); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.provider-link .provider-logo img { + width: 1.5rem; + height: 1.5rem; + object-fit: contain; +} + +.provider-link .initial { + font-size: var(--text-sm); + font-weight: var(--font-bold); + color: var(--color-cyan-400); +} + +.provider-link .provider-name { + font-weight: var(--font-medium); +} + +.module-count { + text-align: center; + font-weight: var(--font-medium); +} + +.module-count.operator { + color: var(--color-green-400); +} + +.module-count.hook { + color: var(--color-blue-400); +} + +.module-count.sensor { + color: var(--color-amber-400); +} + +.module-count.trigger { + color: var(--color-purple-400); +} + +.module-count.transfer { + color: var(--color-pink-400); +} + +.total-count { + text-align: center; + font-weight: var(--font-bold); + color: var(--color-cyan-400); +} + +/* ======================================== + Provider Detail Page + ======================================== */ + +.provider-detail-page .container { + padding-top: var(--space-8); +} + +/* Breadcrumb */ +.breadcrumb { + display: flex; + align-items: center; + gap: var(--space-2); + margin-bottom: var(--space-6); + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.breadcrumb a { + color: var(--text-secondary); + transition: color var(--transition-base); +} + +.breadcrumb a:hover { + color: var(--text-primary); +} + +.breadcrumb svg { + width: 1rem; + height: 1rem; + color: var(--text-muted); +} + +.breadcrumb span:last-child { + color: var(--text-primary); +} + +/* Provider Detail Page - Header */ +.provider-detail-page header { + padding: var(--space-6); + margin-bottom: var(--space-8); +} + +.provider-detail-page header .top { + display: flex; + align-items: flex-start; + gap: var(--space-4); + margin-bottom: var(--space-6); +} + +.provider-detail-page header .logo { + width: 4rem; + height: 4rem; + border-radius: var(--radius-xl); + background: var(--bg-tertiary); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + overflow: hidden; +} + +.provider-detail-page header .logo img { + width: 3.5rem; + height: 3.5rem; + object-fit: contain; +} + +.provider-detail-page header .logo .initial { + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: var(--accent-primary); +} + +.provider-detail-page header .details { + flex: 1; +} + +.provider-detail-page header .title-row { + display: flex; + align-items: center; + gap: var(--space-3); + margin-bottom: var(--space-2); +} + +.provider-detail-page header .title-row h1 { + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: var(--text-primary); +} + +.provider-detail-page header .package-name { + font-size: var(--text-sm); + color: var(--text-muted); + font-family: var(--font-mono); + margin-bottom: var(--space-2); +} + +.provider-detail-page header .description { + color: var(--text-secondary); + max-width: 48rem; +} + +.provider-detail-page header .version-badges { + display: flex; + align-items: center; + gap: var(--space-2); + margin-top: var(--space-3); +} + +.provider-detail-page header .version-label { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.provider-detail-page header .version-badge { + padding: 0.125rem var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-medium); + background: rgb(from var(--color-green-500) r g b / 0.2); + color: var(--color-green-400); + border-radius: var(--radius-sm); + border: 1px solid rgb(from var(--color-green-500) r g b / 0.3); +} + +/* Stats */ +.provider-detail-page header .stats { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-3); + margin-bottom: var(--space-6); +} + +@media (min-width: 640px) { + .provider-detail-page header .stats { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 1024px) { + .provider-detail-page header .stats { + grid-template-columns: repeat(6, 1fr); + } +} + +.provider-detail-page header .stat { + background: rgb(from var(--bg-secondary) r g b / 0.5); + border-radius: var(--radius-lg); + padding: var(--space-3); +} + +.provider-detail-page header .stat-row { + display: flex; + align-items: center; + gap: var(--space-2); + margin-bottom: var(--space-1); +} + +.provider-detail-page header .stat.operator { + border: 1px solid rgb(from var(--color-operator) r g b / 0.2); +} + +.provider-detail-page header .stat.hook { + border: 1px solid rgb(from var(--color-hook) r g b / 0.2); +} + +.provider-detail-page header .stat.sensor { + border: 1px solid rgb(from var(--color-sensor) r g b / 0.2); +} + +.provider-detail-page header .stat.trigger { + border: 1px solid rgb(from var(--color-trigger) r g b / 0.2); +} + +.provider-detail-page header .stat.transfer { + border: 1px solid rgb(from var(--color-transfer) r g b / 0.2); +} + +.provider-detail-page header .stat.bundle { + border: 1px solid rgb(from var(--color-bundle) r g b / 0.2); +} + +.provider-detail-page header .stat.total { + border: 1px solid rgb(from var(--accent-primary) r g b / 0.2); +} + +.provider-detail-page header .stat .icon { + width: 1.5rem; + height: 1.5rem; + border-radius: var(--radius-sm); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-xs); + font-weight: var(--font-bold); +} + +.provider-detail-page header .stat.operator .icon { + background: rgb(from var(--color-operator) r g b / 0.2); + color: var(--color-operator); +} + +.provider-detail-page header .stat.hook .icon { + background: rgb(from var(--color-hook) r g b / 0.2); + color: var(--color-hook); +} + +.provider-detail-page header .stat.sensor .icon { + background: rgb(from var(--color-sensor) r g b / 0.2); + color: var(--color-sensor); +} + +.provider-detail-page header .stat.trigger .icon { + background: rgb(from var(--color-trigger) r g b / 0.2); + color: var(--color-trigger); +} + +.provider-detail-page header .stat.transfer .icon { + background: rgb(from var(--color-transfer) r g b / 0.2); + color: var(--color-transfer); +} + +.provider-detail-page header .stat.bundle .icon { + background: rgb(from var(--color-bundle) r g b / 0.2); + color: var(--color-bundle); +} + +.provider-detail-page header .stat.total .icon { + background: rgb(from var(--accent-primary) r g b / 0.2); + color: var(--accent-primary); +} + +.provider-detail-page header .stat .count { + font-size: var(--text-xl); + font-weight: var(--font-bold); +} + +.provider-detail-page header .stat.operator .count { + color: var(--color-operator); +} + +.provider-detail-page header .stat.hook .count { + color: var(--color-hook); +} + +.provider-detail-page header .stat.sensor .count { + color: var(--color-sensor); +} + +.provider-detail-page header .stat.trigger .count { + color: var(--color-trigger); +} + +.provider-detail-page header .stat.transfer .count { + color: var(--color-transfer); +} + +.provider-detail-page header .stat.bundle .count { + color: var(--color-bundle); +} + +.provider-detail-page header .stat.total .count { + color: var(--accent-primary); +} + +.provider-detail-page header .stat .label { + font-size: var(--text-xs); + color: var(--text-secondary); +} + +/* Header Footer */ +.provider-detail-page header .footer { + display: flex; + flex-direction: column; + gap: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--border-primary); +} + +@media (min-width: 640px) { + .provider-detail-page header .footer { + flex-direction: row; + align-items: center; + justify-content: space-between; + } +} + +.provider-detail-page header .downloads { + text-align: center; +} + +.provider-detail-page header .downloads .count { + font-size: var(--text-2xl); + font-weight: var(--font-bold); + color: var(--text-primary); +} + +.provider-detail-page header .downloads .label { + font-size: var(--text-xs); + color: var(--text-secondary); +} + +.provider-detail-page header .actions { + display: flex; + align-items: center; + gap: var(--space-3); +} + +.btn-primary, +.btn-secondary { + display: inline-flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + font-size: var(--text-sm); + font-weight: var(--font-medium); + border-radius: var(--radius-lg); + transition: all var(--transition-base); + text-decoration: none; +} + +.btn-primary { + background: var(--accent-primary); + color: var(--color-navy-900); +} + +.btn-primary:hover { + background: var(--color-cyan-300); +} + +.btn-primary svg { + width: 1rem; + height: 1rem; +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border: 1px solid var(--border-primary); +} + +.btn-secondary:hover { + background: var(--bg-tertiary); + border-color: var(--border-secondary); +} + +.btn-secondary svg { + width: 1rem; + height: 1rem; +} + +/* Install */ +.provider-detail-page .install { + display: flex; + flex-direction: column; + gap: var(--space-4); + padding: var(--space-4); + margin-bottom: var(--space-6); +} + +@media (min-width: 640px) { + .provider-detail-page .install { + flex-direction: row; + align-items: center; + } +} + +.provider-detail-page .install-label { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--text-secondary); + flex-shrink: 0; +} + +.provider-detail-page .install .command { + display: flex; + align-items: stretch; + background: var(--bg-tertiary-light); + background: light-dark(var(--bg-tertiary-light), var(--color-navy-900)); + border-radius: var(--radius-lg); + padding: 0; + min-width: 0; + overflow: hidden; + flex: 1; +} + +.provider-detail-page .install .command code { + flex: 1; + font-size: var(--text-sm); + font-family: var(--font-mono); + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4); + display: flex; + align-items: center; +} + +.install .copy { + flex-shrink: 0; + padding: var(--space-2) var(--space-3); + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-base); + display: flex; + align-items: center; +} + +.install .copy:hover { + background: var(--border-primary-light); + background: light-dark(var(--color-gray-200), var(--bg-tertiary)); + color: var(--accent-primary); +} + +.install .copy svg { + width: 1rem; + height: 1rem; +} + +/* Install Options (extras/version dropdowns) */ +.provider-detail-page .install-options { + display: flex; + align-items: center; + gap: var(--space-3); + flex-shrink: 0; + flex-wrap: wrap; +} + +.provider-detail-page .install-option { + display: flex; + align-items: center; + gap: var(--space-2); +} + +.provider-detail-page .install-option label { + font-size: var(--text-sm); + color: var(--text-secondary); + white-space: nowrap; +} + +.provider-detail-page .install-option select { + padding: var(--space-2) var(--space-3); + background: var(--bg-tertiary); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + color: var(--text-primary); + font-size: var(--text-sm); + cursor: pointer; +} + +.provider-detail-page .install-option #extras-select { + border-color: rgb(from var(--color-sensor) r g b / 0.5); + color: var(--color-sensor); +} + +.provider-detail-page .install-option #version-select { + border-color: rgb(from var(--accent-primary) r g b / 0.5); + color: var(--accent-primary); + font-weight: var(--font-medium); +} + +/* Extra Dependencies Info Box */ +.version-banner { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3); + margin-bottom: var(--space-6); + background: rgba(245, 158, 11, 0.1); + background: rgb(from var(--color-warning, #f59e0b) r g b / 0.1); + border: 1px solid rgba(245, 158, 11, 0.25); + border-color: rgb(from var(--color-warning, #f59e0b) r g b / 0.25); + border-radius: var(--radius-md, 0.5rem); + font-size: var(--text-sm); + color: var(--text-secondary); +} + +.version-banner > svg { + width: 1.25rem; + height: 1.25rem; + flex-shrink: 0; + color: var(--color-warning, #f59e0b); +} + +.version-banner a { + color: var(--accent-primary); + text-decoration: underline; +} + +.no-modules-banner { + display: flex; + align-items: flex-start; + gap: var(--space-4); + padding: var(--space-6); + margin-bottom: var(--space-6); + background: var(--bg-secondary); + border: 1px solid var(--border-primary); +} + +.no-modules-banner > svg { + width: 1.5rem; + height: 1.5rem; + flex-shrink: 0; + color: var(--text-muted); + margin-top: 0.125rem; +} + +.no-modules-banner h3 { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-1); +} + +.no-modules-banner p { + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: 1.5; +} + +.extra-deps-info { + display: flex; + align-items: flex-start; + gap: var(--space-2); + padding: var(--space-3); + margin-bottom: var(--space-6); + background: rgb(from var(--color-sensor) r g b / 0.1); + border-color: rgb(from var(--color-sensor) r g b / 0.2); +} + +.extra-deps-info[hidden] { + display: none; +} + +.extra-deps-info > svg { + width: 1rem; + height: 1rem; + flex-shrink: 0; + margin-top: 0.125rem; + color: var(--color-sensor); +} + +.extra-deps-info strong { + font-size: var(--text-sm); + color: var(--color-sensor); +} + +.extra-deps-info p { + font-size: var(--text-sm); + color: var(--text-secondary); + margin: var(--space-1) 0 0; +} + +/* Module Type Tabs */ +.module-tabs { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-bottom: var(--space-6); +} + +.module-tab { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-lg); + font-size: var(--text-sm); + font-weight: var(--font-medium); + background: var(--bg-tertiary); + color: var(--text-secondary); + border: 1px solid var(--border-primary); + cursor: pointer; + transition: all var(--transition-base); +} + +.module-tab:hover { + color: var(--text-primary); + border-color: var(--border-secondary); +} + +.module-tab.active { + background: rgb(from var(--accent-primary) r g b / 0.2); + color: var(--accent-primary); + border-color: rgb(from var(--accent-primary) r g b / 0.3); +} + +.tab-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.25rem; + height: 1.25rem; + border-radius: var(--radius-sm); + font-size: var(--text-xs); + font-weight: var(--font-bold); +} + +.tab-icon.operator { background: rgb(from var(--color-operator) r g b / 0.2); color: var(--color-operator); } +.tab-icon.hook { background: rgb(from var(--color-hook) r g b / 0.2); color: var(--color-hook); } +.tab-icon.sensor { background: rgb(from var(--color-sensor) r g b / 0.2); color: var(--color-sensor); } +.tab-icon.trigger { background: rgb(from var(--color-trigger) r g b / 0.2); color: var(--color-trigger); } +.tab-icon.transfer { background: rgb(from var(--color-transfer) r g b / 0.2); color: var(--color-transfer); } +.tab-icon.notifier { background: rgb(from var(--color-notifier) r g b / 0.2); color: var(--color-notifier); } +.tab-icon.secret { background: rgb(from var(--color-secret) r g b / 0.2); color: var(--color-secret); } +.tab-icon.logging { background: rgb(from var(--color-logging) r g b / 0.2); color: var(--color-logging); } +.tab-icon.executor { background: rgb(from var(--color-executor) r g b / 0.2); color: var(--color-executor); } +.tab-icon.bundle { background: rgb(from var(--color-bundle) r g b / 0.2); color: var(--color-bundle); } +.tab-icon.decorator { background: rgb(from var(--color-decorator) r g b / 0.2); color: var(--color-decorator); } + +/* Modules Layout (sidebar + content) */ +.modules-layout { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-8); +} + +@media (min-width: 1024px) { + .modules-layout { + grid-template-columns: 15rem 1fr; + } +} + +/* Sidebar */ +.modules-sidebar { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.sidebar-section { + padding: var(--space-4); +} + +.sidebar-section h3 { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-3); +} + +.category-nav { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.category-btn { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + border-radius: var(--radius-lg); + background: transparent; + color: var(--text-secondary); + border: none; + cursor: pointer; + transition: all var(--transition-base); + text-align: left; +} + +.category-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.category-btn.active { + background: rgb(from var(--accent-primary) r g b / 0.1); + color: var(--accent-primary); + border: 1px solid rgb(from var(--accent-primary) r g b / 0.3); +} + +.category-btn .count { + font-size: var(--text-xs); +} + +.related-nav { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.related-nav a { + display: block; + padding: var(--space-2) var(--space-3); + font-size: var(--text-sm); + color: var(--text-secondary); + border-radius: var(--radius-lg); + transition: all var(--transition-base); + text-decoration: none; +} + +.related-nav a:hover { + color: var(--text-primary); + background: var(--bg-tertiary); +} + +/* Modules section header */ +.modules-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-4); +} + +.module-search-wrapper { + max-width: 16rem; +} + +/* Modules */ +.provider-detail-page .modules { + margin-bottom: var(--space-12); +} + +.provider-detail-page .modules h2 { + font-size: var(--text-xl); + font-weight: var(--font-bold); + color: var(--text-primary); + margin: 0; +} + +.provider-detail-page .modules .grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-3); +} + +.provider-detail-page .modules .module { + padding: var(--space-4); + display: flex; + gap: var(--space-3); +} + +.provider-detail-page .modules .module .icon { + width: 2rem; + height: 2rem; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-sm); + font-weight: var(--font-bold); + flex-shrink: 0; +} + +.provider-detail-page .modules .module .icon.operator { + background: rgb(from var(--color-operator) r g b / 0.2); + color: var(--color-operator); + border: 1px solid rgb(from var(--color-operator) r g b / 0.3); +} + +.provider-detail-page .modules .module .icon.hook { + background: rgb(from var(--color-hook) r g b / 0.2); + color: var(--color-hook); + border: 1px solid rgb(from var(--color-hook) r g b / 0.3); +} + +.provider-detail-page .modules .module .icon.sensor { + background: rgb(from var(--color-sensor) r g b / 0.2); + color: var(--color-sensor); + border: 1px solid rgb(from var(--color-sensor) r g b / 0.3); +} + +.provider-detail-page .modules .module .icon.trigger { + background: rgb(from var(--color-trigger) r g b / 0.2); + color: var(--color-trigger); + border: 1px solid rgb(from var(--color-trigger) r g b / 0.3); +} + +.provider-detail-page .modules .module .icon.transfer { + background: rgb(from var(--color-transfer) r g b / 0.2); + color: var(--color-transfer); + border: 1px solid rgb(from var(--color-transfer) r g b / 0.3); +} + +.provider-detail-page .modules .module .icon.bundle { + background: rgb(from var(--color-bundle) r g b / 0.2); + color: var(--color-bundle); + border: 1px solid rgb(from var(--color-bundle) r g b / 0.3); +} + +.provider-detail-page .modules .module .content { + flex: 1; + min-width: 0; +} + +.provider-detail-page .modules .module .content h3 { + font-size: var(--text-base); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-1); +} + +.provider-detail-page .modules .module .content p { + font-size: var(--text-sm); + color: var(--text-secondary); + margin-bottom: var(--space-3); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.provider-detail-page .modules .module .content .path { + display: block; + font-size: var(--text-xs); + font-family: var(--font-mono); + color: var(--color-gray-800); + color: light-dark(var(--color-gray-800), var(--color-gray-200)); + background: var(--color-gray-100); + background: light-dark(var(--color-gray-100), var(--color-navy-800)); + border: 1px solid var(--color-gray-300); + border: 1px solid light-dark(var(--color-gray-300), var(--color-gray-700)); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Import path + copy button row */ +.provider-detail-page .import-row { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: var(--space-2); +} + +.provider-detail-page .import-row code.path { + flex: 1; + min-width: 0; + margin-top: 0; +} + +/* Module Actions (View Docs, Source) */ +.provider-detail-page .module-actions { + display: flex; + align-items: center; + gap: var(--space-3); + margin-top: var(--space-3); +} + +.provider-detail-page .module-actions .docs-link, +.provider-detail-page .module-actions .source-link { + display: inline-flex; + align-items: center; + gap: 0.25rem; + font-size: var(--text-xs); + text-decoration: none; + transition: color var(--transition-base); +} + +.provider-detail-page .module-actions .docs-link { + color: var(--accent-primary); +} + +.provider-detail-page .module-actions .docs-link:hover { + color: var(--color-cyan-300); +} + +.provider-detail-page .module-actions .source-link { + color: var(--text-muted); +} + +.provider-detail-page .module-actions .source-link:hover { + color: var(--text-primary); +} + +.provider-detail-page .module-actions svg { + width: 0.75rem; + height: 0.75rem; +} + +.provider-detail-page .copy-import { + display: inline-flex; + align-items: center; + padding: 0.375rem; + background: var(--bg-tertiary); + border: 1px solid var(--color-gray-400); + border: 1px solid light-dark(var(--color-gray-400), var(--color-gray-600)); + border-radius: var(--radius-sm); + color: var(--text-muted); + cursor: pointer; + transition: all var(--transition-base); +} + +.provider-detail-page .copy-import:hover { + background: var(--color-gray-200); + background: light-dark(var(--color-gray-200), var(--color-gray-700)); + color: var(--accent-primary); + border-color: var(--accent-primary); +} + +.provider-detail-page .copy-import.copied { + color: var(--color-green-400); + border-color: var(--color-green-400); +} + +.provider-detail-page .copy-import svg { + width: 0.875rem; + height: 0.875rem; +} + +/* Module Highlight */ +.provider-detail-page .module.highlighted { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; +} + +/* Technical */ +.provider-detail-page .technical { + padding: var(--space-5); + margin-bottom: var(--space-6); +} + +.provider-detail-page .technical > div { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-6); +} + +@media (min-width: 768px) { + .provider-detail-page .technical > div { + grid-template-columns: repeat(3, 1fr); + } +} + +.provider-detail-page .technical h3 { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-3); + display: flex; + align-items: center; + gap: var(--space-2); +} + +.provider-detail-page .technical h3 svg { + width: 1rem; + height: 1rem; +} + +.provider-detail-page .technical .airflow-compat h3 svg { + color: var(--color-cyan-400); +} + +.provider-detail-page .technical .python-version h3 svg { + color: var(--color-blue-400); +} + +.provider-detail-page .technical .dependencies h3 svg { + color: var(--color-purple-400); +} + +.provider-detail-page .technical code { + font-size: var(--text-sm); + font-family: var(--font-mono); + color: var(--accent-primary); + background: var(--bg-tertiary); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + display: inline-block; +} + +.provider-detail-page .technical span { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Dependencies */ +.provider-detail-page .technical .dependencies details { + cursor: pointer; + margin-top: var(--space-2); + display: flex; + flex-direction: column; + gap: var(--space-1); + max-height: 12rem; + overflow-y: auto; +} + +.provider-detail-page .technical .dependencies summary { + font-size: var(--text-xs); + color: var(--text-secondary); + list-style: none; + display: flex; + align-items: center; + gap: var(--space-1); + cursor: pointer; + transition: color var(--transition-base); +} + +.provider-detail-page .technical .dependencies summary:hover { + color: var(--accent-primary); +} + +.provider-detail-page .technical .dependencies summary svg { + width: 0.75rem; + height: 0.75rem; + transition: transform var(--transition-base); +} + +.provider-detail-page .technical .dependencies details[open] summary svg { + transform: rotate(90deg); +} + +.provider-detail-page .technical .dependencies summary .hide-label { + display: none; +} + +.provider-detail-page .technical .dependencies details[open] summary .show-label { + display: none; +} + +.provider-detail-page .technical .dependencies details[open] summary .hide-label { + display: inline; +} + +.provider-detail-page .technical .dependencies details code { + font-size: var(--text-xs); + color: var(--color-purple-400); + background: rgb(from var(--color-purple-500) r g b / 0.1); +} + +/* Connection Builder Card */ +.provider-detail-page .connections-card { + padding: var(--space-5); + margin-bottom: var(--space-6); +} + +.provider-detail-page .connections-card > h3 { + font-size: var(--text-sm); + font-weight: var(--font-semibold); + color: var(--text-primary); + margin-bottom: var(--space-3); + display: flex; + align-items: center; + gap: var(--space-2); +} + +.provider-detail-page .connections-card > h3 svg { + width: 1rem; + height: 1rem; + color: var(--color-green-400); +} + +.provider-detail-page .connections-card .no-connections { + font-size: var(--text-sm); + color: var(--text-secondary); +} + +/* Chip grid */ +.provider-detail-page .connections-card .conn-chips { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.provider-detail-page .connections-card .conn-chip { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-3); + font-size: var(--text-sm); + font-family: var(--font-mono); + color: var(--color-green-400); + background: rgb(from var(--color-green-500) r g b / 0.05); + border: 1px solid rgb(from var(--color-green-500) r g b / 0.2); + border-radius: var(--radius-full); + cursor: pointer; + transition: all var(--transition-base); +} + +.provider-detail-page .connections-card .conn-chip:hover { + background: rgb(from var(--color-green-500) r g b / 0.1); + border-color: rgb(from var(--color-green-500) r g b / 0.4); +} + +.provider-detail-page .connections-card .conn-chip.active { + background: rgb(from var(--color-green-500) r g b / 0.15); + border-color: var(--color-green-400); +} + +/* Shared builder panel */ +.provider-detail-page .connections-card .conn-builder-panel { + margin-top: var(--space-3); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.provider-detail-page .connections-card .conn-builder-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-2) var(--space-3); + background: rgb(from var(--color-green-500) r g b / 0.05); + border-bottom: 1px solid var(--border-primary); +} + +.provider-detail-page .connections-card .conn-builder-title { + font-size: var(--text-sm); + font-family: var(--font-mono); + font-weight: var(--font-semibold); + color: var(--color-green-400); +} + +.provider-detail-page .connections-card .conn-builder-docs { + font-size: var(--text-xs); + font-weight: var(--font-semibold); + color: var(--accent-primary); + text-decoration: none; + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-2); + margin: var(--space-2) var(--space-3) 0; + border: 1px solid var(--border-primary); + border-radius: var(--radius-md); + transition: all var(--transition-base); +} + +.provider-detail-page .connections-card .conn-builder-docs:hover { + background: var(--bg-tertiary); + border-color: var(--accent-primary); +} + +.provider-detail-page .connections-card .conn-builder-docs svg { + width: 0.875rem; + height: 0.875rem; +} + +.provider-detail-page .connections-card .conn-builder-docs .external-icon { + width: 0.75rem; + height: 0.75rem; + opacity: 0.6; +} + +.provider-detail-page .connections-card .conn-builder-close { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: var(--space-1); + border-radius: var(--radius-md); + display: flex; + transition: color var(--transition-base); +} + +.provider-detail-page .connections-card .conn-builder-close:hover { + color: var(--text-primary); +} + +.provider-detail-page .connections-card .conn-builder-close svg { + width: 1rem; + height: 1rem; +} + +/* Form fields */ +.provider-detail-page .connections-card .conn-form { + padding: var(--space-3) var(--space-3) var(--space-4); + display: flex; + flex-direction: column; + gap: var(--space-3); +} + +.provider-detail-page .connections-card .conn-field { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.provider-detail-page .connections-card .conn-field > label { + font-size: var(--text-xs); + font-weight: var(--font-semibold); + color: var(--text-secondary); +} + +.provider-detail-page .connections-card .conn-field .field-sensitive { + font-size: 0.625rem; + font-weight: var(--font-normal); + color: var(--color-amber-400); + background: rgb(from var(--color-amber-500) r g b / 0.1); + border: 1px solid rgb(from var(--color-amber-500) r g b / 0.2); + padding: 0 var(--space-1); + border-radius: var(--radius-sm); + vertical-align: middle; + margin-left: var(--space-1); +} + +.provider-detail-page .connections-card .conn-field input[type="text"], +.provider-detail-page .connections-card .conn-field input[type="password"], +.provider-detail-page .connections-card .conn-field input[type="number"], +.provider-detail-page .connections-card .conn-field textarea, +.provider-detail-page .connections-card .conn-field select { + font-size: var(--text-sm); + font-family: var(--font-mono); + padding: var(--space-2); + border: 1px solid var(--border-primary); + border-radius: var(--radius-md); + background: var(--color-input-bg); + color: var(--text-primary); + transition: border-color var(--transition-base); + width: 100%; + box-sizing: border-box; +} + +.provider-detail-page .connections-card .conn-field input:focus, +.provider-detail-page .connections-card .conn-field textarea:focus, +.provider-detail-page .connections-card .conn-field select:focus { + outline: none; + border-color: var(--color-border-focus); +} + +.provider-detail-page .connections-card .conn-field textarea { + resize: vertical; + min-height: 3rem; +} + +.provider-detail-page .connections-card .conn-field .field-help { + font-size: var(--text-xs); + color: var(--text-muted); +} + +.provider-detail-page .connections-card .conn-checkbox { + font-size: var(--text-sm); + color: var(--text-primary); + display: flex; + align-items: center; + gap: var(--space-2); + cursor: pointer; + font-weight: var(--font-normal); +} + +.provider-detail-page .connections-card .conn-checkbox input[type="checkbox"] { + width: auto; + accent-color: var(--color-green-400); +} + +/* Export panel */ +.provider-detail-page .connections-card .conn-export { + margin-top: var(--space-2); + border-top: 1px solid var(--border-primary); + padding-top: var(--space-3); +} + +.provider-detail-page .connections-card .conn-export-tabs { + display: flex; + gap: var(--space-1); + margin-bottom: var(--space-2); +} + +.provider-detail-page .connections-card .conn-export-tab { + padding: var(--space-1) var(--space-3); + font-size: var(--text-xs); + font-weight: var(--font-semibold); + color: var(--text-muted); + background: none; + border: none; + border-radius: var(--radius-lg); + cursor: pointer; + transition: all var(--transition-base); +} + +.provider-detail-page .connections-card .conn-export-tab:hover { + color: var(--text-secondary); +} + +.provider-detail-page .connections-card .conn-export-tab.active { + background: var(--bg-tertiary); + color: var(--color-green-400); +} + +.provider-detail-page .connections-card .conn-export-output { + position: relative; + background: var(--bg-tertiary-light); + background: light-dark(var(--bg-tertiary-light), var(--color-navy-900)); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.provider-detail-page .connections-card .conn-export-output pre { + font-size: var(--text-xs); + font-family: var(--font-mono); + color: var(--text-primary); + padding: var(--space-3); + padding-right: var(--space-10); + margin: 0; + white-space: pre-wrap; + word-break: break-all; + max-height: 12rem; + overflow-y: auto; + background: none; + display: block; + border-radius: 0; +} + +.provider-detail-page .connections-card .conn-copy-btn { + position: absolute; + top: var(--space-2); + right: var(--space-2); + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: var(--space-1); + border-radius: var(--radius-md); + transition: all var(--transition-base); + display: flex; + align-items: center; + justify-content: center; +} + +.provider-detail-page .connections-card .conn-copy-btn:hover { + color: var(--text-primary); + background: var(--bg-tertiary); +} + +.provider-detail-page .connections-card .conn-copy-btn svg { + width: 1rem; + height: 1rem; +} + +/* Search Modal */ +#search-modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); + padding: 10vh 1rem 1rem 1rem; +} + +#search-modal.active { + display: flex; + align-items: flex-start; + justify-content: center; +} + +#search-modal .modal-container { + width: 100%; + max-width: 48rem; + background: var(--bg-primary); + border: 1px solid var(--border-primary); + border-radius: var(--radius-xl); + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2); + overflow: hidden; + display: flex; + flex-direction: column; + max-height: 80vh; +} + +#search-modal header { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-4); + border-bottom: 1px solid var(--border-primary); +} + +#search-modal header svg { + width: 1.25rem; + height: 1.25rem; + color: var(--text-muted); + flex-shrink: 0; +} + +#search-input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: var(--text-primary); + font-size: var(--text-lg); + font-family: inherit; +} + +#search-input::placeholder { + color: var(--text-muted); +} + +#search-close { + background: transparent; + border: none; + padding: var(--space-1); + border-radius: var(--radius-full); + cursor: pointer; + color: var(--text-muted); + transition: all var(--transition-base); + display: flex; + align-items: center; + justify-content: center; +} + +#search-close:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +#search-close svg { + width: 1.25rem; + height: 1.25rem; +} + +#search-modal nav { + display: flex; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border-bottom: 1px solid var(--border-primary); +} + +#search-modal nav button { + padding: var(--space-1) var(--space-3); + font-size: var(--text-sm); + font-weight: var(--font-medium); + border-radius: var(--radius-full); + border: none; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-base); + display: flex; + align-items: center; + gap: var(--space-1); +} + +#search-modal nav button:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +#search-modal nav button.active { + background: rgb(from var(--color-cyan-500) r g b / 0.2); + color: var(--color-cyan-400); + border: 1px solid rgb(from var(--color-cyan-500) r g b / 0.3); +} + +#search-modal nav button .count { + font-size: var(--text-xs); + padding: 0.125rem var(--space-1); + border-radius: var(--radius-full); + background: var(--bg-tertiary); +} + +#search-modal nav button.active .count { + background: rgb(from var(--color-cyan-500) r g b / 0.3); +} + +#search-results { + flex: 1; + overflow-y: auto; + padding: var(--space-2) 0; +} + +#search-results > div { + padding: var(--space-8) var(--space-4); + text-align: center; + color: var(--text-muted); +} + +#search-results > div svg { + width: 3rem; + height: 3rem; + margin: 0 auto var(--space-3); + color: var(--text-muted); +} + +#search-results > div p { + font-size: var(--text-lg); + margin: 0; +} + +#search-results a { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + text-decoration: none; + color: inherit; + transition: background-color var(--transition-base); + border-bottom: 1px solid var(--border-primary); +} + +#search-results a:last-child { + border-bottom: none; +} + +#search-results a:hover, +#search-results a.selected { + background: rgb(from var(--color-cyan-500) r g b / 0.1); +} + +#search-results a > span:first-child { + width: 2rem; + height: 2rem; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--text-sm); + font-weight: var(--font-bold); + flex-shrink: 0; + background: rgb(from var(--color-cyan-500) r g b / 0.2); + color: var(--color-cyan-400); + border: 1px solid rgb(from var(--color-cyan-500) r g b / 0.3); +} + +#search-results a > div { + flex: 1; + min-width: 0; +} + +#search-results a > div > div:first-child { + font-weight: var(--font-medium); + color: var(--text-primary); + margin-bottom: 0.25rem; + display: flex; + align-items: center; + gap: var(--space-2); +} + +#search-results .badge { + padding: 0.125rem var(--space-2); + font-size: var(--text-xs); + font-weight: var(--font-medium); + border-radius: var(--radius); + text-transform: lowercase; +} + +#search-results .badge.operator { + background: rgb(from var(--color-operator) r g b / 0.2); + color: var(--color-operator); + border: 1px solid rgb(from var(--color-operator) r g b / 0.3); +} + +#search-results .badge.hook { + background: rgb(from var(--color-hook) r g b / 0.2); + color: var(--color-hook); + border: 1px solid rgb(from var(--color-hook) r g b / 0.3); +} + +#search-results .badge.sensor { + background: rgb(from var(--color-sensor) r g b / 0.2); + color: var(--color-sensor); + border: 1px solid rgb(from var(--color-sensor) r g b / 0.3); +} + +#search-results .badge.trigger { + background: rgb(from var(--color-trigger) r g b / 0.2); + color: var(--color-trigger); + border: 1px solid rgb(from var(--color-trigger) r g b / 0.3); +} + +#search-results .badge.transfer { + background: rgb(from var(--color-transfer) r g b / 0.2); + color: var(--color-transfer); + border: 1px solid rgb(from var(--color-transfer) r g b / 0.3); +} + +#search-results .badge.bundle { + background: rgb(from var(--color-bundle) r g b / 0.2); + color: var(--color-bundle); + border: 1px solid rgb(from var(--color-bundle) r g b / 0.3); +} + +#search-results .badge.notifier { + background: rgb(from var(--color-notifier) r g b / 0.2); + color: var(--color-notifier); + border: 1px solid rgb(from var(--color-notifier) r g b / 0.3); +} + +#search-results .provider > span:first-child { + background: rgb(from var(--color-cyan-500) r g b / 0.2); + color: var(--color-cyan-400); + border-color: rgb(from var(--color-cyan-500) r g b / 0.3); +} + +#search-results .operator > span:first-child { + background: rgb(from var(--color-operator) r g b / 0.2); + color: var(--color-operator); + border-color: rgb(from var(--color-operator) r g b / 0.3); +} + +#search-results .hook > span:first-child { + background: rgb(from var(--color-hook) r g b / 0.2); + color: var(--color-hook); + border-color: rgb(from var(--color-hook) r g b / 0.3); +} + +#search-results .sensor > span:first-child { + background: rgb(from var(--color-sensor) r g b / 0.2); + color: var(--color-sensor); + border-color: rgb(from var(--color-sensor) r g b / 0.3); +} + +#search-results .trigger > span:first-child { + background: rgb(from var(--color-trigger) r g b / 0.2); + color: var(--color-trigger); + border-color: rgb(from var(--color-trigger) r g b / 0.3); +} + +#search-results .transfer > span:first-child { + background: rgb(from var(--color-transfer) r g b / 0.2); + color: var(--color-transfer); + border-color: rgb(from var(--color-transfer) r g b / 0.3); +} + +#search-results .bundle > span:first-child { + background: rgb(from var(--color-bundle) r g b / 0.2); + color: var(--color-bundle); + border-color: rgb(from var(--color-bundle) r g b / 0.3); +} + +#search-results .notifier > span:first-child { + background: rgb(from var(--color-notifier) r g b / 0.2); + color: var(--color-notifier); + border-color: rgb(from var(--color-notifier) r g b / 0.3); +} + +#search-results a > div > div:last-child { + font-size: var(--text-sm); + color: var(--text-secondary); + display: flex; + gap: var(--space-2); + align-items: center; +} + +#search-results a > div > div:last-child svg { + flex-shrink: 0; +} + +#search-results a > div > div:last-child > span { + display: flex; + align-items: center; + gap: 0.25rem; +} + +#search-results a > div > div:last-child > span:last-child { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#search-results a > svg:last-child { + width: 1rem; + height: 1rem; + color: var(--color-cyan-400); + flex-shrink: 0; +} + +#search-modal footer { + display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-3) var(--space-4); + border-top: 1px solid var(--border-primary); + font-size: var(--text-xs); + color: var(--text-muted); +} + +#search-modal footer > span { + display: flex; + align-items: center; + gap: 0.25rem; +} + +#search-modal footer kbd { + padding: 0.125rem 0.375rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-primary); + border-radius: var(--radius); + font-family: var(--font-mono); + font-size: var(--text-xs); +} + +/* Skip to content link */ +.skip-link { + position: absolute; + top: -100%; + left: var(--space-4); + padding: var(--space-2) var(--space-4); + background: var(--accent-primary); + color: #fff; + border-radius: var(--radius-md); + z-index: 10000; + font-weight: var(--font-semibold); + text-decoration: none; +} + +.skip-link:focus { + top: var(--space-2); +} + +/* Screen reader only */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +/* Global focus-visible styles */ +:focus-visible { + outline: 2px solid var(--color-cyan-500); + outline-offset: 2px; +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +} + +/* 404 Page */ +.error-page { + text-align: center; + padding: var(--space-24) var(--space-4); +} + +.error-page h1 { + font-size: var(--text-6xl); + font-weight: var(--font-bold); + color: var(--text-muted); + margin-bottom: var(--space-4); +} + +.error-page p { + font-size: var(--text-lg); + color: var(--text-secondary); + margin-bottom: var(--space-8); +} + +.error-links { + display: flex; + gap: var(--space-4); + justify-content: center; + flex-wrap: wrap; +} + +.error-links a { + padding: var(--space-2-5) var(--space-5); + background: var(--accent-primary); + color: #fff; + border-radius: var(--radius-lg); + text-decoration: none; + font-weight: var(--font-medium); +} + +.error-links a:hover { + opacity: 0.9; +} diff --git a/registry/src/css/tokens.css b/registry/src/css/tokens.css new file mode 100644 index 0000000000000..dcaa444bf4f1a --- /dev/null +++ b/registry/src/css/tokens.css @@ -0,0 +1,273 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* Design Tokens - Extracted from Tailwind config */ +:root { + /* Navy Color Scale */ + --color-navy-950: #050810; + --color-navy-900: #0a0f1a; + --color-navy-800: #0f172a; + --color-navy-700: #1e293b; + --color-navy-600: #4f46e5; + --color-navy-500: #6366f1; + + /* Cyan Accent Scale */ + --color-cyan-50: #ecfeff; + --color-cyan-100: #cffafe; + --color-cyan-200: #a5f3fc; + --color-cyan-300: #67e8f9; + --color-cyan-400: #22d3ee; + --color-cyan-500: #06b6d4; + --color-cyan-600: #0891b2; + --color-cyan-700: #0e7490; + --color-cyan-800: #155e75; + --color-cyan-900: #164e63; + + /* Gray Scale */ + --color-gray-50: #f9fafb; + --color-gray-100: #f1f5f9; + --color-gray-200: #e2e8f0; + --color-gray-300: #cbd5e1; + --color-gray-400: #94a3b8; + --color-gray-500: #64748b; + --color-gray-600: #475569; + --color-gray-700: #334155; + --color-gray-800: #1e293b; + --color-gray-900: #0f172a; + + /* Module Type Colors */ + --color-operator: #22c55e; + --color-hook: #3b82f6; + --color-sensor: #f59e0b; + --color-trigger: #a855f7; + --color-transfer: #ec4899; + + /* Module Type Extended Colors */ + --color-notifier: #6366f1; + --color-secret: #f43f5e; + --color-logging: #14b8a6; + --color-executor: #f97316; + --color-bundle: #0ea5e9; + --color-decorator: #d946ef; + + /* Additional Colors */ + --color-green-400: #4ade80; + --color-green-500: #22c55e; + --color-green-600: #16a34a; + --color-blue-400: #60a5fa; + --color-blue-500: #3b82f6; + --color-blue-600: #2563eb; + --color-amber-400: #fbbf24; + --color-amber-500: #f59e0b; + --color-amber-600: #d97706; + --color-purple-400: #c084fc; + --color-purple-500: #a855f7; + --color-purple-600: #9333ea; + --color-pink-400: #f472b6; + --color-pink-500: #ec4899; + --color-indigo-400: #818cf8; + --color-indigo-500: #6366f1; + --color-rose-400: #fb7185; + --color-rose-500: #f43f5e; + --color-rose-600: #e11d48; + --color-teal-400: #2dd4bf; + --color-teal-500: #14b8a6; + --color-orange-400: #fb923c; + --color-orange-500: #f97316; + --color-fuchsia-400: #e879f9; + --color-fuchsia-500: #d946ef; + + /* Spacing Scale (0.25rem = 4px base) */ + --space-0: 0; + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-2-5: 0.625rem; /* 10px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + + /* Font Families */ + --font-sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif; + --font-mono: 'JetBrains Mono', 'Menlo', 'Monaco', monospace; + + /* Font Sizes */ + --text-xs: 0.75rem; /* 12px */ + --text-sm: 0.875rem; /* 14px */ + --text-base: 1rem; /* 16px */ + --text-lg: 1.125rem; /* 18px */ + --text-xl: 1.25rem; /* 20px */ + --text-2xl: 1.5rem; /* 24px */ + --text-3xl: 1.875rem; /* 30px */ + --text-4xl: 2.25rem; /* 36px */ + --text-5xl: 3rem; /* 48px */ + --text-6xl: 3.75rem; /* 60px */ + + /* Font Weights */ + --font-normal: 400; + --font-medium: 500; + --font-semibold: 600; + --font-bold: 700; + + /* Line Heights */ + --leading-none: 1; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + + /* Border Radius */ + --radius-sm: 0.125rem; /* 2px */ + --radius: 0.25rem; /* 4px */ + --radius-md: 0.375rem; /* 6px */ + --radius-lg: 0.5rem; /* 8px */ + --radius-xl: 0.75rem; /* 12px */ + --radius-2xl: 1rem; /* 16px */ + --radius-3xl: 1.5rem; /* 24px */ + --radius-full: 9999px; + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); + + /* Transitions */ + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1); + + /* Breakpoints (for reference in media queries) */ + --screen-sm: 640px; + --screen-md: 768px; + --screen-lg: 1024px; + --screen-xl: 1280px; + --screen-2xl: 1536px; + + /* Light Theme Variables */ + --bg-primary-light: #ffffff; + --bg-secondary-light: #f8fafc; + --bg-tertiary-light: var(--color-gray-100); + + --text-primary-light: var(--color-gray-900); + --text-secondary-light: var(--color-gray-600); + --text-muted-light: var(--color-gray-500); + + --border-primary-light: var(--color-gray-200); + --border-secondary-light: var(--color-gray-300); + --border-hover-light: var(--color-cyan-600); + + --accent-primary-light: var(--color-cyan-600); + --accent-secondary-light: var(--color-cyan-700); + + /* Dark Theme Variables */ + --bg-primary-dark: var(--color-navy-900); + --bg-secondary-dark: var(--color-navy-800); + --bg-tertiary-dark: var(--color-navy-700); + + --text-primary-dark: var(--color-gray-100); + --text-secondary-dark: var(--color-gray-400); + --text-muted-dark: var(--color-gray-500); + + --border-primary-dark: var(--color-navy-700); + --border-secondary-dark: var(--color-navy-600); + --border-hover-dark: var(--color-cyan-500); + + --accent-primary-dark: var(--color-cyan-400); + --accent-secondary-dark: var(--color-cyan-500); + + /* Computed Theme Variables (using light-dark()) + * + * These variables use the CSS light-dark() function which automatically + * switches between light and dark values based on the color-scheme property. + * + * Progressive enhancement: We declare each variable twice: + * 1. First with the light value as fallback for browsers without light-dark() support + * 2. Then with light-dark() which overrides in supporting browsers + */ + --bg-primary: var(--bg-primary-light); + --bg-primary: light-dark(var(--bg-primary-light), var(--bg-primary-dark)); + --bg-secondary: var(--bg-secondary-light); + --bg-secondary: light-dark(var(--bg-secondary-light), var(--bg-secondary-dark)); + --bg-surface-alt: var(--bg-secondary-light); + --bg-surface-alt: light-dark(var(--bg-secondary-light), var(--bg-primary-dark)); + --bg-tertiary: var(--bg-tertiary-light); + --bg-tertiary: light-dark(var(--bg-tertiary-light), var(--bg-tertiary-dark)); + + --text-primary: var(--text-primary-light); + --text-primary: light-dark(var(--text-primary-light), var(--text-primary-dark)); + --text-secondary: var(--text-secondary-light); + --text-secondary: light-dark(var(--text-secondary-light), var(--text-secondary-dark)); + --text-muted: var(--text-muted-light); + --text-muted: light-dark(var(--text-muted-light), var(--text-muted-dark)); + + --border-primary: var(--border-primary-light); + --border-primary: light-dark(var(--border-primary-light), var(--border-primary-dark)); + --border-secondary: var(--border-secondary-light); + --border-secondary: light-dark(var(--border-secondary-light), var(--border-secondary-dark)); + --border-hover: var(--border-hover-light); + --border-hover: light-dark(var(--border-hover-light), var(--border-hover-dark)); + + --accent-primary: var(--accent-primary-light); + --accent-primary: light-dark(var(--accent-primary-light), var(--accent-primary-dark)); + --accent-secondary: var(--accent-secondary-light); + --accent-secondary: light-dark(var(--accent-secondary-light), var(--accent-secondary-dark)); + + /* Component-specific color variables */ + --color-card-bg: #ffffff; + --color-card-bg: light-dark(#ffffff, rgba(15, 23, 42, 0.5)); + --color-card-bg-hover: var(--bg-tertiary-light); + --color-card-bg-hover: light-dark(var(--bg-tertiary-light), rgba(15, 23, 42, 0.8)); + --color-card-border-hover: rgba(6, 182, 212, 0.4); + --color-card-border-hover: light-dark(rgba(6, 182, 212, 0.4), rgba(6, 182, 212, 0.5)); + + --color-scrollbar-track: var(--bg-tertiary-light); + --color-scrollbar-track: light-dark(var(--bg-tertiary-light), var(--color-navy-800)); + --color-scrollbar-thumb: var(--color-gray-300); + --color-scrollbar-thumb: light-dark(var(--color-gray-300), var(--color-navy-600)); + --color-scrollbar-thumb-hover: var(--color-gray-400); + --color-scrollbar-thumb-hover: light-dark(var(--color-gray-400), var(--color-navy-500)); + + --color-input-bg: var(--bg-tertiary-light); + --color-input-bg: light-dark(var(--bg-tertiary-light), var(--bg-tertiary-dark)); + --color-input-border: var(--color-gray-200); + --color-input-border: light-dark(var(--color-gray-200), var(--color-gray-700)); + + --color-header-bg: rgba(255, 255, 255, 0.95); + --color-header-bg: light-dark(rgba(255, 255, 255, 0.95), rgba(10, 15, 26, 0.95)); + --color-header-border: var(--color-gray-200); + --color-header-border: light-dark(var(--color-gray-200), var(--border-primary-dark)); + + --color-widget-bg: #ffffff; + --color-widget-bg: light-dark(#ffffff, var(--color-navy-800)); + + --color-nav-link: var(--color-gray-700); + --color-nav-link: light-dark(var(--color-gray-700), var(--color-gray-300)); + --color-border-focus: var(--color-cyan-600); + --color-border-focus: light-dark(var(--color-cyan-600), rgba(6, 182, 212, 0.5)); +} diff --git a/registry/src/explore.njk b/registry/src/explore.njk new file mode 100644 index 0000000000000..705887cf286b6 --- /dev/null +++ b/registry/src/explore.njk @@ -0,0 +1,160 @@ +--- +layout: base.njk +title: Explore +description: Discover Airflow providers by category and use case +mainClass: explore-page +--- + +
+ + + + +
+ {% for category in exploreCategories %} + {% set categoryProviders = exploreCategoryProviders[category.id] or [] %} + + + {% endfor %} +
+ + + + + + +
+ + diff --git a/registry/src/index.njk b/registry/src/index.njk new file mode 100644 index 0000000000000..e8d6a475c7c70 --- /dev/null +++ b/registry/src/index.njk @@ -0,0 +1,222 @@ +--- +layout: base.njk +title: Discover Providers, Operators & Hooks for Apache Airflow +--- + +{% set totalProviders = providers.providers.length %} +{% set totalModules = modules.modules.length %} + +
+
+
+
+
+
+ +
+
+ + Discover {{ totalProviders }}+ providers and {{ totalModules }}+ modules +
+ +

+ Find the perfect + Airflow components + for your data pipelines +

+ +

+ Browse operators, hooks, sensors, and more from the official Apache Airflow provider ecosystem. +

+ + + + +
+
+ +
+
+
+
+
{{ totalProviders }}+
+
Providers
+
+
+
{{ totalModules | formatLargeNumber }}+
+
Modules
+
+
+
{{ statsData.totalDownloads | formatDownloads }}+
+
Monthly Downloads
+
+
+
{{ statsData.totalConnectionTypes }}+
+
Integrations
+
+
+
+
+ + + + +{% set allWithDate = [] %} +{% for provider in providers.providers %} + {% if provider.first_released %} + {% set allWithDate = (allWithDate.push(provider), allWithDate) %} + {% endif %} +{% endfor %} +{% set newProviders = allWithDate | sort(false, false, 'first_released') | reverse | slice(0, 4) %} +{% if newProviders.length > 0 %} +
+
+
+
+

New Providers

+

Recently added to the Airflow ecosystem

+
+ + View all + + + + +
+ +
    + {% for provider in newProviders %} +
  • + {% include "provider-card.njk" %} +
  • + {% endfor %} +
+
+
+{% endif %} + + +
+ +
+ +
+
+
+

Ready to get started?

+

Install any provider with a single command

+
+ +
+ pip install apache-airflow-providers-amazon +
+
+
+ + diff --git a/registry/src/js/connection-builder.js b/registry/src/js/connection-builder.js new file mode 100644 index 0000000000000..a316a99cb565e --- /dev/null +++ b/registry/src/js/connection-builder.js @@ -0,0 +1,421 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +// Connection Builder - chip grid with shared expansion panel +(function () { + var dataEl = document.getElementById("connections-data"); + if (!dataEl) return; + + var connectionTypes; + try { + connectionTypes = JSON.parse(dataEl.textContent || "[]"); + } catch (e) { + return; + } + if (!connectionTypes || !connectionTypes.length) return; + + // Index connection data by connection_type + var connDataMap = {}; + connectionTypes.forEach(function (ct) { + connDataMap[ct.connection_type] = ct; + }); + + var panel = document.getElementById("conn-builder-panel"); + var formContainer = document.getElementById("conn-builder-form"); + var titleEl = document.getElementById("conn-builder-title"); + var docsLink = document.getElementById("conn-builder-docs"); + var closeBtn = document.getElementById("conn-builder-close"); + var chips = document.querySelectorAll(".conn-chip"); + + if (!panel || !formContainer || !titleEl) return; + + var activeConnType = null; + var renderedForms = {}; // Cache rendered HTML per conn type + var debounceTimers = {}; + + // Chip click: select/deselect connection type + chips.forEach(function (chip) { + chip.addEventListener("click", function () { + var connType = chip.dataset.connType; + + // Toggle off if already active + if (activeConnType === connType) { + closePanel(); + return; + } + + // Deactivate previous chip + chips.forEach(function (c) { c.classList.remove("active"); }); + chip.classList.add("active"); + activeConnType = connType; + + // Set title + titleEl.textContent = connType; + + // Show/hide docs link (derive per-connection-type URL) + if (docsLink) { + var baseDocsUrl = chip.dataset.docsUrl; + if (baseDocsUrl) { + var perTypeUrl = baseDocsUrl.replace(/index\.html$/, connType + ".html"); + docsLink.href = perTypeUrl; + docsLink.hidden = false; + } else { + docsLink.hidden = true; + } + } + + // Render form (or restore cached) + var data = connDataMap[connType]; + if (!data) return; + + if (renderedForms[connType]) { + formContainer.innerHTML = renderedForms[connType]; + attachFormListeners(formContainer, connType, data); + } else { + renderConnectionForm(formContainer, connType, data); + renderedForms[connType] = formContainer.innerHTML; + } + + // Show panel + panel.hidden = false; + }); + }); + + // Close button + if (closeBtn) { + closeBtn.addEventListener("click", closePanel); + } + + function closePanel() { + // Save current form state before closing + if (activeConnType) { + renderedForms[activeConnType] = formContainer.innerHTML; + } + chips.forEach(function (c) { c.classList.remove("active"); }); + panel.hidden = true; + activeConnType = null; + } + + function renderConnectionForm(container, connType, data) { + var connIdDefault = connType.replace(/[^a-zA-Z0-9]/g, "_"); + var html = '
'; + html += ''; + html += + ''; + html += "
"; + + // Standard fields + var standardOrder = ["host", "port", "login", "password", "schema", "extra", "description"]; + var stdFields = data.standard_fields || {}; + standardOrder.forEach(function (key) { + var field = stdFields[key]; + if (!field || !field.visible) return; + html += renderField(connType, key, field, false); + }); + + // Custom fields + var customFields = data.custom_fields || {}; + Object.keys(customFields).forEach(function (key) { + html += renderField(connType, key, customFields[key], true); + }); + + // Export panel + html += renderExportPanel(connType); + + container.innerHTML = html; + attachFormListeners(container, connType, data); + updateExports(connType, container, data); + } + + function attachFormListeners(container, connType, data) { + // Input listeners for live export updates + container.querySelectorAll("input, textarea, select").forEach(function (input) { + input.addEventListener("input", function () { + debouncedUpdate(connType, container, data); + }); + input.addEventListener("change", function () { + debouncedUpdate(connType, container, data); + }); + }); + + // Tab switching + container.querySelectorAll(".conn-export-tab").forEach(function (tab) { + tab.addEventListener("click", function () { + container.querySelectorAll(".conn-export-tab").forEach(function (t) { + t.classList.remove("active"); + }); + tab.classList.add("active"); + var format = tab.dataset.format; + container.querySelectorAll(".conn-export-content").forEach(function (c) { + c.hidden = c.dataset.format !== format; + }); + }); + }); + + // Copy buttons + container.querySelectorAll(".conn-copy-btn").forEach(function (btn) { + btn.addEventListener("click", function () { + var targetId = btn.dataset.copyTarget; + var target = document.getElementById(targetId); + if (!target || !navigator.clipboard) return; + navigator.clipboard.writeText(target.textContent || "").then( + function () { + var origHTML = btn.innerHTML; + btn.innerHTML = + ''; + setTimeout(function () { btn.innerHTML = origHTML; }, 2000); + }, + function () {} + ); + }); + }); + + // Update exports on attach (handles restored forms) + updateExports(connType, container, data); + } + + function renderField(connType, key, field, isCustom) { + var label = field.label || key; + var id = "conn-" + connType + "-" + key; + var isSensitive = + key === "password" || field.is_sensitive || field.format === "password"; + var fieldType = field.type || "string"; + var html = '
'; + html += '"; + + if (field.enum && field.enum.length) { + html += '"; + } else if (fieldType === "boolean") { + var checked = field.default === true || field.default === "true" ? " checked" : ""; + html += '"; + } else if (key === "extra" || fieldType === "object") { + html += '