commit 6bc69ac13e703fae5acee0d7e1c386771c2a3d7b Author: wehub-resource-sync Date: Mon Jul 13 12:25:08 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0e84d36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 +tab_width = 2 + +[.dist-tag] +insert_final_newline = false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..3218a35 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,38 @@ +name: "CodeQL" + +on: + push: + branches: ["*.x"] + pull_request: + branches: ["*.x"] + schedule: + - cron: '0 6 * * 1' + +jobs: + analyze: + runs-on: ubuntu-latest + + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ['javascript'] + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/deploy-editor.yml b/.github/workflows/deploy-editor.yml new file mode 100644 index 0000000..4b3d91b --- /dev/null +++ b/.github/workflows/deploy-editor.yml @@ -0,0 +1,51 @@ +name: Deploy Editor + +on: + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + test: + uses: dicebear/dicebear/.github/workflows/test.yml@10.x + deploy: + needs: test + environment: editor + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + # Remove all languages except English due GDPR reasons, because we can't translate the legal pages. + - name: Cleanup languages + run: find apps/editor/src/messages -type f ! -name 'en.json' -exec rm -f {} + + + - name: Build editor + env: + VITE_PRIVACY_POLICY_URL: ${{ vars.VITE_PRIVACY_POLICY_URL }} + VITE_COOKIE_POLICY_URL: ${{ vars.VITE_COOKIE_POLICY_URL }} + VITE_LEGAL_NOTICE_URL: ${{ vars.VITE_LEGAL_NOTICE_URL }} + run: npx turbo run build --filter='@dicebear/editor...' + + - name: Deploy to Bunny.net + uses: ayeressian/bunnycdn-storage-deploy@251c77f4b9e7e683ad00086de2bef5770c3386b2 # v2.4.5 + with: + source: 'apps/editor/dist' + destination: '' + storageZoneName: '${{ secrets.EDITOR_STORAGE_NAME }}' + storagePassword: '${{ secrets.EDITOR_STORAGE_PASSWORD }}' + accessKey: '${{ secrets.CDN_ACCESS_KEY }}' + pullZoneId: '${{ secrets.EDITOR_PULL_ZONE_ID }}' + upload: 'true' + remove: 'true' + purgePullZone: 'true' diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 0000000..a9af4c9 --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,52 @@ +name: Deploy Website + +on: + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + test: + uses: dicebear/dicebear/.github/workflows/test.yml@10.x + deploy: + needs: test + environment: ${{ github.ref_name == '9.x' && 'website-v9' || 'website-latest' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Download legal pages + env: + PAT: ${{ secrets.PAT }} + run: ./apps/docs/scripts/download-legal-pages.sh + + - name: Build with VitePress + env: + VITE_PRIVACY_POLICY_URL: ${{ vars.VITE_PRIVACY_POLICY_URL }} + VITE_COOKIE_POLICY_URL: ${{ vars.VITE_COOKIE_POLICY_URL }} + VITE_LEGAL_NOTICE_URL: ${{ vars.VITE_LEGAL_NOTICE_URL }} + run: npx turbo run build --filter='@dicebear/docs...' + + - name: Deploy to Bunny.net + uses: ayeressian/bunnycdn-storage-deploy@251c77f4b9e7e683ad00086de2bef5770c3386b2 # v2.4.5 + with: + source: 'apps/docs/.vitepress/dist' + destination: '' + storageZoneName: '${{ secrets.WEBSITE_STORAGE_NAME }}' + storagePassword: '${{ secrets.WEBSITE_STORAGE_PASSWORD }}' + accessKey: '${{ secrets.CDN_ACCESS_KEY }}' + pullZoneId: '${{ secrets.WEBSITE_PULL_ZONE_ID }}' + upload: 'true' + remove: 'true' + purgePullZone: 'true' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..0e88073 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,141 @@ +name: Publish + +on: + push: + tags: + - 'v*' + +permissions: + id-token: write # Required for OIDC + contents: read + +jobs: + test: + uses: dicebear/dicebear/.github/workflows/test.yml@10.x + + publish: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: 'https://registry.npmjs.org' + + - run: npm ci + - run: npx turbo run build --filter='!@dicebear/docs' --filter='!@dicebear/editor' + + - name: Determine dist tag + shell: bash + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + TAG=${GITHUB_REF#refs/tags/} + if [[ "$TAG" =~ -(alpha|beta|rc)\. ]]; then + echo "DIST_TAG=next" >> $GITHUB_ENV + else + git fetch origin "$DEFAULT_BRANCH" --depth=1 + if git merge-base --is-ancestor "$GITHUB_SHA" "origin/$DEFAULT_BRANCH"; then + echo "DIST_TAG=latest" >> $GITHUB_ENV + else + MAJOR=$(echo "$TAG" | sed 's/^v//' | cut -d. -f1) + echo "DIST_TAG=v${MAJOR}-lts" >> $GITHUB_ENV + fi + fi + + - run: node scripts/publish.mjs "$DIST_TAG" + + publish-python: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Build dicebear-core + working-directory: src/python/core + run: | + python -m pip install --upgrade build + python -m build + + # Trusted Publishing (OIDC) — no token. Configure the publisher on PyPI: + # project dicebear-core, repo dicebear/dicebear, workflow publish.yml. + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: src/python/core/dist/ + + publish-rust: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: dtolnay/rust-toolchain@stable + + - name: Verify the crate compiles + working-directory: src/rust/core + run: cargo check --all-features + + # Trusted Publishing (OIDC) — no token. Configure the publisher on + # crates.io: crate dicebear-core, repo dicebear/dicebear, workflow + # publish.yml. + - name: Authenticate to crates.io + id: auth + uses: rust-lang/crates-io-auth-action@v1.0.4 + + - name: Publish to crates.io + working-directory: src/rust/core + run: cargo publish + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + + publish-pub: + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: dart-lang/setup-dart@v1 + + # Automated publishing (OIDC via setup-dart, no stored token). Configure + # on pub.dev: package dicebear_core, repository dicebear/dicebear, tag + # pattern v{{version}}. Unlike the Go and PHP ports, pub.dev publishes + # straight from the monorepo subdirectory — no split repository. + - name: Copy the project changelog into the package + # CHANGELOG.md inside the package is a git-ignored build artifact: + # pub.dev expects it in the package directory, while the repository + # maintains a single changelog at the root. + run: cp CHANGELOG.md src/dart/core/CHANGELOG.md + # The first release is published manually (pub.dev's automated-publishing + # settings only exist once the package does), so skip cleanly when the + # pubspec version is already live. This also makes re-runs of the + # workflow safe. + - name: Check whether this version is already on pub.dev + id: pub-version + working-directory: src/dart/core + run: | + VERSION=$(sed -n 's/^version: //p' pubspec.yaml) + if curl -fsSL https://pub.dev/api/packages/dicebear_core \ + | jq -e --arg v "$VERSION" '.versions[] | select(.version == $v)' \ + > /dev/null; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Publish to pub.dev + if: steps.pub-version.outputs.exists == 'false' + working-directory: src/dart/core + run: dart pub publish --force diff --git a/.github/workflows/split-go-core.yml b/.github/workflows/split-go-core.yml new file mode 100644 index 0000000..a6dff4d --- /dev/null +++ b/.github/workflows/split-go-core.yml @@ -0,0 +1,41 @@ +name: Split Go Core + +on: + push: + branches: + - '*.x' + tags: + - 'v*' + +jobs: + split: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: danharrin/monorepo-split-github-action@v2.4.5 + env: + GITHUB_TOKEN: ${{ secrets.SPLIT_TOKEN }} + with: + package_directory: src/go/core + repository_organization: dicebear + repository_name: dicebear-go + branch: ${{ github.ref_name }} + user_name: dicebear-bot + user_email: bot@dicebear.com + + - if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: danharrin/monorepo-split-github-action@v2.4.5 + env: + GITHUB_TOKEN: ${{ secrets.SPLIT_TOKEN }} + with: + package_directory: src/go/core + repository_organization: dicebear + repository_name: dicebear-go + tag: ${{ github.ref_name }} + user_name: dicebear-bot + user_email: bot@dicebear.com diff --git a/.github/workflows/split-php-core.yml b/.github/workflows/split-php-core.yml new file mode 100644 index 0000000..2ce0e96 --- /dev/null +++ b/.github/workflows/split-php-core.yml @@ -0,0 +1,41 @@ +name: Split PHP Core + +on: + push: + branches: + - '*.x' + tags: + - 'v*' + +jobs: + split: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: danharrin/monorepo-split-github-action@v2.4.5 + env: + GITHUB_TOKEN: ${{ secrets.SPLIT_TOKEN }} + with: + package_directory: src/php/core + repository_organization: dicebear + repository_name: dicebear-php + branch: ${{ github.ref_name }} + user_name: dicebear-bot + user_email: bot@dicebear.com + + - if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: danharrin/monorepo-split-github-action@v2.4.5 + env: + GITHUB_TOKEN: ${{ secrets.SPLIT_TOKEN }} + with: + package_directory: src/php/core + repository_organization: dicebear + repository_name: dicebear-php + tag: ${{ github.ref_name }} + user_name: dicebear-bot + user_email: bot@dicebear.com diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..99af429 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,174 @@ +name: Test + +on: + push: + branches: + - '*.x' + pull_request: + branches: + - '*.x' + workflow_call: {} + +jobs: + test-js: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20, 22, 24, 25] + # See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases + + steps: + - uses: actions/checkout@v6 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npx turbo run build --filter='!@dicebear/docs' --filter='!@dicebear/editor' + - run: npx turbo run test + + test-php: + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: ['8.2', '8.3', '8.4', '8.5'] + + steps: + - uses: actions/checkout@v6 + - name: Use PHP ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring + - name: Install dependencies + working-directory: src/php/core + run: composer install --no-interaction --prefer-dist + - name: Run PHPStan + working-directory: src/php/core + run: vendor/bin/phpstan analyse --no-progress + - name: Run tests + working-directory: src/php/core + run: vendor/bin/phpunit + + test-python: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] + + steps: + - uses: actions/checkout@v6 + - name: Use Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + working-directory: src/python/core + run: pip install -e ".[dev]" + - name: Run Ruff + working-directory: src/python/core + run: | + ruff check . + ruff format --check . + - name: Run mypy + working-directory: src/python/core + run: mypy src + - name: Run tests + working-directory: src/python/core + run: pytest + + test-rust: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + - name: Check formatting + working-directory: src/rust/core + run: cargo fmt --check + - name: Run Clippy + working-directory: src/rust/core + run: cargo clippy --all-targets --all-features -- -D warnings + - name: Run tests + working-directory: src/rust/core + run: cargo test + + test-go: + runs-on: ubuntu-latest + + strategy: + matrix: + go-version: ['1.23', '1.24', '1.25'] + + steps: + - uses: actions/checkout@v6 + - name: Use Go ${{ matrix.go-version }} + uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} + - name: Check formatting + working-directory: src/go/core + run: test -z "$(gofmt -l .)" || (gofmt -l . && exit 1) + - name: Run go vet + working-directory: src/go/core + run: go vet ./... + - name: Run tests + working-directory: src/go/core + run: go test ./... + + test-dart: + runs-on: ubuntu-latest + + strategy: + matrix: + # Lower bound = the pubspec `environment.sdk` floor; keep in sync. + sdk: ['3.4', 'stable'] + + steps: + - uses: actions/checkout@v6 + - name: Use Dart ${{ matrix.sdk }} + uses: dart-lang/setup-dart@v1 + with: + sdk: ${{ matrix.sdk }} + - name: Install dependencies + working-directory: src/dart/core + run: dart pub get + - name: Check the embedded web fixtures are up to date + # The web parity suite embeds a copy of the fixtures (dart2js cannot + # read files); regenerate it and fail if it drifted from the canonical + # tests/fixtures/parity. + working-directory: src/dart/core + run: | + dart run tool/generate_web_fixtures.dart + dart format test/parity/embedded_fixtures.dart + git diff --exit-code test/parity/embedded_fixtures.dart + - name: Check formatting + working-directory: src/dart/core + run: dart format --output=none --set-exit-if-changed . + - name: Analyze + working-directory: src/dart/core + run: dart analyze --fatal-infos + - name: Run tests + working-directory: src/dart/core + run: dart test + - name: Run web-eligible tests on dart2js + # The fixture suites are VM-only (they read disk via dart:io), so the + # dart2js build's number formatting and 32-bit PRNG arithmetic is + # asserted by the embedded web_parity_test under Chrome. + # ubuntu-latest ships google-chrome-stable. + working-directory: src/dart/core + run: dart test -p chrome + - name: Copy the project changelog into the package + # CHANGELOG.md inside the package is a git-ignored build artifact: + # pub.dev expects it in the package directory, while the repository + # maintains a single changelog at the root. + run: cp CHANGELOG.md src/dart/core/CHANGELOG.md + - name: Validate the publishable archive + working-directory: src/dart/core + run: dart pub publish --dry-run diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8828275 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Cache and log files +.turbo +.eslintcache +.DS_Store +yarn-error.log + +# Dependencies +node_modules + +# Local environment files +.env*.local diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0e94f89 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +package.json +LICENSE +.github +tests diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..40da83e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "proseWrap": "always", + "tabWidth": 2 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c634de7 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "vue.volar", + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..22eea73 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.wordBasedSuggestions": "off" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..839d468 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,222 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [10.3.0] - 2026-06-13 + +### Added + +- **Core:** Every rendered SVG now starts with the generator comment + `` as the first child of + the root `` element. The comment is byte-identical across the JavaScript, + PHP, Python, Rust, Go, and Dart libraries. The byte output of every avatar + changes as a result, including data URIs and content hashes, so consumers that + compare rendered SVGs against stored snapshots need to update them. SVG + optimizers that strip comments (e.g. SVGO with default settings) remove it + again. +- **Dart library:** A new Dart implementation (the + [`dicebear_core`](https://pub.dev/packages/dicebear_core) package) that + produces identical output to the JavaScript library when given the same styles + and options. It validates style definitions and options against the shared + schemas (via `dicebear_schema`) and pairs with the `dicebear_styles` package. +- **Core (PHP, Python):** Added `Style::fromJson()` (PHP) and + `Style.from_json()` (Python) to build a style from a raw JSON string without a + separate `json_decode(..., true)` / `json.loads(...)` call. Malformed JSON + raises the language's native parse error (`JsonException` / + `json.JSONDecodeError`); an invalid definition raises the usual + `StyleValidationError`. Mirrors `Style::from_str` (Rust) and `Style.parse` + (Dart); the existing array/dict constructor is unchanged. + +### Deprecated + +- **Core (JS, PHP, Python):** Passing a raw style definition to `Avatar` is + deprecated; pass a `Style` instead + (`new Avatar(new Style(definition), options)`), which also lets you reuse one + parsed style across many avatars. The definition still works for now and + renders identically, but emits a deprecation warning (a one-time + `console.warn` in JS, `E_USER_DEPRECATED` in PHP, `DeprecationWarning` in + Python) and will be removed in v11. The Dart, Rust and Go libraries already + require a `Style`, so this brings every port to the same `Avatar(style, …)` + call. + +## [10.2.0] - 2026-06-10 + +### Added + +- **Go library:** A new Go implementation (the + `github.com/dicebear/dicebear-go/v10` module) that produces identical output + to the JavaScript library when given the same styles and options. + +### Fixed + +- **Core:** `Color.luminance()` now derives the sRGB linearization from a + precomputed lookup table (one entry per 8-bit channel value) instead of + calling `pow` at runtime. `pow` is not required to be correctly rounded and + produced last-ULP differences between JS engines (V8 vs. others), the C math + library (PHP, Python, Rust), and Go's pure-Go implementation, so luminance + values, and in contrived cases contrast-based color ordering, could diverge + across languages and even across browsers. The table holds the values the + JavaScript reference produces today, so JavaScript output is unchanged; the + other libraries move by at most one ULP. The Go library additionally forces + intermediate rounding in the weighted sum, which the compiler could otherwise + fuse into FMA instructions on arm64. Rendered SVGs are unaffected. +- **Core (PHP):** `Avatar::toDataUri()` now percent-encodes exactly like + JavaScript's `encodeURIComponent`. Previously the PHP library used plain + `rawurlencode`, which additionally escapes `!*'()`, characters that occur in + every rendered SVG (e.g. `url(#…)` references and `translate(…)` transforms), + so the data URI diverged byte-wise from the JavaScript, Python, Rust, and Go + libraries. The decoded SVG was unaffected. +- **Core (JS):** The `initial` style variable now resolves to the full first + code point of the initials. Previously the JavaScript library emitted a lone + UTF-16 surrogate (ill-formed XML) when the initials started with a character + outside the Basic Multilingual Plane (e.g. an emoji). The PHP, Python, Rust, + and Go libraries already returned the full character; all libraries are now + byte-identical for such seeds. +- **Core (Rust):** `Avatar.to_json()` now records `size` before `title` in the + resolved-options snapshot, matching the JavaScript, PHP, and Python libraries. + The rendered SVG was unaffected; only consumers comparing or hashing the + serialized options JSON across languages were affected. +- **Core (Python):** `Avatar.to_json()` now serializes whole-number floats in + the resolved-options snapshot as integers (`1`, not `1.0`), matching the + JavaScript, Rust, and PHP libraries. Previously snapshot values such as + `scale`, `rotate`, `translateX`/`translateY`, `borderRadius`, color angles, + and per-component transforms were emitted as `1.0`/`0.0`, so the serialized + JSON diverged from the other ports. The rendered SVG was unaffected. The + values were already numerically equal, so only consumers comparing or hashing + the serialized options JSON across languages were affected. + +## [10.2.0-rc.1] - 2026-06-07 + +### Added + +- **Rust library:** A new Rust implementation (the `dicebear-core` crate) that + produces identical output to the JavaScript library when given the same styles + and options. + +### Fixed + +- **Core:** Initials now discard everything from the first `@` to the end of the + seed (e.g. an email domain). Previously the strip stopped at the first line + terminator (at a line feed in PHP and Python, and additionally at a carriage + return or `U+2028`/`U+2029` in JavaScript), so a seed with a line break after + the `@` kept the trailing text as a second word, and the libraries could even + diverge from each other. All language libraries now produce byte-identical + initials for such seeds. + +## [10.1.0] - 2026-06-06 + +### Changed + +- **Schema:** Bumped the bundled `@dicebear/schema` to `1.1.0` across the + JavaScript, PHP, and Python libraries. It adds an upper bound of `1000000` to + the canvas and component `width`/`height`, preventing the language ports' + number-to-string formatting from diverging at extreme values. Official styles + use ~100, so no real avatar is affected. +- **Styles:** Bumped `@dicebear/styles` to `10.1.0`. Lorelei's mouth is now + visible through `beard` variants (the overlaying mask was previously rendered + at `0` opacity), and all style definitions now reference + `@dicebear/schema@1.1.0`. + +## [10.1.0-rc.1] - 2026-06-02 + +### Added + +- **Python library:** A new Python implementation that produces identical output + to the JavaScript library when given the same styles and options. + +## [10.0.2] - 2026-06-02 + +### Fixed + +- **Core:** Numeric values in rendered SVGs are now consistently rounded to at + most 5 decimal places, so the JavaScript and PHP libraries produce + byte-identical output for every input. Previously, fractional or very + small/large values (e.g. a fractional `borderRadius` or `translateX`, + component transforms, or gradient stop offsets) could be stringified + differently between languages (scientific notation, differing precision). + Avatars built from whole-number options are unaffected. +- **Core (PHP):** `Prng::float` now rounds halves toward +Infinity (matching the + JavaScript reference's `Math.round`) instead of PHP's native `round()`, which + rounds halves away from zero. The two diverged for negative values landing + exactly on a `.5` boundary, so a PHP-rendered avatar could differ from the + JavaScript one by `0.0001` in a rotate/translate transform or color angle for + certain seeds. Output is now byte-identical across languages. +- **Core (PHP):** Initials are now derived correctly from seeds containing + multibyte letters such as `ü` or `ô`. The quote-stripping step was missing the + `/u` (Unicode) flag, so it removed raw UTF-8 bytes and corrupted those + letters: e.g. `über` and `côté` produced wrong or empty initials instead of + `ÜB` / `CÔ`. The PHP output now matches the JavaScript reference. +- **Core:** Range options (`scale`, `borderRadius`, `rotate`, + `translateX`/`translateY`, and per-color angle/fill-stops) given as a + single-element array `[n]` are now treated as the fixed value `n` (identical + to the scalar `n`), and an empty array `[]` falls back to the option's + default. Both forms are permitted by the schema. Previously the behaviour + diverged: the JavaScript library emitted `NaN` (e.g. `scale(NaN)`), while PHP + dropped `[n]` to the default. All three now agree. + +## [10.0.1] - 2026-05-29 + +### Fixed + +- **CLI:** `dicebear --version` and `dicebear --help` no longer fail by trying + to read a file named `--version`/`--help`. The definition path is now resolved + via the argument parser, so flags (and the values they consume) before the + path are handled correctly, e.g. `dicebear --json my-style.json` and + `dicebear --count 2 my-style.json`. + +## [10.0.0] - 2026-05-27 + +See the +[v10.0.0 release notes](https://github.com/dicebear/dicebear/releases/tag/v10.0.0). + +### Added + +- **6 new avatar styles:** Disco, Glyphs, Initial Face, Shape Grid, Stripes, and + Triangles. +- **PHP library:** A new PHP implementation that produces identical output to + the JavaScript library when given the same styles and options. +- **CLI support for custom styles:** Generate avatars from a JSON style + definition, e.g. `dicebear ./path/to/style.json --seed test --format svg`. +- **Weighted variants:** Assign weights to component variants to control how + frequently each appears. +- **Gradient support:** Colors can be defined as gradients, including an angle + parameter. +- **Integrated validation:** Built-in validation for avatar styles and options. +- **Redesigned playground:** Adjust options, upload custom styles, batch + download avatars, and view the number of possible combinations. +- **New tools:** WCAG Contrast Picker and Bundle Size Estimator. +- Reorganized and improved documentation, with better style docs and component + previews. + +### Changed + +- Each avatar style is now stored as a JSON definition file instead of + JavaScript code, separating licensing concerns from implementation. +- Styles are now distributed via `@dicebear/styles` as JSON definitions. +- The JavaScript API now uses `Style` and `Avatar` classes together with + definition imports. +- **BREAKING:** Component options are now suffixed with `Variant` (e.g. + `eyesVariant` instead of `eyes`). + +### Removed + +- **BREAKING:** Individual style packages (e.g. `@dicebear/initials`) have been + removed in favor of `@dicebear/styles`. + +[Unreleased]: https://github.com/dicebear/dicebear/compare/v10.3.0...HEAD +[10.3.0]: https://github.com/dicebear/dicebear/compare/v10.2.0...v10.3.0 +[10.2.0]: https://github.com/dicebear/dicebear/compare/v10.2.0-rc.1...v10.2.0 +[10.2.0-rc.1]: + https://github.com/dicebear/dicebear/compare/v10.1.0...v10.2.0-rc.1 +[10.1.0]: https://github.com/dicebear/dicebear/compare/v10.1.0-rc.1...v10.1.0 +[10.1.0-rc.1]: + https://github.com/dicebear/dicebear/compare/v10.0.2...v10.1.0-rc.1 +[10.0.2]: https://github.com/dicebear/dicebear/compare/v10.0.1...v10.0.2 +[10.0.1]: https://github.com/dicebear/dicebear/compare/v10.0.0...v10.0.1 +[10.0.0]: https://github.com/dicebear/dicebear/releases/tag/v10.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d152ee5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,362 @@ +# Contributing + +Thanks for your interest in contributing to DiceBear. + +This is the main monorepo: the JavaScript, PHP, Python, Rust, Go, and Dart core +libraries, the CLI, the docs site, and the editor all live here. Repositories +covering the JSON Schema, the avatar style definitions, the HTTP API, and the +Figma exporter are separate and each have their own `CONTRIBUTING.md`: + +- [`dicebear/schema`](https://github.com/dicebear/schema/blob/main/CONTRIBUTING.md): + JSON Schema for definitions and options +- [`dicebear/styles`](https://github.com/dicebear/styles/blob/main/CONTRIBUTING.md): + Official avatar style definitions +- [`dicebear/api`](https://github.com/dicebear/api/blob/main/CONTRIBUTING.md): + Self-hostable HTTP API +- [`dicebear/exporter-plugin-for-figma`](https://github.com/dicebear/exporter-plugin-for-figma/blob/main/CONTRIBUTING.md): + Figma plugin + +If your contribution belongs to one of those repos, read its file first. The +instructions below only cover this monorepo. + +## Before you start + +- Bug fixes, small improvements, new tests: open a pull request against the + branch that matches the target major (for DiceBear 10 that's `10.x`; the + current stable line lives on `9.x`). +- New avatar styles: contribute them to + [`dicebear/styles`](https://github.com/dicebear/styles), not here. The + walkthrough is in + [Create an avatar style with Figma](https://www.dicebear.com/guides/create-an-avatar-style-with-figma/) + or + [Create an avatar style from scratch](https://www.dicebear.com/guides/create-an-avatar-style-from-scratch/). +- Larger changes (new public API, rendering behavior, breaking changes): open an + issue first so we can agree on scope before you spend time on it. +- Security issues go privately to ; never file a public + issue for a vulnerability. +- Contributors follow the + [DiceBear Code of Conduct](https://github.com/dicebear/.github/blob/main/CODE_OF_CONDUCT.md). + +## Requirements + +- [Node.js](https://nodejs.org/) 20 or newer (CI runs on 20, 22, 24, 25) +- npm 11 (this repo pins `packageManager` in `package.json`; use + [Corepack](https://nodejs.org/api/corepack.html) if you don't already) +- For PHP work: PHP 8.2+ and Composer, plus `vendor/bin/phpunit` via + `composer install` inside `src/php/core/` +- For Python work: Python 3.10+ (CI runs on 3.10 to 3.14); install the package + in a virtualenv with `pip install -e ".[dev]"` inside `src/python/core/` +- For Rust work: Rust 1.80+ with the `clippy` and `rustfmt` components; build + and test with `cargo test`, `cargo clippy`, and `cargo fmt` inside + `src/rust/core/` +- For Go work: Go 1.23+ (CI runs on 1.23 to 1.25); test with `go test ./...` and + check with `gofmt -l .` and `go vet ./...` inside `src/go/core/` +- For Dart work: Dart SDK 3.4+ (CI runs on 3.4 and stable); test with + `dart test` and check with `dart format --output=none --set-exit-if-changed .` + and `dart analyze --fatal-infos` inside `src/dart/core/` + +## Local setup + +```sh +git clone git@github.com:dicebear/dicebear.git +cd dicebear +npm install +``` + +The monorepo uses npm workspaces (`src/js/*` and `apps/*`) driven by +[Turborepo](https://turborepo.com/). A single install at the root is enough; do +not `npm install` inside individual packages. + +## Common scripts + +Run these from the repo root: + +| Script | What it does | +| ---------------------- | --------------------------------------------------- | +| `npm run build` | Builds every workspace via Turbo | +| `npm test` | Runs every workspace's test target | +| `npm run test:scripts` | Runs the repo-level scripts in `tests/**/*.test.js` | +| `npm run lint` | Runs ESLint across the repo with caching | +| `npm run lint:fix` | Same, with `--fix` | +| `npm run prettier` | Formats `src/` and `apps/` with Prettier | + +For faster loops, scope to a single workspace: + +```sh +npm run build --workspace @dicebear/core +npm run test --workspace @dicebear/core +``` + +## Repository layout + +``` +src/ +├── js/ # TypeScript packages published to npm +│ ├── core/ # Rendering engine (@dicebear/core) +│ ├── cli/ # Command-line interface +│ └── converter/ # SVG → raster converter +├── php/ # PHP port (Composer package `dicebear/core`) +├── python/ # Python port (PyPI package `dicebear-core`) +├── rust/ # Rust port (crates.io crate `dicebear-core`) +├── go/ # Go port (module `github.com/dicebear/dicebear-go/v10`) +└── dart/ # Dart port (pub.dev package `dicebear_core`) +apps/ +├── docs/ # VitePress documentation site (dicebear.com), including the Playground +└── editor/ # The in-browser editor (editor.dicebear.com) +tests/ +└── fixtures/parity/ # Cross-language parity fixtures (see below) +scripts/ +└── version.mjs # Bumps versions across the workspace and tags +``` + +## Working on a package + +### TypeScript packages (`src/js/*`) + +```sh +npm run build --workspace +npm run test --workspace +``` + +If you are working on the CLI, call the compiled script directly once you've +built it: + +```sh +node src/js/cli/bin/index.js +``` + +### PHP core (`src/php/core/`) + +```sh +cd src/php/core +composer install +vendor/bin/phpunit +``` + +### Python core (`src/python/core/`) + +```sh +cd src/python/core +pip install -e ".[dev]" # in a virtualenv +ruff check . +ruff format --check . +mypy src +pytest +``` + +The Python core reads the two draft-07 schemas from the `dicebear-schema` +package (the Python counterpart of `@dicebear/schema` / `dicebear/schema`), +which `pip install -e ".[dev]"` pulls in as a runtime dependency. Nothing is +vendored. + +### Rust core (`src/rust/core/`) + +```sh +cd src/rust/core +cargo test +cargo clippy --all-targets --all-features -- -D warnings +cargo fmt --check +``` + +The Rust core reads the two draft-07 schemas from the `dicebear-schema` crate +(the Rust counterpart of `@dicebear/schema` / `dicebear/schema`) as a runtime +dependency. Nothing is vendored. + +### Go core (`src/go/core/`) + +```sh +cd src/go/core +go test ./... +go vet ./... +gofmt -l . # prints nothing when formatting is correct +``` + +The Go core reads the two draft-07 schemas from the `github.com/dicebear/schema` +module (the Go counterpart of `@dicebear/schema` / `dicebear/schema`) as a +dependency. Nothing is vendored. Style definitions come from +`github.com/dicebear/styles/v10`. The module path carries the major version +(`/v10`), so a major bump changes the path by hand; `scripts/version.mjs` only +creates the Git tag the module proxy reads. + +### Dart core (`src/dart/core/`) + +```sh +cd src/dart/core +dart pub get +dart test +dart analyze --fatal-infos +dart format --output=none --set-exit-if-changed . +``` + +The Dart core reads the two draft-07 schemas from the `dicebear_schema` package +(the Dart counterpart of `@dicebear/schema` / `dicebear/schema`) as a runtime +dependency, validated with `package:json_schema`. Nothing is vendored. + +`src/dart/core/CHANGELOG.md` is a git-ignored build artifact: pub.dev expects a +changelog inside the package directory, so the CI workflows copy the repository +root `CHANGELOG.md` there before `dart pub publish [--dry-run]`. This follows +the same pattern as the generated `LICENSE` copies in the styles and schema +repositories. Maintain the root changelog only. + +### Cross-language parity + +Every port must produce output **byte-identical** to the reference JavaScript +core (`@dicebear/core`) for the same inputs. A shared fixture suite in +`tests/fixtures/parity/` (generated from the JS reference) enforces this, and +each side consumes it: + +- JS side: `src/js/core/tests/Parity.test.js`, run via + `npm run test --workspace @dicebear/core`. +- PHP side: `tests/ParityTest.php`, run via `vendor/bin/phpunit` in + `src/php/core/`. +- Python side: `tests/test_parity.py`, run via `pytest` in `src/python/core/`. +- Rust side: the module unit tests plus `tests/avatars.rs`, run via `cargo test` + in `src/rust/core/`. +- Go side: the in-package tests (`parity_test.go`, `avatars_test.go`), run via + `go test ./...` in `src/go/core/`. +- Dart side: the tests under `test/parity/`, run via `dart test` in + `src/dart/core/`. + +The fixtures cover `Fnv1a` (hash + hex), `Mulberry32` (chained sequences), every +`Prng` method, number-to-string formatting (`numbers.json`, the `formatNumber` +contract: every emitted number rounded to at most 5 decimal places, which every +port must reproduce identically), initials extraction, the `Color` helpers +(including bit-exact luminance doubles), accept/reject validation outcomes plus +circular color-reference chains, the `OptionsDescriptor` field map, and full +`Avatar.toString()` (plus selected `toDataUri()`) output for the `initials`, +`thumbs`, `glass`, `notionists`, and `shape-grid` styles. That last bucket +covers seed, size, transforms, gradients, `title` escaping, and +component-variant overrides. + +Float determinism is part of the parity contract: `pow` is not correctly rounded +(JS engines, libm, and Go all differ in the last bit), so the sRGB linearization +ships as a precomputed 256-entry table in every port, and languages that fuse +multiply-add into FMA instructions (e.g. Go on arm64) must force intermediate +rounding. The details live in the +[Implement DiceBear Core](https://www.dicebear.com/specification/implement-dicebear-core/) +spec. + +If your change affects rendering or the PRNG in `@dicebear/core`, regenerate the +fixtures from the JS reference and commit the diff: + +```sh +npm run fixtures:parity +``` + +The PHP, Python, Rust, Go, and Dart suites will then fail loudly until those +sides are brought back in sync. That is the intended signal. If you only intend +to touch one language, expect to update both before your PR can be merged. + +When porting DiceBear to another language, run these fixtures against your +implementation to prove it conforms. See +[Implement DiceBear Core](https://www.dicebear.com/specification/implement-dicebear-core/) +for the full spec. + +## Documentation changes (`apps/docs/`) + +The docs site is a VitePress app under `apps/docs/`. + +```sh +npm run dev --workspace @dicebear/docs # live reload on localhost +npm run build --workspace @dicebear/docs # production build check +``` + +For larger editorial changes, open a draft PR early so reviewers can follow +along. + +## Editor changes (`apps/editor/`) + +The editor is the standalone app served at +[editor.dicebear.com](https://editor.dicebear.com). + +```sh +npm run dev --workspace @dicebear/editor +npm run build --workspace @dicebear/editor +``` + +## Code style + +- ESLint and Prettier decide what counts as correctly formatted code; run + `npm run lint` and `npm run prettier` before you open a PR. +- TypeScript is `strict`. Prefer narrow types to `any` / `unknown` casts. +- PHP code follows PSR-12; `vendor/bin/phpunit` and Composer's built-in scripts + catch the rest. +- Python code is formatted and linted with [Ruff](https://docs.astral.sh/ruff/) + and type-checked with `mypy` in strict mode; run `ruff check .`, + `ruff format .`, and `mypy src` in `src/python/core/` before you open a PR. +- Rust code is formatted with `rustfmt` and linted with Clippy (warnings + denied); run `cargo fmt` and + `cargo clippy --all-targets --all-features -- -D warnings` in `src/rust/core/` + before you open a PR. +- Go code is formatted with `gofmt` and vetted with `go vet`; run `gofmt -w .` + and `go vet ./...` in `src/go/core/` before you open a PR. +- Dart code is formatted with `dart format` and analyzed with + `dart analyze --fatal-infos` (lints from `analysis_options.yaml`); run both in + `src/dart/core/` before you open a PR. + +## Releasing (maintainers only) + +> Only maintainers with write access can release new versions. This section is +> documented here for completeness. + +Releases are triggered by Git tags. The version script updates every package in +the workspace, creates a commit, and creates the tag: + +```sh +node scripts/version.mjs +``` + +The version must be a valid [semver](https://semver.org/) value (e.g. `10.1.0` +or `10.2.0-alpha.1`). The script will: + +1. Update `version` in every `package.json` across the workspace +2. Update internal workspace dependency references +3. Update `version` in `src/python/core/pyproject.toml`, + `src/rust/core/Cargo.toml`, and `src/dart/core/pubspec.yaml` (the Python, + Rust, and Dart cores are not npm workspaces, so they are bumped explicitly to + stay in lockstep) +4. Sync `package-lock.json` +5. Create a Git commit and tag (e.g. `v10.1.0`) + +Push the commit and the tag: + +```sh +git push && git push --tags +``` + +The tag triggers the +[Publish](https://github.com/dicebear/dicebear/actions/workflows/publish.yml) +workflow, which: + +1. Runs the test suite on Node 20, 22, 24, and 25 +2. Builds every package +3. Picks the npm dist-tag: + - Tags containing `alpha`, `beta`, or `rc` go out as `next` + - Other tags on the default branch go out as `latest` + - Other tags on any other branch go out as `v-lts` (so that + backporting a patch to `9.x` after `10.x` has shipped does not overwrite + `latest`) +4. Publishes all changed packages to npm via `scripts/publish.mjs` +5. Builds `src/python/core` and publishes `dicebear-core` to PyPI via trusted + publishing (the `publish-python` job): no token and no separate repository, + the same way the npm packages go out +6. Publishes the Rust core `dicebear-core` to crates.io via trusted publishing + (the `publish-rust` job): likewise no token; `cargo publish` builds and + uploads `src/rust/core` in one step +7. Publishes the Dart core `dicebear_core` to pub.dev via automated publishing + (the `publish-pub` job): likewise no token; pub.dev verifies the `v` + tag against the pubspec version and publishes `src/dart/core` straight from + the monorepo + +The PHP port is the exception: Composer/Packagist consumes one Git repository +per package rather than a monorepo subdirectory, so `split-php-core.yml` mirrors +`src/php/core` (tags included) to the standalone +[`dicebear/dicebear-php`](https://github.com/dicebear/dicebear-php) repository, +and Packagist publishes `dicebear/core` from that mirror. All five ports ride +the same version the monorepo tagged. + +## Licensing + +By opening a pull request you agree that your contribution is released under the +repository's [MIT license](./LICENSE). Avatar style artwork in `dicebear/styles` +may carry other licenses; see that repo's `LICENSE.md` for details. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..34df6f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Florian Körner + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7f769f --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +

DiceBear Avatar Library

+ +DiceBear is an open source avatar library. It turns any seed string (a username +or an email address, for example) into an SVG avatar in one of 35+ styles +designed by various artists. The same seed always produces the same avatar, so +you store a string instead of an image and never ask users to upload a profile +picture. + +Avatars are customizable through style options: colors, backgrounds, rotation, +individual features like hair or glasses. + +[Playground](https://www.dicebear.com/playground) | +[Documentation](https://www.dicebear.com/introduction) | +[Editor](https://editor.dicebear.com) + +## One library, six languages + +DiceBear 10 ships as native libraries for JavaScript, PHP, Python, Rust, Go, and +Dart. Every port passes a shared test suite that requires byte-identical SVG +output to the JavaScript reference. Generate an avatar in the browser, +regenerate it later in a Go or PHP backend, and you get the same bytes. + +| Language | Package | Install | +| ----------------------- | ----------------------------------------------------------------------- | -------------------------------------------- | +| JavaScript / TypeScript | [`@dicebear/core`](https://www.npmjs.com/package/@dicebear/core) | `npm install @dicebear/core` | +| PHP | [`dicebear/core`](https://packagist.org/packages/dicebear/core) | `composer require dicebear/core` | +| Python | [`dicebear-core`](https://pypi.org/project/dicebear-core/) | `pip install dicebear-core` | +| Rust | [`dicebear-core`](https://crates.io/crates/dicebear-core) | `cargo add dicebear-core` | +| Go | [`dicebear-go`](https://pkg.go.dev/github.com/dicebear/dicebear-go/v10) | `go get github.com/dicebear/dicebear-go/v10` | +| Dart | [`dicebear_core`](https://pub.dev/packages/dicebear_core) | `dart pub add dicebear_core` | + +In JavaScript it looks like this; the +[documentation](https://www.dicebear.com/introduction) has the equivalent for +each language: + +```js +import { Avatar } from '@dicebear/core'; +import definition from '@dicebear/styles/lorelei.json' with { type: 'json' }; + +const avatar = new Avatar(definition, { + seed: 'John Doe', + size: 128, +}); + +avatar.toString(); // SVG string +avatar.toDataUri(); // data:image/svg+xml;charset=utf-8,... +``` + +The 35+ avatar styles are plain JSON definitions from the +[`dicebear/styles`](https://github.com/dicebear/styles) repository, available as +a package for each language. You can also +[create your own style](https://www.dicebear.com/guides/create-an-avatar-style-with-figma/), +with Figma or from scratch. + +## Without writing code + +- The [HTTP API](https://www.dicebear.com/how-to-use/http-api/) returns avatars + from a plain URL, free and without an account: + `https://api.dicebear.com/10.x/lorelei/svg?seed=Felix`. For full control and + privacy you can + [host it yourself](https://www.dicebear.com/guides/host-the-http-api-yourself/) + with a single Docker container. +- The [CLI](https://www.dicebear.com/how-to-use/cli/) generates avatar files in + bulk: `npx dicebear lorelei --count 10`. +- The [editor](https://editor.dicebear.com) lets you assemble a single avatar by + hand and export it. + +## This repository + +This monorepo contains the six core libraries, the CLI, the SVG-to-raster +converter, the documentation site ([dicebear.com](https://www.dicebear.com)), +and the editor. Related projects live in their own repositories: + +- [`dicebear/styles`](https://github.com/dicebear/styles): the official avatar + style definitions +- [`dicebear/schema`](https://github.com/dicebear/schema): the JSON Schema + behind definitions and options +- [`dicebear/api`](https://github.com/dicebear/api): the self-hostable HTTP API +- [`dicebear/exporter-plugin-for-figma`](https://github.com/dicebear/exporter-plugin-for-figma): + the Figma plugin for style authors + +Contributions are welcome; [CONTRIBUTING.md](./CONTRIBUTING.md) explains the +setup and where each kind of change belongs. + +## License + +The code is [MIT licensed](./LICENSE), including commercial use. The avatar +styles are the work of their respective artists and carry their own licenses; +the [license overview](https://www.dicebear.com/licenses/) lists them all, and +many only ask for attribution. + +## Sponsors + +Advertisement: Many thanks to our sponsors who provide us with free or +discounted products. + + + + + + bunny.net + + diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..562c42b --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`dicebear/dicebear` +- 原始仓库:https://github.com/dicebear/dicebear +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/apps/docs/.env.example b/apps/docs/.env.example new file mode 100644 index 0000000..b9101cb --- /dev/null +++ b/apps/docs/.env.example @@ -0,0 +1,4 @@ +# Legal links shown in the footer. +VITE_PRIVACY_POLICY_URL= +VITE_COOKIE_POLICY_URL= +VITE_SITE_NOTICE_URL= diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 0000000..bab9e83 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1,9 @@ +# Cache and log files +.DS_STORE + +# Dependencies +node_modules + +# Vitepress +.vitepress/cache +.vitepress/dist diff --git a/apps/docs/.vitepress/config.ts b/apps/docs/.vitepress/config.ts new file mode 100644 index 0000000..cc29c47 --- /dev/null +++ b/apps/docs/.vitepress/config.ts @@ -0,0 +1,263 @@ +import * as path from 'node:path'; +import { defineConfig, type DefaultTheme, type HeadConfig } from 'vitepress'; +import { ThemeOptions } from '@theme/types'; + +import sidebarDocs from './config/sidebarDocs'; +import sidebarStyles from './config/sidebarStyles'; +import sidebarTools from './config/sidebarTools'; +import avatarStyles from './config/avatarStyles'; +import avatarUniqueCounts from './config/avatarUniqueCounts'; +import avatarStyleSizes from './config/avatarStyleSizes'; +import { formatStars } from './theme/utils/format'; + +async function fetchGitHubStars( + repos: string[], +): Promise> { + const result: Record = {}; + const TIMEOUT_MS = 5000; + + await Promise.all( + repos.map(async (repo) => { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), TIMEOUT_MS); + try { + const res = await fetch(`https://api.github.com/repos/${repo}`, { + signal: controller.signal, + }); + if (res.ok) { + const data = await res.json(); + result[repo] = formatStars(data.stargazers_count); + } + } catch (err) { + console.warn( + `[github-stars] failed for ${repo}:`, + err instanceof Error ? err.message : err, + ); + } finally { + clearTimeout(timer); + } + }), + ); + + return result; +} + +const githubStars = await fetchGitHubStars([ + 'dicebear/dicebear', + 'nusu/avvvatars', + 'dmester/jdenticon', + 'multiavatar/Multiavatar', + 'boringdesigners/boring-avatars', +]); + +const isProduction = process.env.NODE_ENV === 'production'; + +const thirdPartyScripts: HeadConfig[] = isProduction + ? [ + [ + 'script', + { + defer: '', + src: 'https://u.dicebear.com/script.js', + 'data-website-id': '75d27df5-3441-4530-8f29-70d04ae9085e', + }, + ], + ] + : []; + +export default defineConfig({ + title: 'DiceBear', + description: + 'DiceBear is a free, open source avatar library and avatar API with 35+ avatar styles. Generate profile pictures and user placeholder images for any project.', + head: [ + // Most pages load avatars from the HTTP API (seed demo, style showcase, + // playground). Warming up the connection hides the DNS/TLS latency on + // mobile. The avatars are plain requests (no CORS), so the hint + // must not carry a crossorigin attribute to match the connection. + ['link', { rel: 'preconnect', href: 'https://api.dicebear.com' }], + [ + 'link', + { + rel: 'icon', + type: 'image/png', + href: '/favicon-96x96.png', + sizes: '96x96', + }, + ], + ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], + ['link', { rel: 'shortcut icon', href: '/favicon.ico' }], + [ + 'link', + { + rel: 'apple-touch-icon', + sizes: '180x180', + href: '/apple-touch-icon.png', + }, + ], + ['meta', { name: 'apple-mobile-web-app-title', content: 'DiceBear' }], + ['link', { rel: 'manifest', href: '/site.webmanifest' }], + ['meta', { property: 'og:site_name', content: 'DiceBear' }], + ['meta', { property: 'og:type', content: 'website' }], + ['meta', { name: 'twitter:card', content: 'summary' }], + [ + 'script', + { type: 'application/ld+json' }, + JSON.stringify({ + '@context': 'https://schema.org', + '@type': 'WebSite', + name: 'DiceBear', + url: 'https://www.dicebear.com', + description: + 'DiceBear is a free, open source avatar library and Avatar API. Generate unique, deterministic SVG avatars and profile pictures with 35+ styles — privacy-focused and self-hostable.', + }), + ], + [ + 'script', + { type: 'application/ld+json' }, + JSON.stringify({ + '@context': 'https://schema.org', + '@type': 'SoftwareApplication', + name: 'DiceBear', + applicationCategory: 'DeveloperApplication', + operatingSystem: 'Any', + url: 'https://www.dicebear.com', + description: + 'Privacy-focused, open source SVG avatar library with 35+ styles. Free Avatar API, JavaScript library, PHP library, Python library, Rust library, Go library, Dart library, and CLI for generating deterministic profile pictures and user placeholder images.', + offers: { + '@type': 'Offer', + price: '0', + priceCurrency: 'USD', + }, + }), + ], + ...thirdPartyScripts, + ], + srcDir: path.join(__dirname, '..', 'pages'), + transformHead: (ctx) => { + const result: HeadConfig[] = []; + + // The theme renders text with the self-hosted variable Inter (see + // theme/index.ts); VitePress's built-in Inter is disabled via + // theme-without-fonts. Preload the latin subset so the first paint + // doesn't wait a full round trip after the CSS arrives. Font preloads + // always need `crossorigin`, even for same-origin requests. + const interFont = ctx.assets.find((asset) => + /inter-latin-wght-normal\.[\w-]+\.woff2$/.test(asset), + ); + + if (interFont) { + result.push([ + 'link', + { + rel: 'preload', + href: interFont, + as: 'font', + type: 'font/woff2', + crossorigin: '', + }, + ]); + } + + if (ctx.pageData.relativePath) { + const canonicalPath = ctx.pageData.relativePath + .replace('index.md', '') + .replace(/\.md$/, ''); + + const canonicalUrl = `https://www.dicebear.com/${canonicalPath}`; + + result.push(['link', { rel: 'canonical', href: canonicalUrl }]); + + if (canonicalPath.startsWith('legal/legal-notice')) { + result.push(['meta', { name: 'robots', content: 'noindex, nofollow' }]); + } + + const pageTitle = + ctx.pageData.frontmatter.title || ctx.pageData.title || 'DiceBear'; + const pageDescription = + ctx.pageData.frontmatter.description || + ctx.pageData.description || + 'DiceBear is a free, open source avatar library and Avatar API with 35+ avatar styles.'; + + result.push( + ['meta', { property: 'og:title', content: pageTitle }], + ['meta', { property: 'og:description', content: pageDescription }], + ['meta', { property: 'og:url', content: canonicalUrl }], + ['meta', { name: 'twitter:title', content: pageTitle }], + ['meta', { name: 'twitter:description', content: pageDescription }], + ); + } + + return result; + }, + vite: { + ssr: { + noExternal: ['vue-countup-v3', 'vue-chartjs', 'globe.gl', 'three'], + }, + resolve: { + alias: { + '@playground': path.resolve(__dirname, 'theme/components/playground'), + '@theme': path.resolve(__dirname, 'theme'), + './components/VPLocalNav.vue': path.resolve( + __dirname, + 'theme/components/layout/LayoutVPLocalNav.vue', + ), + }, + }, + }, + themeConfig: { + avatarStyles, + avatarUniqueCounts, + avatarStyleSizes, + githubStars, + siteTitle: '', + // Intrinsic SVG dimensions as explicit attributes so the browser can + // reserve the aspect ratio before the file loads (the displayed size + // comes from the theme's CSS height). VPImage spreads nested src + // objects onto the at runtime, but ThemeableImage only types the + // light/dark form with plain strings, hence the double cast. + logo: { + dark: { src: '/logo-dark.svg', width: 183, height: 32 }, + light: { src: '/logo.svg', width: 183, height: 32 }, + } as unknown as DefaultTheme.ThemeableImage, + externalLinkIcon: true, + search: { + provider: 'local', + }, + nav: [ + { text: 'Playground', link: '/playground/', activeMatch: '^/playground' }, + { + text: 'Docs', + link: '/introduction/', + activeMatch: '^/(introduction|how-to-use|guides|specification)', + }, + { + text: 'Styles', + link: '/styles/', + activeMatch: '^/styles', + }, + { text: 'Editor', link: 'https://editor.dicebear.com' }, + { + text: '10.x', + items: [{ text: '9.x', link: 'https://v9.dicebear.com' }], + }, + ], + outline: [2, 2], + socialLinks: [], + editLink: { + pattern: + 'https://github.com/dicebear/dicebear/edit/10.x/apps/docs/pages/:path', + }, + sidebar: { + '/introduction/': sidebarDocs, + '/styles/': sidebarStyles, + '/how-to-use/': sidebarDocs, + '/guides/': sidebarDocs, + '/specification/': sidebarDocs, + '/tools/': sidebarTools, + }, + }, + sitemap: { + hostname: 'https://www.dicebear.com', + }, + markdown: {}, +}); diff --git a/apps/docs/.vitepress/config/avatarStyleSizes.ts b/apps/docs/.vitepress/config/avatarStyleSizes.ts new file mode 100644 index 0000000..7981934 --- /dev/null +++ b/apps/docs/.vitepress/config/avatarStyleSizes.ts @@ -0,0 +1,67 @@ +import { createRequire } from 'node:module'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { gzipSync } from 'node:zlib'; + +import type { AvatarStyleSizeBundle } from '@theme/types'; + +const require = createRequire(import.meta.url); + +function sizeFor(file: string): { raw: number; gzip: number } { + const buf = fs.readFileSync(file); + return { raw: buf.byteLength, gzip: gzipSync(buf).byteLength }; +} + +function walkJsFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + out.push(...walkJsFiles(full)); + } else if (entry.isFile() && entry.name.endsWith('.js')) { + out.push(full); + } + } + return out; +} + +const stylesDir = path.dirname( + require.resolve('@dicebear/styles/initials.json'), +); +const coreLibDir = path.dirname(require.resolve('@dicebear/core')); +// @dicebear/converter `main` points to lib/node/index.js; step up to lib/ +const converterLibDir = path.dirname( + path.dirname(require.resolve('@dicebear/converter')), +); + +const styles: Record = {}; +for (const file of fs.readdirSync(stylesDir)) { + if (!file.endsWith('.min.json')) continue; + const name = file.replace('.min.json', ''); + styles[name] = sizeFor(path.join(stylesDir, file)); +} + +function bundleSize( + dir: string, + exclude: (file: string) => boolean = () => false, +) { + let raw = 0; + let gzip = 0; + for (const file of walkJsFiles(dir)) { + if (exclude(file)) continue; + const s = sizeFor(file); + raw += s.raw; + gzip += s.gzip; + } + return { raw, gzip }; +} + +const avatarStyleSizes: AvatarStyleSizeBundle = { + core: bundleSize(coreLibDir), + converter: bundleSize(converterLibDir, (file) => + file.includes(`${path.sep}node${path.sep}`), + ), + styles, +}; + +export default avatarStyleSizes; diff --git a/apps/docs/.vitepress/config/avatarStyles.ts b/apps/docs/.vitepress/config/avatarStyles.ts new file mode 100644 index 0000000..3dd6599 --- /dev/null +++ b/apps/docs/.vitepress/config/avatarStyles.ts @@ -0,0 +1,43 @@ +import { Style } from '@dicebear/core'; +import { AvatarStyles } from '@theme/types'; +import { createRequire } from 'node:module'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; + +const require = createRequire(import.meta.url); + +const definitionsDir = path.dirname( + require.resolve('@dicebear/styles/initials.json'), +); + +const avatarStyles: AvatarStyles = {}; + +for (const file of fs.readdirSync(definitionsDir)) { + if (!file.endsWith('.min.json')) { + continue; + } + + const name = file.replace('.min.json', ''); + const definition = JSON.parse( + fs.readFileSync(path.join(definitionsDir, file), 'utf-8'), + ); + const style = new Style(definition); + const meta = style.meta(); + + avatarStyles[name] = { + definitionUrl: style.id(), + meta: { + title: meta.source().name(), + creator: meta.creator().name(), + homepage: meta.creator().url(), + source: meta.source().url(), + license: { + name: meta.license().name(), + url: meta.license().url(), + text: meta.license().text(), + }, + }, + }; +} + +export default avatarStyles; diff --git a/apps/docs/.vitepress/config/avatarUniqueCounts.ts b/apps/docs/.vitepress/config/avatarUniqueCounts.ts new file mode 100644 index 0000000..69ea28e --- /dev/null +++ b/apps/docs/.vitepress/config/avatarUniqueCounts.ts @@ -0,0 +1,30 @@ +import type { StyleDefinition } from '@dicebear/core'; +import { createRequire } from 'node:module'; +import * as path from 'node:path'; +import * as fs from 'node:fs'; + +import type { AvatarUniqueCount } from '@theme/types'; +import { computeCount } from '../theme/utils/avatar/combinationCount'; + +const require = createRequire(import.meta.url); + +const definitionsDir = path.dirname( + require.resolve('@dicebear/styles/initials.json'), +); + +const avatarUniqueCounts: Record = {}; + +for (const file of fs.readdirSync(definitionsDir)) { + if (!file.endsWith('.min.json')) { + continue; + } + + const name = file.replace('.min.json', ''); + const definition: StyleDefinition = JSON.parse( + fs.readFileSync(path.join(definitionsDir, file), 'utf-8'), + ); + + avatarUniqueCounts[name] = computeCount(definition); +} + +export default avatarUniqueCounts; diff --git a/apps/docs/.vitepress/config/sidebarDocs.ts b/apps/docs/.vitepress/config/sidebarDocs.ts new file mode 100644 index 0000000..5a4071b --- /dev/null +++ b/apps/docs/.vitepress/config/sidebarDocs.ts @@ -0,0 +1,182 @@ +import { DefaultTheme } from 'vitepress'; + +// NOTE: VitePress renders sidebar item `text` with `v-html`, so inline markup is +// allowed — we use it to attach small status badges (styled via +// `.vp-sidebar-badge` in theme/styles/main.scss). Groups are intentionally NOT +// collapsible (no `collapsed` key), so every section stays expanded. +const sidebar: DefaultTheme.SidebarItem[] = [ + { + text: 'Introduction', + items: [ + { text: 'What is DiceBear?', link: '/introduction/' }, + { + text: 'DiceBear vs. Alternatives', + link: '/guides/avatar-library-comparison/', + }, + ], + }, + { + text: 'How to use', + items: [ + { + text: 'JS Library', + link: '/how-to-use/js-library/', + items: [ + { text: 'Core', link: '/how-to-use/js-library/' }, + { text: 'Converter', link: '/how-to-use/js-library/converter/' }, + ], + }, + { + text: 'PHP Library New', + link: '/how-to-use/php-library/', + }, + { + text: 'Python Library New', + link: '/how-to-use/python-library/', + }, + { + text: 'Rust Library New', + link: '/how-to-use/rust-library/', + }, + { + text: 'Go Library New', + link: '/how-to-use/go-library/', + }, + { + text: 'Dart Library New', + link: '/how-to-use/dart-library/', + }, + { text: 'HTTP-API', link: '/how-to-use/http-api/' }, + { text: 'CLI', link: '/how-to-use/cli/' }, + ], + }, + { + text: 'Frameworks', + items: [ + { + text: 'Angular', + link: '/guides/use-the-library-with-angular/', + }, + { + text: 'Flutter', + link: '/guides/use-the-library-with-flutter/', + }, + { + text: 'Next.js', + link: '/guides/use-the-library-with-next-js/', + }, + { + text: 'Nuxt', + link: '/guides/use-the-library-with-nuxt/', + }, + { + text: 'React', + link: '/guides/use-the-library-with-react/', + }, + { + text: 'React Native', + link: '/guides/use-the-library-with-react-native/', + }, + { + text: 'Svelte', + link: '/guides/use-the-library-with-svelte/', + }, + { + text: 'Vue', + link: '/guides/use-the-library-with-vue/', + }, + ], + }, + { + text: 'Custom Styles', + items: [ + { + text: 'With Figma', + link: '/guides/create-an-avatar-style-with-figma/', + }, + { + text: 'From Scratch', + link: '/guides/create-an-avatar-style-from-scratch/', + }, + ], + }, + { + text: 'Specification', + items: [ + { + text: 'Definition Schema', + link: '/specification/definition-schema/', + }, + { + text: 'Implement DiceBear Core', + link: '/specification/implement-dicebear-core/', + }, + ], + }, + { + text: 'Concepts', + items: [ + { + text: 'Gender', + link: '/guides/how-do-i-set-a-gender/', + }, + ], + }, + { + text: 'Use Cases', + items: [ + { + text: 'Avatar Placeholder', + link: '/guides/use-as-avatar-placeholder/', + }, + { + text: 'Gravatar Default Image', + link: '/guides/use-the-http-api-as-gravatar-default-image/', + }, + { + text: 'Self-host the HTTP-API', + link: '/guides/host-the-http-api-yourself/', + }, + ], + }, + { + text: 'Advanced', + items: [ + { + text: 'Access Style Options', + link: '/guides/access-all-available-options/', + }, + { + text: 'Load All Avatar Styles', + link: '/guides/load-all-avatar-styles/', + }, + { + text: 'Unique Avatar Count', + link: '/guides/how-many-unique-avatars/', + }, + ], + }, + { + text: 'Contributing', + items: [ + { + text: 'Documentation', + link: '/guides/contribute-to-the-documentation/', + }, + { + text: 'Editor', + link: '/guides/contribute-to-the-editor/', + }, + { + text: 'API', + link: '/guides/contribute-to-the-api/', + }, + { + text: 'Library', + link: '/guides/contribute-to-the-library/', + }, + ], + }, +]; + +export default sidebar; diff --git a/apps/docs/.vitepress/config/sidebarPlayground.ts b/apps/docs/.vitepress/config/sidebarPlayground.ts new file mode 100644 index 0000000..d5082aa --- /dev/null +++ b/apps/docs/.vitepress/config/sidebarPlayground.ts @@ -0,0 +1,10 @@ +import { DefaultTheme } from 'vitepress'; + +const sidebar: DefaultTheme.SidebarItem[] = [ + { + text: 'Playground', + items: [{ text: 'Playground', link: '/playground/' }], + }, +]; + +export default sidebar; diff --git a/apps/docs/.vitepress/config/sidebarStyles.ts b/apps/docs/.vitepress/config/sidebarStyles.ts new file mode 100644 index 0000000..753af4a --- /dev/null +++ b/apps/docs/.vitepress/config/sidebarStyles.ts @@ -0,0 +1,36 @@ +import { capitalCase } from 'change-case'; +import { DefaultTheme } from 'vitepress'; +import avatarStyles from './avatarStyles'; + +// Avatar styles to flag with a "New" badge in the sidebar. These are the styles +// added in the most recent @dicebear/styles release that introduced new styles +// (v10.0.0). Update this set when new styles ship. The badge markup is rendered +// via v-html (see `.vp-sidebar-badge` in theme/styles/main.scss). +const NEW_STYLES = new Set([ + 'disco', + 'glyphs', + 'initial-face', + 'shape-grid', + 'stripes', + 'triangles', +]); + +const sidebar: DefaultTheme.SidebarItem[] = [ + { + text: 'Styles', + items: Object.keys(avatarStyles) + .sort((a, b) => a.localeCompare(b)) + .map((styleName) => { + const label = capitalCase(styleName); + + return { + text: NEW_STYLES.has(styleName) + ? `${label} New` + : label, + link: `/styles/${styleName}/`, + }; + }), + }, +]; + +export default sidebar; diff --git a/apps/docs/.vitepress/config/sidebarTools.ts b/apps/docs/.vitepress/config/sidebarTools.ts new file mode 100644 index 0000000..4d39c65 --- /dev/null +++ b/apps/docs/.vitepress/config/sidebarTools.ts @@ -0,0 +1,13 @@ +import { DefaultTheme } from 'vitepress'; + +const sidebar: DefaultTheme.SidebarItem[] = [ + { + text: 'Tools', + items: [ + { text: 'WCAG Contrast Picker', link: '/tools/contrast/' }, + { text: 'Bundle Size Estimator', link: '/tools/bundle-size/' }, + ], + }, +]; + +export default sidebar; diff --git a/apps/docs/.vitepress/theme/Layout.vue b/apps/docs/.vitepress/theme/Layout.vue new file mode 100644 index 0000000..bac71c4 --- /dev/null +++ b/apps/docs/.vitepress/theme/Layout.vue @@ -0,0 +1,41 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppCDN.vue b/apps/docs/.vitepress/theme/components/app/AppCDN.vue new file mode 100644 index 0000000..e6a8323 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppCDN.vue @@ -0,0 +1,295 @@ + + + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppComparison.vue b/apps/docs/.vitepress/theme/components/app/AppComparison.vue new file mode 100644 index 0000000..22fd96c --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppComparison.vue @@ -0,0 +1,283 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppCreateStyle.vue b/apps/docs/.vitepress/theme/components/app/AppCreateStyle.vue new file mode 100644 index 0000000..733103a --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppCreateStyle.vue @@ -0,0 +1,475 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppEditor.vue b/apps/docs/.vitepress/theme/components/app/AppEditor.vue new file mode 100644 index 0000000..8f4144b --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppEditor.vue @@ -0,0 +1,555 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppFrameworks.vue b/apps/docs/.vitepress/theme/components/app/AppFrameworks.vue new file mode 100644 index 0000000..071da46 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppFrameworks.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppHero.vue b/apps/docs/.vitepress/theme/components/app/AppHero.vue new file mode 100644 index 0000000..1a91634 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppHero.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppHeroAsideUserList.vue b/apps/docs/.vitepress/theme/components/app/AppHeroAsideUserList.vue new file mode 100644 index 0000000..ba3ffec --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppHeroAsideUserList.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppHeroContent.vue b/apps/docs/.vitepress/theme/components/app/AppHeroContent.vue new file mode 100644 index 0000000..6c3bc79 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppHeroContent.vue @@ -0,0 +1,47 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppHeroSwarm.vue b/apps/docs/.vitepress/theme/components/app/AppHeroSwarm.vue new file mode 100644 index 0000000..78198a2 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppHeroSwarm.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppHighlights.vue b/apps/docs/.vitepress/theme/components/app/AppHighlights.vue new file mode 100644 index 0000000..526dda4 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppHighlights.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppIntegration.vue b/apps/docs/.vitepress/theme/components/app/AppIntegration.vue new file mode 100644 index 0000000..ca3c957 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppIntegration.vue @@ -0,0 +1,424 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppOpenSource.vue b/apps/docs/.vitepress/theme/components/app/AppOpenSource.vue new file mode 100644 index 0000000..3d959ce --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppOpenSource.vue @@ -0,0 +1,302 @@ + + + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppOpenSourceCards.vue b/apps/docs/.vitepress/theme/components/app/AppOpenSourceCards.vue new file mode 100644 index 0000000..a802109 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppOpenSourceCards.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppSeedDemo.vue b/apps/docs/.vitepress/theme/components/app/AppSeedDemo.vue new file mode 100644 index 0000000..271dea1 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppSeedDemo.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppSeedDemoCode.vue b/apps/docs/.vitepress/theme/components/app/AppSeedDemoCode.vue new file mode 100644 index 0000000..f020aed --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppSeedDemoCode.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppSeedDemoPreview.vue b/apps/docs/.vitepress/theme/components/app/AppSeedDemoPreview.vue new file mode 100644 index 0000000..0bdd23e --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppSeedDemoPreview.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppSmallHero.vue b/apps/docs/.vitepress/theme/components/app/AppSmallHero.vue new file mode 100644 index 0000000..b911f2b --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppSmallHero.vue @@ -0,0 +1,302 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppStatsBanner.vue b/apps/docs/.vitepress/theme/components/app/AppStatsBanner.vue new file mode 100644 index 0000000..736eef3 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppStatsBanner.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppStatsBannerCard.vue b/apps/docs/.vitepress/theme/components/app/AppStatsBannerCard.vue new file mode 100644 index 0000000..d1e49d7 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppStatsBannerCard.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppStatsChart.vue b/apps/docs/.vitepress/theme/components/app/AppStatsChart.vue new file mode 100644 index 0000000..c31906b --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppStatsChart.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppStatsGlobe.vue b/apps/docs/.vitepress/theme/components/app/AppStatsGlobe.vue new file mode 100644 index 0000000..61c92eb --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppStatsGlobe.vue @@ -0,0 +1,569 @@ + + + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppStatsMultiLineChart.vue b/apps/docs/.vitepress/theme/components/app/AppStatsMultiLineChart.vue new file mode 100644 index 0000000..ce28b7d --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppStatsMultiLineChart.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppStyleShowcase.vue b/apps/docs/.vitepress/theme/components/app/AppStyleShowcase.vue new file mode 100644 index 0000000..5075a75 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppStyleShowcase.vue @@ -0,0 +1,440 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppUseCases.vue b/apps/docs/.vitepress/theme/components/app/AppUseCases.vue new file mode 100644 index 0000000..2b63cbc --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppUseCases.vue @@ -0,0 +1,599 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/app/AppUseCasesCard.vue b/apps/docs/.vitepress/theme/components/app/AppUseCasesCard.vue new file mode 100644 index 0000000..2917448 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/app/AppUseCasesCard.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/docs/DocsComparisonTable.vue b/apps/docs/.vitepress/theme/components/docs/DocsComparisonTable.vue new file mode 100644 index 0000000..2c826bc --- /dev/null +++ b/apps/docs/.vitepress/theme/components/docs/DocsComparisonTable.vue @@ -0,0 +1,221 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/docs/DocsGrid.vue b/apps/docs/.vitepress/theme/components/docs/DocsGrid.vue new file mode 100644 index 0000000..de74a5c --- /dev/null +++ b/apps/docs/.vitepress/theme/components/docs/DocsGrid.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/docs/DocsHighlights.vue b/apps/docs/.vitepress/theme/components/docs/DocsHighlights.vue new file mode 100644 index 0000000..fa297e3 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/docs/DocsHighlights.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/docs/DocsStyleGrid.vue b/apps/docs/.vitepress/theme/components/docs/DocsStyleGrid.vue new file mode 100644 index 0000000..e60d6cb --- /dev/null +++ b/apps/docs/.vitepress/theme/components/docs/DocsStyleGrid.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/guides/UniqueAvatarsTable.vue b/apps/docs/.vitepress/theme/components/guides/UniqueAvatarsTable.vue new file mode 100644 index 0000000..28b0cb8 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/guides/UniqueAvatarsTable.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutClientOnly.vue b/apps/docs/.vitepress/theme/components/layout/LayoutClientOnly.vue new file mode 100644 index 0000000..833f4c1 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutClientOnly.vue @@ -0,0 +1,14 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutFooter.vue b/apps/docs/.vitepress/theme/components/layout/LayoutFooter.vue new file mode 100644 index 0000000..5c3875b --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutFooter.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutFooterBrand.vue b/apps/docs/.vitepress/theme/components/layout/LayoutFooterBrand.vue new file mode 100644 index 0000000..e80542e --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutFooterBrand.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutFooterCredits.vue b/apps/docs/.vitepress/theme/components/layout/LayoutFooterCredits.vue new file mode 100644 index 0000000..dee0d46 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutFooterCredits.vue @@ -0,0 +1,68 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutFooterLinks.vue b/apps/docs/.vitepress/theme/components/layout/LayoutFooterLinks.vue new file mode 100644 index 0000000..31509df --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutFooterLinks.vue @@ -0,0 +1,37 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutFooterSponsor.vue b/apps/docs/.vitepress/theme/components/layout/LayoutFooterSponsor.vue new file mode 100644 index 0000000..cdcf62c --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutFooterSponsor.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutNavActions.vue b/apps/docs/.vitepress/theme/components/layout/LayoutNavActions.vue new file mode 100644 index 0000000..389f1a0 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutNavActions.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/layout/LayoutVPLocalNav.vue b/apps/docs/.vitepress/theme/components/layout/LayoutVPLocalNav.vue new file mode 100644 index 0000000..13b21b5 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/layout/LayoutVPLocalNav.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/pages/PageHome.vue b/apps/docs/.vitepress/theme/components/pages/PageHome.vue new file mode 100644 index 0000000..0d35df6 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/pages/PageHome.vue @@ -0,0 +1,21 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/pages/PageLicenses.vue b/apps/docs/.vitepress/theme/components/pages/PageLicenses.vue new file mode 100644 index 0000000..4f1c9e9 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/pages/PageLicenses.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/pages/PageStats.vue b/apps/docs/.vitepress/theme/components/pages/PageStats.vue new file mode 100644 index 0000000..bfe889a --- /dev/null +++ b/apps/docs/.vitepress/theme/components/pages/PageStats.vue @@ -0,0 +1,566 @@ + + + + + diff --git a/apps/docs/.vitepress/theme/components/pages/PageWhyDiceBear.vue b/apps/docs/.vitepress/theme/components/pages/PageWhyDiceBear.vue new file mode 100644 index 0000000..0f312da --- /dev/null +++ b/apps/docs/.vitepress/theme/components/pages/PageWhyDiceBear.vue @@ -0,0 +1,26 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/playground/Playground.vue b/apps/docs/.vitepress/theme/components/playground/Playground.vue new file mode 100644 index 0000000..29798ed --- /dev/null +++ b/apps/docs/.vitepress/theme/components/playground/Playground.vue @@ -0,0 +1,16 @@ + + + diff --git a/apps/docs/.vitepress/theme/components/playground/PlaygroundBatchDownload.vue b/apps/docs/.vitepress/theme/components/playground/PlaygroundBatchDownload.vue new file mode 100644 index 0000000..cbf8af5 --- /dev/null +++ b/apps/docs/.vitepress/theme/components/playground/PlaygroundBatchDownload.vue @@ -0,0 +1,533 @@ + + +