Repository: DenverCoder1/github-readme-streak-stats Branch: main Commit: 4ccf1e400748 Files: 57 Total size: 354.3 KB Directory structure: gitextract_zrbf4bwk/ ├── .deepsource.toml ├── .devcontainer/ │ ├── Dockerfile │ ├── devcontainer.json │ ├── first-run-notice.txt │ ├── on-create.sh │ └── post-create.sh ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ ├── question.md │ │ └── theme.md │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── force-release.yml │ ├── phpunit-ci-coverage.yml │ ├── prettier.yml │ ├── release.yml │ └── translation-progress.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── Aptfile ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── composer.json ├── docs/ │ ├── faq.md │ └── themes.md ├── package.json ├── scripts/ │ └── translation-progress.php ├── src/ │ ├── cache.php │ ├── card.php │ ├── colors.php │ ├── demo/ │ │ ├── css/ │ │ │ ├── style.css │ │ │ └── toggle-dark.css │ │ ├── index.php │ │ ├── js/ │ │ │ ├── accordion.js │ │ │ ├── script.js │ │ │ └── toggle-dark.js │ │ └── preview.php │ ├── index.php │ ├── stats.php │ ├── themes.php │ ├── translations.php │ └── whitelist.php └── tests/ ├── CacheTest.php ├── OptionsTest.php ├── RenderTest.php ├── StatsTest.php ├── TranslationsTest.php └── phpunit/ └── phpunit.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .deepsource.toml ================================================ version = 1 test_patterns = [ "tests/**" ] exclude_patterns = [ "vendor/**", "*.min.js" ] [[analyzers]] name = "php" enabled = true [analyzers.meta] skip_doc_coverage = ["class", "magic"] [[analyzers]] name = "javascript" enabled = true ================================================ FILE: .devcontainer/Dockerfile ================================================ FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu-24.04 ADD first-run-notice.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt RUN apt-get update -y && \ apt-get install -y php php-curl php-xml inkscape composer ================================================ FILE: .devcontainer/devcontainer.json ================================================ { "build": { "dockerfile": "Dockerfile" }, "onCreateCommand": "/workspaces/github-readme-streak-stats/.devcontainer/on-create.sh", "postCreateCommand": "/workspaces/github-readme-streak-stats/.devcontainer/post-create.sh"} ================================================ FILE: .devcontainer/first-run-notice.txt ================================================ 👋 Welcome to Codespaces! You are using the pre-configured image. Tests can be executed with: composer test ================================================ FILE: .devcontainer/on-create.sh ================================================ #!/bin/bash set -e cd /workspaces/github-readme-streak-stats composer install ================================================ FILE: .devcontainer/post-create.sh ================================================ #!/bin/bash set -e cd /workspaces/github-readme-streak-stats if [ -n "$GITHUB_TOKEN" ]; then echo "TOKEN=$GITHUB_TOKEN" > .env fi ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false indent_size = 2 [*.{js,css}] indent_size = 2 ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto eol=lf ================================================ FILE: .github/FUNDING.yml ================================================ github: [DenverCoder1] patreon: open_collective: ko_fi: tidelift: community_bridge: liberapay: issuehunt: otechie: custom: ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "" labels: "bug" assignees: "" --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: "" labels: "enhancement" assignees: "" --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: I have a question about GitHub Streak Stats title: "" labels: "question" assignees: "" --- **Description** A brief description of the question or issue: ================================================ FILE: .github/ISSUE_TEMPLATE/theme.md ================================================ --- name: Theme Request about: Request a theme for the project title: "" labels: "theme" assignees: "" --- **Describe your theme in detail** A clear description about what the theme would entail. **Include a screenshot / image** **Color palette** Describe the colors that could be used with this theme. Are you going to add the theme? - [ ] Check for yes ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "composer" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ================================================ FILE: .github/pull_request_template.md ================================================ ## Description Fixes # ### Type of change - [ ] Bug fix (added a non-breaking change which fixes an issue) - [ ] New feature (added a non-breaking change which adds functionality) - [ ] Updated documentation (updated the readme, templates, or other repo files) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ## How Has This Been Tested? - [ ] Tested locally with a valid username - [ ] Tested locally with an invalid username - [ ] Ran tests with `composer test` - [ ] Added or updated test cases to test new features ## Checklist: - [ ] I have checked to make sure no other [pull requests](https://github.com/DenverCoder1/github-readme-streak-stats/pulls?q=is%3Apr+sort%3Aupdated-desc+) are open for this issue - [ ] The code is properly formatted and is consistent with the existing code style - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings ## Screenshots ================================================ FILE: .github/workflows/force-release.yml ================================================ name: Manual Release on: workflow_dispatch: jobs: changelog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: conventional Changelog Action id: changelog uses: TriPSs/conventional-changelog-action@v3.7.1 with: github-token: ${{ secrets.CHANGELOG_RELEASE }} version-file: './composer.json' output-file: 'false' skip-on-empty: 'false' - name: create release uses: actions/create-release@v1 if: ${{ steps.changelog.outputs.skipped == 'false' }} env: GITHUB_TOKEN: ${{ secrets.CHANGELOG_RELEASE }} with: tag_name: ${{ steps.changelog.outputs.tag }} release_name: ${{ steps.changelog.outputs.tag }} body: ${{ steps.changelog.outputs.clean_changelog }} ================================================ FILE: .github/workflows/phpunit-ci-coverage.yml ================================================ name: PHPUnit CI on: workflow_dispatch: pull_request: push: branches: - main env: PHP_VERSION: 8.2 jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: php-actions/composer@v6 with: php_extensions: intl php_version: ${{ env.PHP_VERSION }} - name: PHPUnit Tests uses: php-actions/phpunit@v4 with: php_extensions: intl php_version: ${{ env.PHP_VERSION }} bootstrap: vendor/autoload.php configuration: tests/phpunit/phpunit.xml args: --testdox env: TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/prettier.yml ================================================ name: Format with Prettier on: push: branches: - main pull_request: paths: - "**.php" - "**.md" - "**.js" - "**.css" - ".github/workflows/prettier.yml" jobs: prettier: runs-on: ubuntu-latest steps: - name: Checkout Pull Request if: ${{ github.event_name == 'pull_request' }} uses: actions/checkout@v3 with: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.ref }} - name: Checkout Push if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '22' - name: Install prettier and plugin-php run: npm i - name: Lint with Prettier continue-on-error: true run: composer lint - name: Prettify code run: | composer lint-fix git diff - name: Commit changes uses: EndBug/add-and-commit@v9 with: message: "style: Formatted code with Prettier" default_author: github_actions ================================================ FILE: .github/workflows/release.yml ================================================ name: Automated Releases on: workflow_dispatch: jobs: changelog: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: conventional Changelog Action id: changelog uses: TriPSs/conventional-changelog-action@v3.7.1 with: github-token: ${{ secrets.CHANGELOG_RELEASE }} version-file: './composer.json' output-file: 'false' - name: create release uses: actions/create-release@v1 if: ${{ steps.changelog.outputs.skipped == 'false' }} env: GITHUB_TOKEN: ${{ secrets.CHANGELOG_RELEASE }} with: tag_name: ${{ steps.changelog.outputs.tag }} release_name: ${{ steps.changelog.outputs.tag }} body: ${{ steps.changelog.outputs.clean_changelog }} ================================================ FILE: .github/workflows/translation-progress.yml ================================================ name: Update Translation Progress on: workflow_dispatch: push: branches: - main paths: - "src/translations.php" - "scripts/translation-progress.php" - ".github/workflows/translation-progress.yml" - "README.md" env: PHP_VERSION: 8.2 jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: php-actions/composer@v6 with: php_extensions: intl php_version: ${{ env.PHP_VERSION }} - name: Update Translations run: php scripts/translation-progress.php - name: Commit Changes uses: EndBug/add-and-commit@v7 with: author_name: GitHub Actions author_email: github-actions[bot]@users.noreply.github.com message: "docs(readme): Update translation progress" add: "README.md" ================================================ FILE: .gitignore ================================================ # Generated files vendor/ node_modules/ *.log composer.phar yarn.lock package-lock.json .vercel # Cache directory cache/ # Local Configuration .DS_Store # Environment .env .php-version DOCKER_ENV docker_tag # IDE .vscode/ .idea/ ================================================ FILE: .prettierignore ================================================ vendor **/*.min.js .prettierrc ================================================ FILE: .prettierrc.js ================================================ module.exports = { printWidth: 120, endOfLine: "auto", plugins: ["@prettier/plugin-php"], overrides: [ { files: "*.php", options: { parser: "php", }, }, ], }; ================================================ FILE: Aptfile ================================================ inkscape ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting me via direct message on Twitter, Reddit, or Discord. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ ## Contributing Guidelines Contributions are welcome! Feel free to open an issue or submit a pull request if you have a way to improve this project. Make sure your request is meaningful and you have tested the app locally before submitting a pull request. This documentation contains a set of guidelines to help you during the contribution process. ### Need some help regarding the basics? You can refer to the following articles on the basics of Git and GitHub in case you are stuck: - [Forking a Repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) - [Cloning a Repo](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) - [How to create a Pull Request](https://opensource.com/article/19/7/create-pull-request-github) - [Getting started with Git and GitHub](https://towardsdatascience.com/getting-started-with-git-and-github-6fcd0f2d4ac6) - [Learn GitHub from Scratch](https://github.com/githubtraining/introduction-to-github) ### Installing Requirements #### Requirements - [PHP 8.2+](https://www.apachefriends.org/index.html) - [Composer](https://getcomposer.org) - [Inkscape](https://inkscape.org) (for PNG rendering) #### Linux ```bash sudo apt-get install php sudo apt-get install php-curl sudo apt-get install composer sudo apt-get install inkscape ``` #### Windows Install PHP from [XAMPP](https://www.apachefriends.org/index.html) or [php.net](https://windows.php.net/download) [▶ How to install and run PHP using XAMPP (Windows)](https://www.youtube.com/watch?v=K-qXW9ymeYQ) [📥 Download Composer](https://getcomposer.org/download/) ### Clone the repository ``` git clone https://github.com/DenverCoder1/github-readme-streak-stats.git cd github-readme-streak-stats ``` ### Authorization To get the GitHub API to run locally you will need to provide a token. 1. Visit [this link](https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats) to create a new Personal Access Token 2. Scroll to the bottom and click **"Generate token"** 3. **Make a copy** of the `.env.example` named `.env` in the root directory and add **your token** after `TOKEN=`. ```php TOKEN= ``` ### Install dependencies Run the following command to install all the required dependencies to work on this project. ```bash composer install ``` ### Running the app locally ```bash composer start ``` Open http://localhost:8000/?user=DenverCoder1 to run the project locally Open http://localhost:8000/demo/ to run the demo site ### Running the tests Run the following command to run the PHPUnit test script which will verify that the tested functionality is still working. ```bash composer test ``` ## Linting This project uses Prettier for formatting PHP, Markdown, JavaScript and CSS files. ```bash # Run prettier and show the files that need to be fixed composer lint # Run prettier and fix the files composer lint-fix ``` ## Submitting Contributions 👨‍💻 Below you will find the process and workflow used to review and merge your changes. ### Step 0 : Find an issue - Take a look at the existing issues or create your **own** issues! ![issues tab](https://user-images.githubusercontent.com/63443481/136185624-24447858-de8d-4b0a-bb6b-2528d9031196.PNG) ### Step 1 : Fork the Project - Fork this repository. This will create a copy of this repository on your GitHub profile. Keep a reference to the original project in the `upstream` remote. ```bash git clone https://github.com//github-readme-streak-stats.git cd github-readme-streak-stats git remote add upstream https://github.com/DenverCoder1/github-readme-streak-stats.git ``` ![fork button](https://user-images.githubusercontent.com/63443481/136185816-0b6770d7-0b00-4951-861a-dd15e3954918.PNG) - If you have already forked the project, update your copy before working. ```bash git remote update git checkout git rebase upstream/ ``` ### Step 2 : Branch Create a new branch. Use its name to identify the issue you're addressing. ```bash # Creates a new branch with the name feature_name and switches to it git checkout -b feature_name ``` ### Step 3 : Work on the issue assigned - Work on the issue(s) assigned to you. - Make all the necessary changes to the codebase. - After you've made changes or made your contribution to the project, add changes to the branch you've just created using: ```bash # To add all new files to the branch git add . # To add only a few files to the branch git add ``` ### Step 4 : Commit - Commit a descriptive message using: ```bash # This message will get associated with all files you have changed git commit -m "message" ``` ### Step 5 : Work Remotely - Now you are ready to your work on the remote repository. - When your work is ready and complies with the project conventions, upload your changes to your fork: ```bash # To push your work to your remote repository git push -u origin Branch_Name ``` - Here is how your branch will look. ![forked branch](https://user-images.githubusercontent.com/63443481/136186235-204f5c7a-1129-44b5-af20-89aa6a68d952.PNG) ### Step 6 : Pull Request - Go to your forked repository in your browser and click on "Compare and pull request". Then add a title and description to your pull request that explains your contribution. compare and pull request opening pull request - Voila! Your Pull Request has been submitted and it's ready to be merged.🥳 #### Happy Contributing! ================================================ FILE: Dockerfile ================================================ # Use PHP 8.3 (8.4 not supported yet) FROM php:8.3-apache@sha256:6be4ef702b2dd05352f7e5fe14667696a4ad091c9d2ad9083becbee4300dc3b1 # Install system dependencies and PHP extensions in one layer RUN apt-get update && apt-get install -y --no-install-recommends \ git \ unzip \ libicu-dev \ inkscape \ fonts-dejavu-core \ curl \ && docker-php-ext-configure intl \ && docker-php-ext-install intl \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Install Composer COPY --from=composer/composer:latest-bin@sha256:c9bda63056674836406cacfbbdd8ef770fb4692ac419c967034225213c64e11b /composer /usr/bin/composer # Set working directory WORKDIR /var/www/html # Copy composer files and install dependencies COPY composer.json composer.lock ./ COPY src/ ./src/ RUN composer install --no-dev --optimize-autoloader --no-scripts # Configure Apache to serve from src/ directory and pass environment variables RUN a2enmod rewrite headers && \ echo 'ServerTokens Prod\n\ ServerSignature Off\n\ PassEnv TOKEN\n\ PassEnv WHITELIST\n\ \n\ ServerAdmin webmaster@localhost\n\ DocumentRoot /var/www/html/src\n\ \n\ Options -Indexes\n\ AllowOverride None\n\ Require all granted\n\ Header always set Access-Control-Allow-Origin "*"\n\ Header always set Content-Type "image/svg+xml" "expr=%{REQUEST_URI} =~ m#\\.svg$#i"\n\ Header always set Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; img-src data:;" "expr=%{REQUEST_URI} =~ m#\\.svg$#i"\n\ Header always set Referrer-Policy "no-referrer-when-downgrade"\n\ Header always set X-Content-Type-Options "nosniff"\n\ \n\ ErrorLog ${APACHE_LOG_DIR}/error.log\n\ CustomLog ${APACHE_LOG_DIR}/access.log combined\n\ ' > /etc/apache2/sites-available/000-default.conf RUN mkdir -p /var/www/html/cache # Set secure permissions (cache dir needs write access for www-data) RUN chown -R www-data:www-data /var/www/html && \ find /var/www/html -type d -exec chmod 755 {} \; && \ find /var/www/html -type f -exec chmod 644 {} \; && \ chmod 775 /var/www/html/cache # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost/demo/ || exit 1 # Expose port EXPOSE 80 # Start Apache CMD ["apache2-foreground"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Jonah Lawrence 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. ================================================ FILE: Procfile ================================================ web: vendor/bin/heroku-php-apache2 src/ ================================================ FILE: README.md ================================================

Github Readme Streak Stats

Display your total contributions, current streak,
and longest streak on your GitHub profile README

## ⚡ Quick setup 1. Copy-paste the markdown below into your GitHub profile README 2. Replace the value after `?user=` with your GitHub username ```md [![GitHub Streak](https://streak-stats.demolab.com/?user=DenverCoder1)](https://git.io/streak-stats) ``` 3. Star the repo 😄 ### Next Steps - Check out the [Demo Site](https://streak-stats.demolab.com) or [Options](https://github.com/DenverCoder1/github-readme-streak-stats?tab=readme-ov-file#-options) below for available customizations. - It is recommended to self-host the project more better reliability. See [Deploying it on your own](https://github.com/DenverCoder1/github-readme-streak-stats?tab=readme-ov-file#-deploying-it-on-your-own) for more details. [![][hspace]](#) [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)][herokudeploy] [![Deploy to Vercel](https://i.imgur.com/Mb3VLCi.png)][verceldeploy] ## ⚙ Demo Site Here you can customize your Streak Stats card with a live preview. [![Demo Site](https://user-images.githubusercontent.com/20955511/114579753-dbac8780-9c86-11eb-97dd-207039f67d20.gif "Demo Site")](http://streak-stats.demolab.com/demo/) ## 🔧 Options The `user` field is the only required option. All other fields are optional. If the `theme` parameter is specified, any color customizations specified will be applied on top of the theme, overriding the theme's values. | Parameter | Details | Example | | :------------------------: | :----------------------------------------------: | :------------------------------------------------------------------------------------------------: | | `user` | GitHub username to show stats for | `DenverCoder1` | | `theme` | The theme to apply (Default: `default`) | `dark`, `radical`, etc. [🎨➜](./docs/themes.md) | | `hide_border` | Make the border transparent (Default: `false`) | `true` or `false` | | `border_radius` | Set the roundness of the edges (Default: `4.5`) | Number `0` (sharp corners) to `248` (ellipse) | | `background` | Background color (eg. `f2f2f2`, `35,d22,00f`) | **hex code** without `#`, **css color**, or gradient in the form `angle,start_color,...,end_color` | | `border` | Border color | **hex code** without `#` or **css color** | | `stroke` | Stroke line color between sections | **hex code** without `#` or **css color** | | `ring` | Color of the ring around the current streak | **hex code** without `#` or **css color** | | `fire` | Color of the fire in the ring | **hex code** without `#` or **css color** | | `currStreakNum` | Current streak number | **hex code** without `#` or **css color** | | `sideNums` | Total and longest streak numbers | **hex code** without `#` or **css color** | | `currStreakLabel` | Current streak label | **hex code** without `#` or **css color** | | `sideLabels` | Total and longest streak labels | **hex code** without `#` or **css color** | | `dates` | Date range text color | **hex code** without `#` or **css color** | | `excludeDaysLabel` | Excluded days of the week text color | **hex code** without `#` or **css color** | | `date_format` | Date format pattern or empty for locale format | See note below on [📅 Date Formats](#-date-formats) | | `locale` | Locale for labels and numbers (Default: `en`) | ISO 639-1 code - See [🗪 Locales](#-locales) | | `short_numbers` | Use short numbers (e.g. 1.5k instead of 1,500) | `true` or `false` | | `type` | Output format (Default: `svg`) | Current options: `svg`, `png` or `json` | | `mode` | Streak mode (Default: `daily`) | `daily` (contribute daily) or `weekly` (contribute once per Sun-Sat week) | | `exclude_days` | List of days of the week to exclude from streaks | Comma-separated list of day abbreviations (Sun, Mon, Tue, Wed, Thu, Fri, Sat) e.g. `Sun,Sat` | | `disable_animations` | Disable SVG animations (Default: `false`) | `true` or `false` | | `card_width` | Width of the card in pixels (Default: `495`) | Positive integer, minimum width is 100px per column | | `card_height` | Height of the card in pixels (Default: `195`) | Positive integer, minimum height is 170px | | `hide_total_contributions` | Hide the total contributions (Default: `false`) | `true` or `false` | | `hide_current_streak` | Hide the current streak (Default: `false`) | `true` or `false` | | `hide_longest_streak` | Hide the longest streak (Default: `false`) | `true` or `false` | | `starting_year` | Starting year of contributions | Integer, must be `2005` or later, eg. `2017`. By default, your account creation year is used. | ### 🖌 Themes To enable a theme, append `&theme=` followed by the theme name to the end of the source URL: ```md [![GitHub Streak](https://streak-stats.demolab.com/?user=DenverCoder1&theme=dark)](https://git.io/streak-stats) ``` | Theme | Preview | | :------------: | :-----------------------------------------------------------: | | `default` | ![default](https://i.imgur.com/IaTuYdS.png) | | `dark` | ![dark](https://i.imgur.com/bUrsjlp.png) | | `highcontrast` | ![highcontrast](https://i.imgur.com/ovrVrTY.png) | | More themes! | **🎨 [See a list of all available themes](./docs/themes.md)** | **If you have come up with a new theme you'd like to share with others, please see [Issue #32](https://github.com/DenverCoder1/github-readme-streak-stats/issues/32) for more information on how to contribute.** ### 🗪 Locales The following are the locales that have labels translated in Streak Stats. The `locale` query parameter accepts any ISO language or locale code, see [here](https://gist.github.com/DenverCoder1/f61147ba26bfcf7c3bf605af7d3382d5) for a list of valid locales. The locale provided will be used for the date format and number format even if translations are not yet available.
en - English
English 100%
am - አማርኛ
አማርኛ 100%
ar - العربية
العربية 100%
as - অসমীয়া
অসমীয়া 100%
bho - भोजपुरी
भोजपुरी 100%
bn - বাংলা
বাংলা 100%
ca - català
català 100%
ceb - Cebuano
Cebuano 100%
da - dansk
dansk 100%
de - Deutsch
Deutsch 100%
el - Ελληνικά
Ελληνικά 100%
es - español
español 100%
fa - فارسی
فارسی 100%
fil - Filipino
Filipino 100%
fr - français
français 100%
gu - ગુજરાતી
ગુજરાતી 100%
he - עברית
עברית 100%
hi - हिन्दी
हिन्दी 100%
hu - magyar
magyar 100%
id - Indonesia
Indonesia 100%
it - italiano
italiano 100%
ja - 日本語
日本語 100%
jv - Jawa
Jawa 100%
kn - ಕನ್ನಡ
ಕನ್ನಡ 100%
ko - 한국어
한국어 100%
mai - मैथिली
मैथिली 100%
mal - മലയാളം
മലയാളം 100%
mi - Māori
Māori 100%
mr - मराठी
मराठी 100%
ms - Melayu
Melayu 100%
ms_ID - Melayu (Indonesia)
Melayu (Indonesia) 100%
my - မြန်မာ
မြန်မာ 100%
ne - नेपाली
नेपाली 100%
nl - Nederlands
Nederlands 100%
no - norsk
norsk 100%
pa - ਪੰਜਾਬੀ
ਪੰਜਾਬੀ 100%
pl - polski
polski 100%
ps - پښتو
پښتو 100%
pt - português
português 100%
pt_BR - português (Brasil)
português (Brasil) 100%
ro - română
română 100%
ru - русский
русский 100%
sa - संस्कृत भाषा
संस्कृत भाषा 100%
sd_PK - سنڌي (پاڪستان)
سنڌي (پاڪستان) 100%
sr_Cyrl - српски (ћирилица)
српски (ћирилица) 100%
sr_Latn - srpski (latinica)
srpski (latinica) 100%
su - Basa Sunda
Basa Sunda 100%
sv - svenska
svenska 100%
sw - Kiswahili
Kiswahili 100%
ta - தமிழ்
தமிழ் 100%
tcy - Tulu
Tulu 100%
te - తెలుగు
తెలుగు 100%
th - ไทย
ไทย 100%
tr - Türkçe
Türkçe 100%
uk - українська
українська 100%
ur_PK - اردو (پاکستان)
اردو (پاکستان) 100%
vi - Tiếng Việt
Tiếng Việt 100%
yo - Èdè Yorùbá
Èdè Yorùbá 100%
zh_Hans - 中文(简体)
中文(简体) 100%
zh_Hant - 中文(繁體)
中文(繁體) 100%
bg - български
български 86%
ht - créole haïtien
créole haïtien 86%
hy - հայերեն
հայերեն 86%
rw - Kinyarwanda
Kinyarwanda 86%
**If you would like to help translate the Streak Stats cards, please see [Issue #236](https://github.com/DenverCoder1/github-readme-streak-stats/issues/236) for more information.** ### 📅 Date Formats If `date_format` is not provided or is empty, the PHP Intl library is used to determine the date format based on the locale specified in the `locale` query parameter. A custom date format can be specified by passing a string to the `date_format` parameter. The required format is to use format string characters from [PHP's date function](https://www.php.net/manual/en/datetime.format.php) with brackets around the part representing the year. When the contribution year is equal to the current year, the characters in brackets will be omitted. **Examples:** | Date Format | Result | | :-----------------: | :-----------------------------------------------------------------------------: | |
d F[, Y]
|
"2020-04-14" => "14 April, 2020"

"2024-04-14" => "14 April"
| |
j/n/Y
|
"2020-04-14" => "14/4/2020"

"2024-04-14" => "14/4/2024"
| |
[Y.]n.j
|
"2020-04-14" => "2020.4.14"

"2024-04-14" => "4.14"
| |
M j[, Y]
|
"2020-04-14" => "Apr 14, 2020"

"2024-04-14" => "Apr 14"
| ### Example ```md [![GitHub Streak](https://streak-stats.demolab.com/?user=denvercoder1&currStreakNum=2FD3EB&fire=pink&sideLabels=F00&date_format=[Y.]n.j)](https://git.io/streak-stats) ``` ## ℹ️ How these stats are calculated This tool uses the contribution graphs on your GitHub profile to calculate which days you have contributed. To include contributions in private repositories, turn on the setting for "Private contributions" from the dropdown menu above the contribution graph on your profile page. Contributions include commits, pull requests, and issues that you create in standalone repositories. The longest streak is the highest number of consecutive days on which you have made at least one contribution. The current streak is the number of consecutive days ending with the current day on which you have made at least one contribution. If you have made a contribution today, it will be counted towards the current streak, however, if you have not made a contribution today, the streak will only count days before today so that your streak will not be zero. > [!NOTE] > You may need to wait up to 24 hours for new contributions to show up ([Learn how contributions are counted](https://docs.github.com/articles/why-are-my-contributions-not-showing-up-on-my-profile)) ## 📤 Deploying it on your own It is preferable to host the files on your own server and it takes less than 2 minutes to set up. Doing this can lead to better uptime and more control over customization (you can modify the code for your usage). You can deploy the PHP files on any website server with PHP installed including Heroku and Vercel. The Inkscape dependency is required for PNG rendering, as well as Segoe UI font for the intended rendering. If using Heroku, the buildpacks will install these for you automatically. ### [![Deploy to Vercel](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/5a503e6b-c462-4627-82ee-651f2cb2a1fc)][verceldeploy] Vercel is the recommended option for hosting the files since it is **free** and easy to set up. Watch the video below or expand the instructions to learn how to deploy to Vercel. > [!NOTE] > PNG mode is not supported since Inkscape will not be installed but the default SVG mode will work. ### 📺 [Click here for a video tutorial on how to self-host on Vercel](https://www.youtube.com/watch?v=maoXtlb8t44)
Instructions for deploying to Vercel (Free) ### Step-by-step instructions for deploying to Vercel #### Option 1: Deploy to Vercel quickly with the Deploy button (recommended) > [!IMPORTANT] > Make sure that you host the **`vercel`** branch as otherwise you'll get a 404 error from Vercel. You can set the `vercel` branch as default after forking the repo. 1. Click the Deploy button below [![][hspace]](#) [![Deploy with Vercel](https://i.imgur.com/Mb3VLCi.png)][verceldeploy] 2. Create your repository by filling in a Repository Name and clicking "Create" 3. Visit [this link](https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats) to create a new Personal Access Token (no scopes required) 4. Scroll to the bottom and click **"Generate token"** 5. **Add the token** as a Config Var with the key `TOKEN`: ![vercel environment variables](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/17a433d6-0aaa-4c69-9a53-6d4638318fbb) 6. Click **"Deploy"** at the end of the form 7. Once the app is deployed, click the screenshot of your app or continue to the dashboard to find your domain to use in place of `streak-stats.demolab.com` ![deployment](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/32092461-5983-4fed-b21b-29be55ed85e8) > ⚠️ **Note** > If you receive an error related to libssl or Node 20.x, you can fix this by opening your Vercel project settings and changing the Node.js version to 18.x. > > ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/5fb18fb5-debe-4620-9c8b-193ab442a617) #### Option 2: Deploy to Vercel manually 1. Sign in to **Vercel** or create a new account at 2. Use the following command to clone the repository: `git clone https://github.com/DenverCoder1/github-readme-streak-stats.git`. If you plan to make changes, you can also fork the repository and clone your fork instead. If you do not have Git installed, you can download it from . 3. Navigate to the cloned repository's directory using the command `cd github-readme-streak-stats` 4. Switch to the "vercel" branch using the command `git checkout vercel` 5. Make sure you have the Vercel CLI (Command Line Interface) installed on your system. If not, you can download it from . 6. Run the command `vercel` and follow the prompts to link your Vercel account and choose a project name 7. After successful deployment, your app will be available at `.vercel.app` 8. Open [this link](https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats) to create a new Personal Access Token on GitHub. You don't need to select any scopes for the token. 9. Scroll to the bottom of the page and click on **"Generate token"** 10. Visit the Vercel dashboard at and select your project. Then, click on **"Settings"** and choose **"Environment Variables"**. 11. Add a new environment variable with the key `TOKEN` and the value as the token you generated in step 9, then save your changes 12. (Optional) You can also set the `WHITELIST` environment variable to restrict which GitHub usernames can be accessed through the service. Provide the usernames as a comma-separated list, for example: `user1,user2,user3`. If the variable is not set, information can be requested for any GitHub user. 13. To apply the new environment variable(s), you need to redeploy the app. Run `vercel --prod` to deploy the app to production. ![image](https://user-images.githubusercontent.com/20955511/209588756-8bf5b0cd-9aa6-41e8-909c-97bf41e525b3.png) > ⚠️ **Note** > To set up automatic Vercel deployments from GitHub, make sure to turn **off** "Include source files outside of the Root Directory" in the General settings and use `vercel` as the production branch in the Git settings. > ⚠️ **Note** > If you receive an error related to libssl or Node 20.x, you can fix this by opening your Vercel project settings and changing the Node.js version to 18.x. > > ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/5fb18fb5-debe-4620-9c8b-193ab442a617)
### [![Deploy on Heroku](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/e8b575af-5746-4200-a295-7e7baa448383)][herokudeploy] Heroku is another great option for hosting the files. All features are supported on Heroku and it is where the default domain is hosted. Heroku is not free, however, and you will need to pay between \$5 and \$7 per month to keep the app running. Expand the instructions below to learn how to deploy to Heroku.
Instructions for deploying to Heroku (Paid) ### Step-by-step instructions for deploying to Heroku 1. Sign in to **Heroku** or create a new account at 2. Visit [this link](https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats) to create a new Personal Access Token (no scopes required) 3. Scroll to the bottom and click **"Generate token"** 4. Click the Deploy button below [![][hspace]](#) [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)][herokudeploy] 5. **Add the token** as a Config Var with the key `TOKEN`: ![heroku config variables](https://user-images.githubusercontent.com/20955511/136292022-a8d9b3b5-d7d8-4a5e-a049-8d23b51ce9d7.png) 6. (Optional) You can also set the `WHITELIST` Config Var to restrict which GitHub usernames can be accessed through the service. Provide the usernames as a comma-separated list, for example: `user1,user2,user3`. If the variable is not set, information can be requested for any GitHub user. 7. Click **"Deploy App"** at the end of the form 8. Once the app is deployed, you can use `.herokuapp.com` in place of `streak-stats.demolab.com`
### ![Deploy on your own](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/e36ed842-ab56-473a-83fd-ace5bf968996) You can transfer the files to any webserver using FTP or other means, then refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for installation steps. ### 🐳 Docker Docker is a great option for self-hosting with full control over your environment. All features are supported including PNG rendering with Inkscape. Expand the instructions below to learn how to deploy with Docker.
Instructions for deploying with Docker ### Step-by-step instructions for deploying with Docker 1. Clone the repository: ```bash git clone https://github.com/DenverCoder1/github-readme-streak-stats.git cd github-readme-streak-stats ``` 2. Visit https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats to create a new Personal Access Token (no scopes required) 3. Scroll to the bottom and click "Generate token" 4. Build the Docker image: ```bash docker build -t streak-stats . ``` 5. Run the container with your GitHub token: ```bash docker run -d -p 8080:80 -e TOKEN=your_github_token_here streak-stats ``` 6. You can also optionally set the `WHITELIST` environment variable to restrict which GitHub usernames can be accessed through the service. If the `WHITELIST` variable is not set, information can be requested for any GitHub user. Provide the usernames as a comma-separated list, for example: ```bash docker run -d -p 8080:80 -e TOKEN=your_github_token_here -e WHITELIST=user1,user2,user3 streak-stats ``` 7. Visit http://localhost:8080 to access your self-hosted instance
[hspace]: https://user-images.githubusercontent.com/20955511/136058102-b79570bc-4912-4369-b664-064a0ada8588.png [verceldeploy]: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FDenverCoder1%2Fgithub-readme-streak-stats%2Ftree%2Fvercel&env=TOKEN&envDescription=GitHub%20Personal%20Access%20Token%20(no%20scopes%20required)&envLink=https%3A%2F%2Fgithub.com%2Fsettings%2Ftokens%2Fnew%3Fdescription%3DGitHub%2520Readme%2520Streak%2520Stats&project-name=streak-stats&repository-name=github-readme-streak-stats [herokudeploy]: https://heroku.com/deploy?template=https://github.com/DenverCoder1/github-readme-streak-stats/tree/main ## 🤗 Contributing Contributions are welcome! Feel free to [open an issue](https://github.com/DenverCoder1/github-readme-streak-stats/issues/new/choose) or submit a [pull request](https://github.com/DenverCoder1/github-readme-streak-stats/compare) if you have a way to improve this project. Make sure your request is meaningful and you have tested the app locally before submitting a pull request. Refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for more details on contributing, installing requirements, and running the application. ## 🙋‍♂️ Support 💙 If you like this project, give it a ⭐ and share it with friends!

Youtube Sponsor with Github

[☕ Buy me a coffee](https://ko-fi.com/jlawrence) --- Made with ❤️ and PHP Powered by Heroku ================================================ FILE: app.json ================================================ { "name": "GitHub Readme Streak Stats", "description": "🔥 Stay motivated and show off your contribution streak! 🌟 Display your total contributions, current streak, and longest streak on your GitHub profile README.", "repository": "https://github.com/DenverCoder1/github-readme-streak-stats/", "logo": "https://i.imgur.com/Z4bDOxC.png", "keywords": ["github", "dynamic", "readme", "contributions", "streak", "stats"], "addons": [], "env": { "TOKEN": { "description": "GitHub personal access token obtained from https://github.com/settings/tokens/new", "required": true } }, "formation": { "web": { "quantity": 1, "size": "basic" } }, "buildpacks": [ { "url": "https://github.com/heroku/heroku-buildpack-apt" }, { "url": "https://github.com/DenverCoder1/heroku-buildpack-fonts-segoe-ui" }, { "url": "heroku/php" } ] } ================================================ FILE: composer.json ================================================ { "name": "denvercoder1/github-readme-streak-stats", "description": "🔥 Stay motivated and show off your contribution streak! 🌟 Display your total contributions, current streak, and longest streak on your GitHub profile README.", "keywords": [ "github", "dynamic", "readme", "contributions", "streak", "stats" ], "license": "MIT", "version": "1.6.0", "homepage": "https://github.com/DenverCoder1/github-readme-streak-stats", "repositories": [ { "type": "vcs", "url": "https://github.com/DenverCoder1/github-readme-streak-stats" } ], "support": { "issues": "https://github.com/DenverCoder1/github-readme-streak-stats/issues", "source": "https://github.com/DenverCoder1/github-readme-streak-stats" }, "autoload": { "classmap": [ "src/" ] }, "require": { "php": "^8.2", "ext-intl": "*", "vlucas/phpdotenv": "^5.3" }, "require-dev": { "phpunit/phpunit": "^11" }, "scripts": { "start": [ "Composer\\Config::disableProcessTimeout", "php -S localhost:8000 -t src" ], "test": "./vendor/bin/phpunit --testdox tests", "lint": "npx prettier --check *.md **/*.{php,md,js,css}", "lint-fix": "npx prettier --write *.md **/*.{php,md,js,css}" } } ================================================ FILE: docs/faq.md ================================================ # FAQ ## How do I create a Readme for my profile? A profile readme appears on your profile page when you create a repository with the same name as your username and add a `README.md` file to it. For example, the repository for the user [`DenverCoder1`](https://github.com/DenverCoder1) is located at [`DenverCoder1/DenverCoder1`](https://github.com/DenverCoder1/DenverCoder1). ## How do I include GitHub Readme Streak Stats in my Readme? Markdown files on GitHub support embedded images using Markdown or HTML. You can customize your Streak Stats image on the [demo site](https://streak-stats.demolab.com/demo/) and use the image source in either of the following ways: ### Markdown ```md [![GitHub Streak](https://streak-stats.demolab.com?user=DenverCoder1)](https://git.io/streak-stats) ``` ### HTML ```html ``` ## Why doesn't my Streak Stats match my contribution graph? GitHub Readme Streak Stats uses the GitHub API to fetch your contribution data. These stats are returned in UTC time which may not match your local time. Additionally, due to caching, the stats may not be updated immediately after a commit. You may need to wait up to a few hours to see the latest stats. If you think your stats are not showing up because of a time zone issue, you can try one of the following: 1. Change the date of the commit. You can [adjust the time](https://codewithhugo.com/change-the-date-of-a-git-commit/) of a past commit to make it in the middle of the day. 2. Create a new commit in a repository with the date set to the date that is missing from your streak stats: ```bash git commit --date="2022-08-02 12:00" -m "Test commit" --allow-empty git push ``` ## What is considered a "contribution"? Contributions include commits, pull requests, and issues that you create in standalone repositories ([Learn more about what is considered a contribution](https://docs.github.com/articles/why-are-my-contributions-not-showing-up-on-my-profile)). The longest streak is the highest number of consecutive days on which you have made at least one contribution. The current streak is the number of consecutive days ending with the current day on which you have made at least one contribution. If you have made a contribution today, it will be counted towards the current streak, however, if you have not made a contribution today, the streak will only count days before today so that your streak will not be zero. > Note: You may need to wait up to 24 hours for new contributions to show up ([Learn how contributions are counted](https://docs.github.com/articles/why-are-my-contributions-not-showing-up-on-my-profile)) ## How do I enable private contributions? To include contributions in private repositories, turn on the setting for "Private contributions" from the dropdown menu above the contribution graph on your profile page. ## How do I center the image on the page? To center align images, you must use the HTML syntax and wrap it in an element with the HTML attribute `align="center"`. ```html

``` ## How do I make different images for dark mode and light mode? You can [specify theme context](https://github.blog/changelog/2022-05-19-specify-theme-context-for-images-in-markdown-beta/) using the `` and `` elements as shown below. The dark mode version appears in the `srcset` of the `` tag and the light mode version appears in the `src` of the `` tag. ```html ``` ## Why and how do I self-host GitHub Readme Streak Stats? Self-hosting the code can be done online and only takes a couple minutes. The benefits include better uptime since it will use your own access token so will not run into ratelimiting issues and it allows you to customize the deployment for your own use case. ### [📺 Click here for a video tutorial on how to self-host on Vercel](https://www.youtube.com/watch?v=maoXtlb8t44) See [Deploying it on your own](https://github.com/DenverCoder1/github-readme-streak-stats?tab=readme-ov-file#-deploying-it-on-your-own) in the Readme for detailed instructions. ================================================ FILE: docs/themes.md ================================================ ## Currently supported themes To enable a theme, append `&theme=` followed by the theme name to the end of your url. You can also try out and customize these themes on the [Demo Site](https://streak-stats.demolab.com/demo/)! Note: Theme names provided are case-insensitive and any use of underscores will be treated the same as hyphens. | Theme | Preview | | :---------------------------: | :------------------------------------------------------------------------------------------------------------------------: | | `default` | ![image](https://user-images.githubusercontent.com/107488620/183304039-a1fcf05c-0112-493a-9188-778708dc9e8f.png) | | `dark` | ![image](https://user-images.githubusercontent.com/107488620/183304038-2788ab5d-4c02-45e9-a724-990f27061c54.png) | | `highcontrast` | ![image](https://user-images.githubusercontent.com/107488620/183304037-0e54b5e6-f39a-481d-806f-3369d257a391.png) | | `transparent` | ![image](https://user-images.githubusercontent.com/20955511/221571948-1b69a2cc-87af-4e96-83fa-f01278c22c33.png) | | `radical` | ![image](https://user-images.githubusercontent.com/20955511/183303809-eb8fea2f-d56b-4ad3-9f6d-ef55f8812ed2.png) | | `merko` | ![image](https://user-images.githubusercontent.com/20955511/183303806-4ce9e5bb-6bd7-4914-a4ff-47edee01bde3.png) | | `gruvbox` | ![image](https://user-images.githubusercontent.com/20955511/183303804-95ff960f-ad52-4026-8627-a67f1599cee3.png) | | `gruvbox-duo` | ![image](https://user-images.githubusercontent.com/20955511/183303801-eb1d8dea-7f89-4075-b334-542bb546dfcd.png) | | `tokyonight` | ![image](https://user-images.githubusercontent.com/20955511/183303799-e039b635-5424-437b-9f87-7ed9dca8aea6.png) | | `tokyonight-duo` | ![image](https://user-images.githubusercontent.com/20955511/183303796-03bb6eb2-667f-492b-8397-efd2ad93edeb.png) | | `onedark` | ![image](https://user-images.githubusercontent.com/20955511/183303794-54389af4-24f3-41e6-9d70-2e949d19227e.png) | | `onedark-duo` | ![image](https://user-images.githubusercontent.com/20955511/183303791-a4a6d5f0-ab3a-4f6e-b4cc-a87bb24fd135.png) | | `cobalt` | ![image](https://user-images.githubusercontent.com/20955511/183303787-eaa77366-6f13-4dc8-a0fa-637ac5333612.png) | | `synthwave` | ![image](https://user-images.githubusercontent.com/20955511/183303784-6257055f-d206-4d1a-bdb9-95e9dd7052fb.png) | | `dracula` | ![image](https://user-images.githubusercontent.com/20955511/183303782-2231d9eb-9b65-4cf9-9e26-f4cfb773abf6.png) | | `prussian` | ![image](https://user-images.githubusercontent.com/20955511/183303779-56649d30-2226-4797-b001-0ca1c3902132.png) | | `monokai` | ![image](https://user-images.githubusercontent.com/20955511/183303777-5f424f42-3c71-4802-946d-148dd4a0805f.png) | | `vue` | ![image](https://user-images.githubusercontent.com/20955511/183303773-44ea348d-973b-4d3c-967c-7152bba274d5.png) | | `vue-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303769-0735cf9f-d44c-40ca-b2c1-2b56384670b4.png) | | `shades-of-purple` | ![image](https://user-images.githubusercontent.com/20955511/183303767-30426d56-e2bd-487a-98d7-7e5f5c8eb640.png) | | `nightowl` | ![image](https://user-images.githubusercontent.com/20955511/183303763-289d7a24-070f-4604-b729-8dd75eefe234.png) | | `buefy` | ![image](https://user-images.githubusercontent.com/20955511/183303761-3e0d060a-6a67-407a-9a0a-9c1e615cff87.png) | | `buefy-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303760-df6fcc74-884a-404b-9966-34363a7438b3.png) | | `blue-green` | ![image](https://user-images.githubusercontent.com/20955511/183303758-c8c90e09-db0d-4179-a91f-6463489fee7e.png) | | `algolia` | ![image](https://user-images.githubusercontent.com/20955511/183303756-2b0134af-ab8b-42d4-b805-4e853f929c5e.png) | | `great-gatsby` | ![image](https://user-images.githubusercontent.com/20955511/183303754-168e88f6-80db-443b-b91b-2086b164531b.png) | | `darcula` | ![image](https://user-images.githubusercontent.com/20955511/183303753-4b91b591-4502-4a39-9554-8ed2c7eb9777.png) | | `bear` | ![image](https://user-images.githubusercontent.com/20955511/183303752-5adcd734-3cdb-44f7-8c67-e42edde5ac9c.png) | | `solarized-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303751-b1570958-bb9a-4829-9588-0d94c3fb5cfe.png) | | `solarized-light` | ![image](https://user-images.githubusercontent.com/20955511/183303750-03e52dfd-b052-4acd-aee6-78a1106c147e.png) | | `chartreuse-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303749-1a489c0e-7a53-4fd5-90cd-b1271aca26e3.png) | | `nord` | ![image](https://user-images.githubusercontent.com/20955511/183303748-556b28e8-2f87-4657-b164-899f3216ef51.png) | | `gotham` | ![image](https://user-images.githubusercontent.com/20955511/183303747-bf39ce32-1bdf-4712-b4fd-abd0eb54a89e.png) | | `material-palenight` | ![image](https://user-images.githubusercontent.com/20955511/183303746-e73933e0-03fa-480d-9469-296852be957a.png) | | `graywhite` | ![image](https://user-images.githubusercontent.com/20955511/183303745-185ba0c3-a840-4a4e-95e3-03325c3b3e4e.png) | | `vision-friendly-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303743-0c134e67-aa99-43cb-9a56-3a8b6c9fe44a.png) | | `ayu-mirage` | ![image](https://user-images.githubusercontent.com/20955511/183303742-31e46a33-fb80-4cf4-a966-d751d98a9c93.png) | | `midnight-purple` | ![image](https://user-images.githubusercontent.com/20955511/183303740-641a4a18-da69-46a8-b218-f1a6dc04fcdf.png) | | `calm` | ![image](https://user-images.githubusercontent.com/20955511/183303737-c00375f6-e2bc-4cf5-99c2-1544366fd260.png) | | `flag-india` | ![image](https://user-images.githubusercontent.com/20955511/183303735-66e35638-0fa3-40f4-b9aa-9b6c284eac8f.png) | | `omni` | ![image](https://user-images.githubusercontent.com/20955511/183303734-67e9f9d1-82e5-4518-8105-9105c8a13e6b.png) | | `react` | ![image](https://user-images.githubusercontent.com/20955511/183303733-0d994b10-1fb3-497a-8c8c-7d901dda03ed.png) | | `jolly` | ![image](https://user-images.githubusercontent.com/20955511/183303732-2e877a4e-f609-452d-b091-d5fb48482def.png) | | `maroongold` | ![image](https://user-images.githubusercontent.com/20955511/183303731-08ca9109-551d-4052-a17f-630cbb0cf323.png) | | `yeblu` | ![image](https://user-images.githubusercontent.com/20955511/183303730-5ffad264-362d-4ee6-82b2-15b8a8669462.png) | | `blueberry` | ![image](https://user-images.githubusercontent.com/20955511/183303729-f3c89ba7-efef-437e-9a05-fa5feebb9d72.png) | | `blueberry-duo` | ![image](https://user-images.githubusercontent.com/20955511/183303728-4d209b8c-536f-4921-aa43-6371f1e313fe.png) | | `slateorange` | ![image](https://user-images.githubusercontent.com/20955511/183303727-7ffec3ef-1303-4096-bd0f-f8fc1e4949e6.png) | | `kacho-ga` | ![image](https://user-images.githubusercontent.com/20955511/183303726-9adaaf73-2ea8-4b78-a3f4-7382ce299511.png) | | `ads-juicy-fresh` | ![image](https://user-images.githubusercontent.com/20955511/183303725-25851d72-963a-4532-a5ca-1eaae6c4c224.png) | | `black-ice` | ![image](https://user-images.githubusercontent.com/20955511/183303724-de45e18a-d4f8-48ae-88c1-d54a35d2ecea.png) | | `soft-green` | ![image](https://user-images.githubusercontent.com/20955511/183303722-3ae70df8-87ff-4b3b-a941-f84cef5dddf4.png) | | `blood` | ![image](https://user-images.githubusercontent.com/20955511/183303721-a22ea310-ebab-4ef5-bab9-2f1d7e7c566d.png) | | `blood-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303720-487819af-3c20-4854-8ae1-85d70115cf80.png) | | `green-nur` | ![image](https://user-images.githubusercontent.com/20955511/183303719-dc5ad223-cdd6-4830-9ffb-0ae965ec0159.png) | | `neon-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303718-8b043f5f-8d87-4370-ac42-38032e230d6e.png) | | `neon-palenight` | ![image](https://user-images.githubusercontent.com/20955511/183303716-bf924275-320f-44b6-8ad7-6a5f786ee9e6.png) | | `dark-smoky` | ![image](https://user-images.githubusercontent.com/20955511/183303715-baad8600-943a-4ad6-85d9-f7c2a46eab41.png) | | `monokai-metallian` | ![image](https://user-images.githubusercontent.com/20955511/183303713-2bf8ee11-a251-4d39-8aa5-ed1fd4c545ce.png) | | `city-lights` | ![image](https://user-images.githubusercontent.com/20955511/183303712-c9aa7429-eece-4d03-8c10-fbf28c77d495.png) | | `blux` | ![image](https://user-images.githubusercontent.com/20955511/183303711-ed60bb0e-9392-468b-a344-22debb20613a.png) | | `earth` | ![image](https://user-images.githubusercontent.com/20955511/183303710-b3c336ad-df6d-4529-aa95-6808bfe907dc.png) | | `deepblue` | ![image](https://user-images.githubusercontent.com/20955511/183303709-823b626b-d9c6-4e12-a146-e641a0345a2f.png) | | `holi-theme` | ![image](https://user-images.githubusercontent.com/20955511/183303708-83f5f757-5692-4e24-8e66-daaa8bca6b5b.png) | | `ayu-light` | ![image](https://user-images.githubusercontent.com/20955511/183303707-fb381b09-9963-48c8-90b9-f6b5bc67c85a.png) | | `javascript` | ![image](https://user-images.githubusercontent.com/20955511/183303706-4b4e34ef-6d43-4255-9a58-1d35c3127ff7.png) | | `javascript-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303704-65313140-d66a-4f9b-9ce6-da176ecd6ec7.png) | | `noctis-minimus` | ![image](https://user-images.githubusercontent.com/20955511/183303703-3f774a1e-573c-48a3-a7cd-1f226784d74f.png) | | `github-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303702-1bd5adbb-7277-4610-ad59-e5bdf20dd1de.png) | | `github-dark-blue` | ![image](https://user-images.githubusercontent.com/20955511/183303701-34bf6b33-812d-4afd-9c1f-70b04b2e486a.png) | | `github-light` | ![image](https://user-images.githubusercontent.com/20955511/183303700-7678833c-70c1-4260-8da0-5c8db7b2c38b.png) | | `elegant` | ![image](https://user-images.githubusercontent.com/20955511/183303699-fdd92594-83ca-486f-9ed4-a555f674d59a.png) | | `leafy` | ![image](https://user-images.githubusercontent.com/20955511/183303696-5129d744-af63-4874-bc99-d603ffb03b2e.png) | | `navy-gear` | ![image](https://user-images.githubusercontent.com/20955511/183303695-633ba0b8-11c0-49f3-988d-49390862696a.png) | | `hacker` | ![image](https://user-images.githubusercontent.com/20955511/183303694-e5cd3ee9-2158-41ed-8ad6-20ca7f1298cf.png) | | `garden` | ![image](https://user-images.githubusercontent.com/20955511/183303692-ea99a78d-be75-43fa-80ca-83f3ae454a35.png) | | `github-green-purple` | ![image](https://user-images.githubusercontent.com/20955511/183303691-278ec85a-197d-4a6b-abf3-593e4cc8492b.png) | | `icegray` | ![image](https://user-images.githubusercontent.com/20955511/183303690-7d798870-dd80-4d71-b5c2-775cc3555e14.png) | | `neon-blurange` | ![image](https://user-images.githubusercontent.com/20955511/183303688-7a4ceb50-84e8-47ca-8cf0-14f212227ce6.png) | | `yellowdark` | ![image](https://user-images.githubusercontent.com/20955511/183303687-49da2ffe-5fc9-4a0b-9ca9-c46bc394ec03.png) | | `java-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303686-a652b2fb-daae-4390-b245-71610aa54ef7.png) | | `android-dark` | ![image](https://user-images.githubusercontent.com/20955511/183303685-fed30ead-2660-48bc-b724-04fe3c394c7f.png) | | `deuteranopia-friendly-theme` | ![image](https://user-images.githubusercontent.com/107488620/183304765-9d423ff4-52ed-4a27-8a1c-2bcd290f4803.png) | | `windows-dark` | ![image](https://user-images.githubusercontent.com/103951737/183449796-23096f23-54b5-45af-8078-b8afd4f3baf3.png) | | `git-dark` | ![image](https://user-images.githubusercontent.com/103951737/183690748-060943ff-7b39-4229-b32d-806d654bd12d.png) | | `python-dark` | ![image](https://user-images.githubusercontent.com/103951737/183929763-ae8c93d4-0106-461c-bded-2c2adb0bd6bf.png) | | `sea` | ![image](https://user-images.githubusercontent.com/103951737/184303266-0e5f8a25-bfeb-4876-abf1-91a38ca87680.png) | | `sea-dark` | ![image](https://user-images.githubusercontent.com/103951737/184301879-953370eb-e61a-4e0f-abf4-7029c336e8f1.png) | | `violet-dark` | ![image](https://user-images.githubusercontent.com/103951737/184529784-05de7e57-b939-42f7-9852-345fa191c343.png) | | `horizon` | ![image](https://user-images.githubusercontent.com/3828247/184559656-e1f1b290-0a44-45cc-9681-010577386760.png) | | `material` | ![image](https://user-images.githubusercontent.com/20955511/193617994-dfab039d-b111-4a95-a00d-39517d9e40ab.png) | | `modern-lilac` | ![image](https://user-images.githubusercontent.com/20955511/197569406-6ff144c3-1d6e-4500-9f0b-3112a6c62584.png) | | `modern-lilac2` | ![image](https://user-images.githubusercontent.com/20955511/197575977-029fc730-9c7e-4556-be7c-a727a1715fa7.png) | | `halloween` | ![image](https://user-images.githubusercontent.com/20955511/198897937-a3c918ea-0f35-43a0-9faf-80ad8f254cdf.png) | | `violet-punch` | ![image](https://user-images.githubusercontent.com/20955511/199313653-d678d969-facd-4f8d-b36e-2d0ee2ce61a5.png) | | `submarine-flowers` | ![image](https://user-images.githubusercontent.com/20955511/201519290-14d69c90-ce17-4c63-9020-7b244ebc6fab.png) | | `rising-sun` | ![image](https://user-images.githubusercontent.com/20955511/221126697-2c47639d-23c5-4c23-b545-d883063deebf.png) | | `gruvbox-light` | ![image](https://user-images.githubusercontent.com/20955511/221585454-f9474df6-bbf4-4e3a-91e4-5e9e090e90c0.png) | | `outrun` | ![image](https://user-images.githubusercontent.com/20955511/221585435-d39df945-6387-4e3e-abdf-0af7dd0dabef.png) | | `ocean-dark` | ![image](https://user-images.githubusercontent.com/20955511/221585476-3eb2d25c-346b-4562-808e-bf09a59b17cd.png) | | `discord-old-blurple` | ![image](https://user-images.githubusercontent.com/20955511/221585526-e191cb4c-9957-4ec9-85ec-8916ac691b40.png) | | `aura-dark` | ![image](https://user-images.githubusercontent.com/20955511/221585541-88c2a657-dbe7-47a2-b6f9-9e3cdf1fbbfe.png) | | `panda` | ![image](https://user-images.githubusercontent.com/20955511/221585562-1f7edc63-41c7-43c6-ac33-fd0ecb32ec5f.png) | | `cobalt2` | ![image](https://user-images.githubusercontent.com/20955511/221585614-256d590d-9c45-43a8-be15-48231e418bf2.png) | | `swift` | ![image](https://user-images.githubusercontent.com/20955511/221585640-666641b9-cc29-435c-948f-f50e58a6b330.png) | | `aura` | ![image](https://user-images.githubusercontent.com/20955511/221585659-f4e8a547-7f98-4438-aba9-8f13ffbcc657.png) | | `apprentice` | ![image](https://user-images.githubusercontent.com/20955511/221585690-155c5b01-988e-4e1c-a588-94edb0913800.png) | | `moltack` | ![image](https://user-images.githubusercontent.com/20955511/221585716-9e9a9bb6-17cf-458d-826c-1d9a659cdcec.png) | | `codestackr` | ![image](https://user-images.githubusercontent.com/20955511/221585743-c836e303-9b9a-4caf-bd12-ef83bf39bf54.png) | | `rose-pine` | ![image](https://user-images.githubusercontent.com/20955511/221585761-b7df70e8-b2c4-446a-a6fc-4fd13aa18117.png) | | `date-night` | ![image](https://user-images.githubusercontent.com/20955511/221585779-db7f394d-b3c6-49e4-ad75-bbba97530765.png) | | `one-dark-pro` | ![image](https://user-images.githubusercontent.com/20955511/221585805-1d10928a-286c-4945-95ed-a7317e56692f.png) | | `rose` | ![image](https://user-images.githubusercontent.com/20955511/221585827-e566b73a-e0c0-4711-b48c-667e6500d44e.png) | | `neon` | ![image](https://user-images.githubusercontent.com/20955511/225303106-8c901c48-732e-49ae-a2e6-8733254536eb.png) | | `sunset-gradient` | ![image](https://user-images.githubusercontent.com/20955511/233865257-3ed2bd35-458b-46bc-a189-57b0c8a2a473.png) | | `ocean-gradient` | ![image](https://user-images.githubusercontent.com/20955511/233865264-3bb6c04d-05d2-47b1-857c-3f9a1277651f.png) | | `ambient-gradient` | ![image](https://user-images.githubusercontent.com/20955511/233865269-81583e73-c9b6-4e4b-9475-bc130de1bfdd.png) | | `catppuccin-latte` | ![image](https://user-images.githubusercontent.com/85760664/248204601-358a8a31-4ffc-4535-a617-840926ecd4f0.png) | | `catppuccin-frappe` | ![image](https://user-images.githubusercontent.com/85760664/248204858-daa7bd60-1e83-4b4e-8afc-65644055235e.png) | | `catppuccin-macchiato` | ![image](https://user-images.githubusercontent.com/85760664/248205012-15d74ba2-746a-4efd-b2f5-bc2db87b7c10.png) | | `catppuccin-mocha` | ![image](https://user-images.githubusercontent.com/85760664/248204228-9f965d12-2013-48c9-b3a8-e9717b1c4e43.png) | | `burnt-neon` | ![image](https://user-images.githubusercontent.com/112064697/250343082-de641726-1200-4264-885a-154d539cfc3f.png) | | `humoris` | ![image](https://user-images.githubusercontent.com/20955511/263020536-793bedbd-cca6-47e5-92dc-c7b38ab05bce.png) | | `shadow-red` | ![image](https://user-images.githubusercontent.com/86386385/263407052-345edfdf-b6ee-4b53-a4c4-7dcb4948f6dc.png) | | `shadow-green` | ![image](https://user-images.githubusercontent.com/86386385/263407047-d769c2cf-e435-4d46-9a34-04c16f61d200.png) | | `shadow-blue` | ![image](https://user-images.githubusercontent.com/86386385/263407038-bdcd2ed9-4d2c-4a46-b8df-1b989ee517f5.png) | | `shadow-orange` | ![image](https://user-images.githubusercontent.com/86386385/263406777-07fd919b-7b4f-4fa9-ac47-3ebd0602a80b.png) | | `shadow-purple` | ![image](https://user-images.githubusercontent.com/86386385/263406551-46e14eac-fdbc-4b90-9df8-85c0bd1eeb41.png) | | `shadow-brown` | ![image](https://user-images.githubusercontent.com/86386385/263406156-5e17541d-4dcf-4315-b68d-d36c95d53767.png) | | `github-dark-dimmed` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/b29e3fe2-86ca-4bf5-81ce-9f6187b02c99) | | `blue-navy` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/29a78acd-56e8-465d-aff0-f984ecc14423) | | `calm-pink` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/7a789c2c-33d7-41a9-8a6e-034bbfe3e915) | | `whatsapp-light` | ![image](https://user-images.githubusercontent.com/86386385/266839259-1fe6a2b7-d2f2-46b0-b94d-397ff3f2a95a.png) | | `whatsapp-dark` | ![image](https://user-images.githubusercontent.com/86386385/266839261-d9a4a98c-ef9f-45ab-a3d6-1dca785225c3.png) | | `carbonfox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/a26f8086-91de-49d7-83ca-8453cd031e72) | | `dawnfox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/feff8dd8-d7c0-4d1d-9a84-129f1333a9e7) | | `dayfox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/74111bcb-9825-4d26-a2c8-abec3618274f) | | `duskfox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/8dfd700c-e391-4ba0-a434-db4d4455000d) | | `nightfox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/00ab9a73-67d6-430f-8b22-da49a3e49091) | | `nordfox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/6feef268-ed8f-4d60-bbd8-f7a0a7a58ce8) | | `terafox` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/ef943ced-365f-4ce5-965a-a9499ce1d8e1) | | `iceberg` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/912d8f6a-ba21-4668-9109-300c67a1f1c2) | | `whatsapp-light2` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/45d22825-e71b-42c7-aabf-14f50d47beef) | | `whatsapp-dark2` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/86386385/4b41e537-368f-4f67-a1e6-81ca757ce5f7) | | `travelers-theme` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/45b0bb8c-fb88-4f2e-ad97-665db6bce4a7) | | `youtube-dark` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/62086478/6f774511-2477-46d2-b7bd-de3a57a3ca78) | | `meta-light` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/105522342/c9429386-0b15-4efc-9bf0-c67f4aec05d4) | | `meta-dark` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/105522342/62119e5a-29fc-4285-ac5d-4125c49dff8c) | | `dark-minimalist` | ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/77511070/11ba7899-1ad3-4c4b-880b-6f9e7c285f1b) | | `telegram` | ![image](https://github.com/user-attachments/assets/59a5d9d5-8a2a-4916-aa46-a0a49a6f0372) | | `taiga` | ![image](https://github.com/user-attachments/assets/be4e961d-a13e-401a-90f8-f2b062a8c0f9) | | `telegram-gradient` | ![image](https://github.com/user-attachments/assets/985c3e04-a5dd-4cba-a66e-d43ad9668af0) | | `microsoft` | ![image](https://github.com/user-attachments/assets/4c2cce9d-90b5-4e38-8422-656c5a78b4d9) | | `microsoft-dark` | ![image](https://github.com/user-attachments/assets/a5918d7d-f568-4012-b06f-d9cfacaece04) | | `hacker-inverted` | ![image](https://github.com/user-attachments/assets/b64c136a-827b-4177-98f9-28db59bba0ef) | | `rust-ferris-light` | ![image](https://github.com/user-attachments/assets/2e1d175f-c39d-4e56-be41-d9c277f1e83a) | | `rust-ferris-dark` | ![image](https://github.com/user-attachments/assets/05e3f9ac-708d-415d-990f-ede3d0a84bab) | | `cyber-streakglow` | ![image](https://github.com/user-attachments/assets/8c6108e1-f3a1-4653-9f68-08ed6dcfc498) | | `vitesse` | ![image](https://github.com/user-attachments/assets/baa2fa20-36ea-4158-befc-79c21f102f87) | | `nord-aurora` | ![image](https://github.com/user-attachments/assets/d61bf5c3-66f2-4c02-bd9d-30bf1be47c97) | | `dark-aura` | ![Image](https://github.com/user-attachments/assets/14889d0e-26db-4fa6-8026-6312c9b4636e) | | `everforest-dark` | ![image](https://github.com/user-attachments/assets/45a4e0a0-d330-4233-9d76-89003e59bb31) | | `everforest-light` | ![image](https://github.com/user-attachments/assets/592466c0-5a67-48cc-adf0-f8a21ca891b6) | | `oceanic-next` | ![image](https://github.com/user-attachments/assets/e0182770-a511-42b6-a40b-644317268a0f) | | `kanagawa-paper` | ![image](https://github.com/user-attachments/assets/541a521d-a6a8-4b55-ab79-7b1a9bdb092c) | | `sakura-x` | ![image](https://github.com/user-attachments/assets/65360cfa-9d5e-42f2-b3c9-cc2815623413) | ### Can't find the theme you like? You can now customize your stats card with the interactive [Demo Site](https://streak-stats.demolab.com/demo/) or by customizing the [url parameters](/README.md#-options). If you would like to share your theme with others, feel free to open an issue/pull request! Note: When submitting a new theme, make sure the name is all lowercase. Hyphens are allowed between words, but there should be no underscores. On the demo site, you can export a list of colors from the advanced section by clicking "Export to PHP". ================================================ FILE: package.json ================================================ { "engines": { "node": "22.x" }, "devDependencies": { "@prettier/plugin-php": "^0.24.0", "prettier": "^3.6.2" } } ================================================ FILE: scripts/translation-progress.php ================================================ $phrases) { // skip aliases if (is_string($phrases)) { continue; } $translated = 0; foreach ($phrases_to_translate as $phrase) { if (isset($phrases[$phrase])) { $translated++; } } $percentage = round(($translated / count($phrases_to_translate)) * 100); $locale_name = Locale::getDisplayName($locale, $locale); $line_number = getLineNumber($translations_file, $locale); $progress[$locale] = [ "locale" => $locale, "locale_name" => $locale_name, "percentage" => $percentage, "line_number" => $line_number, ]; } // sort by percentage uasort($progress, function ($a, $b) { return $b["percentage"] <=> $a["percentage"]; }); return $progress; } /** * Get the line number of the locale in the translations file * * @param array $translations_file The translations file * @param string $locale The locale * @return int The line number of the locale in the translations file */ function getLineNumber(array $translations_file, string $locale): int { return key(preg_grep("/^\\s*\"$locale\"\\s*=>\\s*\\[/", $translations_file)) + 1; } /** * Convert progress to labeled badges * * @param array $progress The progress array * @return string The markdown for the image badges */ function progressToBadges(array $progress): string { $per_row = 5; $table = ""; $i = 0; foreach (array_values($progress) as $data) { if ($i % $per_row === 0) { $table .= ""; } $line_url = "https://github.com/DenverCoder1/github-readme-streak-stats/blob/main/src/translations.php#L{$data["line_number"]}"; $table .= ""; $i++; if ($i % $per_row === 0) { $table .= ""; } } if ($i % $per_row !== 0) { while ($i % $per_row !== 0) { $table .= ""; $i++; } $table .= ""; } $table .= "
{$data["locale"]} - {$data["locale_name"]}
\"{$data["locale_name"]}
\n"; return $table; } /** * Update readme by replacing the content between the start and end markers * * @param string $path The path to the readme file * @param string $start The start marker * @param string $end The end marker * @param string $content The content to replace the content between the start and end markers * @return int|false The number of bytes that were written to the file, or false on failure */ function updateReadme(string $path, string $start, string $end, string $content): int|false { $readme = file_get_contents($path); if (strpos($readme, $start) === false || strpos($readme, $end) === false) { throw new Exception("Start or end marker not found in readme"); } $start_pos = strpos($readme, $start) + strlen($start); $end_pos = strpos($readme, $end); $length = $end_pos - $start_pos; $readme = substr_replace($readme, $content, $start_pos, $length); return file_put_contents($path, $readme); } $progress = getProgress($GLOBALS["TRANSLATIONS"]); $badges = "\n" . progressToBadges($progress); $update = updateReadme( __DIR__ . "/../README.md", "", "", $badges, ); exit($update === false ? 1 : 0); ================================================ FILE: src/cache.php ================================================ $user, "options" => $options], JSON_THROW_ON_ERROR); } catch (JsonException $e) { // Fallback to simple concatenation if JSON encoding fails error_log("Cache key JSON encoding failed: " . $e->getMessage()); $keyData = $user . serialize($options); } return hash("sha256", $keyData); } /** * Get the cache file path for a given key * * @param string $key Cache key * @return string Full path to cache file */ function getCacheFilePath(string $key): string { return CACHE_DIR . "/" . $key . ".json"; } /** * Ensure the cache directory exists * * @return bool True if directory exists or was created */ function ensureCacheDir(): bool { if (!is_dir(CACHE_DIR)) { return mkdir(CACHE_DIR, 0755, true); } return true; } /** * Get cached stats if available and not expired * * @param string $user GitHub username * @param array $options Additional options * @param int $maxAge Maximum age in seconds (default: 24 hours) * @return array|null Cached stats array or null if not cached/expired */ function getCachedStats(string $user, array $options = [], int $maxAge = CACHE_DURATION): ?array { $key = getCacheKey($user, $options); $filePath = getCacheFilePath($key); if (!file_exists($filePath)) { return null; } $mtime = filemtime($filePath); if ($mtime === false) { return null; } $fileAge = time() - $mtime; if ($fileAge > $maxAge) { unlink($filePath); return null; } $handle = fopen($filePath, "r"); if ($handle === false) { return null; } if (!flock($handle, LOCK_SH)) { fclose($handle); return null; } $contents = stream_get_contents($handle); flock($handle, LOCK_UN); fclose($handle); if ($contents === false || $contents === "") { return null; } $data = json_decode($contents, true); if (!is_array($data)) { return null; } return $data; } /** * Save stats to cache * * @param string $user GitHub username * @param array $options Additional options * @param array $stats Stats array to cache * @return bool True if successfully cached */ function setCachedStats(string $user, array $options, array $stats): bool { if (!ensureCacheDir()) { error_log("Failed to create cache directory: " . CACHE_DIR); return false; } $key = getCacheKey($user, $options); $filePath = getCacheFilePath($key); $data = json_encode($stats); if ($data === false) { error_log("Failed to encode stats to JSON for user: " . $user); return false; } $result = file_put_contents($filePath, $data, LOCK_EX); if ($result === false) { error_log("Failed to write cache file: " . $filePath); return false; } return true; } /** * Clear all expired cache files * * @param int $maxAge Maximum age in seconds * @return int Number of files deleted */ function clearExpiredCache(int $maxAge = CACHE_DURATION): int { if (!is_dir(CACHE_DIR)) { return 0; } $deleted = 0; $files = glob(CACHE_DIR . "/*.json"); if ($files === false) { return 0; } foreach ($files as $file) { $mtime = filemtime($file); if ($mtime === false) { continue; } $fileAge = time() - $mtime; if ($fileAge > $maxAge) { if (unlink($file)) { $deleted++; } } } return $deleted; } /** * Clear cache for a specific user * * Note: This function only clears the cache for the user with empty/default options. * Cache entries with non-empty options (starting_year, mode, exclude_days) will NOT * be cleared. This is a limitation of the hash-based cache key system - we cannot * enumerate all possible option combinations without storing additional metadata. * * @param string $user GitHub username * @return bool True if cache was cleared (or didn't exist) */ function clearUserCache(string $user): bool { if (!is_dir(CACHE_DIR)) { return true; } $key = getCacheKey($user, []); $filePath = getCacheFilePath($key); if (file_exists($filePath)) { return unlink($filePath); } return true; } ================================================ FILE: src/card.php ================================================ getBestPattern("MMM d"); $dateFormatter = new IntlDateFormatter( $locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, pattern: $pattern, ); $formatted = $dateFormatter->format($date); } } // otherwise, display month, day, and year else { if ($format) { // remove brackets, but leave text within them $formatted = date_format($date, str_replace(["[", "]"], "", $format)); } else { // format with year using locale $pattern = $patternGenerator->getBestPattern("yyyy MMM d"); $dateFormatter = new IntlDateFormatter( $locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, pattern: $pattern, ); $formatted = $dateFormatter->format($date); } } // sanitize and return formatted date return htmlspecialchars($formatted); } /** * Translate days of the week * * Takes a list of days (eg. ["Sun", "Mon", "Sat"]) and returns the short abbreviation of the days of the week in another locale * e.g. ["Sun", "Mon", "Sat"] -> ["dim", "lun", "sam"] * * @param array $days List of days to translate * @param string $locale Locale code * * @return array Translated days */ function translateDays(array $days, string $locale): array { if ($locale === "en") { return $days; } $patternGenerator = new IntlDatePatternGenerator($locale); $pattern = $patternGenerator->getBestPattern("EEE"); $dateFormatter = new IntlDateFormatter( $locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, pattern: $pattern, ); $translatedDays = []; foreach ($days as $day) { $translatedDays[] = $dateFormatter->format(new DateTime($day)); } return $translatedDays; } /** * Get the excluding days text * * @param array $excludedDays List of excluded days * @param array $localeTranslations Translations for the locale * @param string $localeCode Locale code * @return string Excluding days text */ function getExcludingDaysText($excludedDays, $localeTranslations, $localeCode) { $separator = $localeTranslations["comma_separator"] ?? ", "; $daysCommaSeparated = implode($separator, translateDays($excludedDays, $localeCode)); return str_replace("{days}", $daysCommaSeparated, $localeTranslations["Excluding {days}"]); } /** * Normalize a theme name * * @param string $theme Theme name * @return string Normalized theme name */ function normalizeThemeName(string $theme): string { return strtolower(str_replace("_", "-", $theme)); } /** * Check theme and color customization parameters to generate a theme mapping * * @param array $params Request parameters * @return array The chosen theme or default */ function getRequestedTheme(array $params): array { /** * @var array> $THEMES * List of theme names mapped to labeled colors */ $THEMES = include "themes.php"; /** * @var array $CSS_COLORS * List of valid CSS colors */ $CSS_COLORS = include "colors.php"; // normalize theme name $selectedTheme = normalizeThemeName($params["theme"] ?? "default"); // get theme colors, or default colors if theme not found $theme = $THEMES[$selectedTheme] ?? $THEMES["default"]; // personal theme customizations $properties = array_keys($theme); foreach ($properties as $prop) { // check if each property was passed as a parameter if (isset($params[$prop])) { // ignore case $param = strtolower($params[$prop]); // check if color is valid hex color (3, 4, 6, or 8 hex digits) if (preg_match("/^([a-f0-9]{3}|[a-f0-9]{4}|[a-f0-9]{6}|[a-f0-9]{8})$/", $param)) { // set property $theme[$prop] = "#" . $param; } // check if color is valid css color elseif (in_array($param, $CSS_COLORS)) { // set property $theme[$prop] = $param; } // if the property is background gradient is allowed (angle,start_color,...,end_color) elseif ($prop == "background" && preg_match("/^-?[0-9]+,[a-f0-9]{3,8}(,[a-f0-9]{3,8})+$/", $param)) { // set property $theme[$prop] = $param; } } } // hide borders if (isset($params["hide_border"]) && $params["hide_border"] == "true") { $theme["border"] = "#0000"; // transparent } // set background $gradient = ""; $backgroundParts = explode(",", $theme["background"] ?? ""); if (count($backgroundParts) >= 3) { $theme["background"] = "url(#gradient)"; $gradient = ""; $backgroundColors = array_slice($backgroundParts, 1); $colorCount = count($backgroundColors); for ($index = 0; $index < $colorCount; $index++) { $offset = ($index * 100) / ($colorCount - 1); $gradient .= ""; } $gradient .= ""; } $theme["backgroundGradient"] = $gradient; return $theme; } /** * Wraps a string to a given number of characters * * Similar to `wordwrap()`, but uses regex and does not break with certain non-ascii characters * * @param string $string The input string * @param int $width The number of characters at which the string will be wrapped * @param string $break The line is broken using the optional `break` parameter * @param bool $cut_long_words If the `cut_long_words` parameter is set to true, the string is * the string is always wrapped at or before the specified width. So if you have * a word that is larger than the given width, it is broken apart. * When false the function does not split the word even if the width is smaller * than the word width. * @return string The given string wrapped at the specified length */ function utf8WordWrap(string $string, int $width = 75, string $break = "\n", bool $cut_long_words = false): string { // match anything 1 to $width chars long followed by whitespace or EOS $string = preg_replace("/(.{1,$width})(?:\s|$)/uS", "$1$break", $string); // split words that are too long after being broken up if ($cut_long_words) { $string = preg_replace("/(\S{" . $width . "})(?=\S)/u", "$1$break", $string); } // trim any trailing line breaks return rtrim($string, $break); } /** * Get the length of a string with utf8 characters * * Similar to `strlen()`, but uses regex and does not break with certain non-ascii characters * * @param string $string The input string * @return int The length of the string */ function utf8Strlen(string $string): int { return preg_match_all("/./us", $string, $matches); } /** * Split lines of text using elements if it contains a newline or exceeds a maximum number of characters * * @param string $text Text to split * @param int $maxChars Maximum number of characters per line * @param int $line1Offset Offset for the first line * @return string Original text if one line, or split text with elements */ function splitLines(string $text, int $maxChars, int $line1Offset): string { // if too many characters, insert \n before a " " or "-" if possible if ($maxChars > 0 && utf8Strlen($text) > $maxChars && strpos($text, "\n") === false) { // prefer splitting at " - " if possible if (strpos($text, " - ") !== false) { $text = str_replace(" - ", "\n- ", $text); } // otherwise, use word wrap to split at spaces else { $text = utf8WordWrap($text, $maxChars, "\n", true); } } $text = htmlspecialchars($text); return preg_replace( "/^(.*)\n(.*)/", "$1$2", $text, ); } /** * Normalize a locale code * * @param string $localeCode Locale code * @return string Normalized locale code */ function normalizeLocaleCode(string $localeCode): string { preg_match("/^([a-z]{2,3})(?:[_-]([a-z]{4}))?(?:[_-]([0-9]{3}|[a-z]{2}))?$/i", $localeCode, $matches); if (empty($matches)) { return "en"; } $language = $matches[1]; $script = $matches[2] ?? ""; $region = $matches[3] ?? ""; // convert language to lowercase $language = strtolower($language); // convert script to title case $script = ucfirst(strtolower($script)); // convert region to uppercase $region = strtoupper($region); // combine language, script, and region using underscores return implode("_", array_filter([$language, $script, $region])); } /** * Get the translations for a locale code after normalizing it * * @param string $localeCode Locale code * @return array Translations for the locale code */ function getTranslations(string $localeCode): array { // normalize locale code $localeCode = normalizeLocaleCode($localeCode); // get the labels from the translations file $translations = include "translations.php"; // if the locale does not exist, try without the script and region if (!isset($translations[$localeCode])) { $localeCode = explode("_", $localeCode)[0]; } // get the translations for the locale or empty array if it does not exist $localeTranslations = $translations[$localeCode] ?? []; // if the locale returned is a string, it is an alias for another locale if (is_string($localeTranslations)) { // get the translations for the alias $localeTranslations = $translations[$localeTranslations]; } // fill in missing translations with English $localeTranslations += $translations["en"]; // return the translations return $localeTranslations; } /** * Get the card width from params taking into account minimum and default values * * @param array $params Request parameters * @param int $numColumns Number of columns in the card * @return int Card width */ function getCardWidth(array $params, int $numColumns = 3): int { $defaultWidth = 495; $minimumWidth = 100 * $numColumns; return max($minimumWidth, intval($params["card_width"] ?? $defaultWidth)); } /** * Get the card height from params taking into account minimum and default values * * @param array $params Request parameters * @return int Card width */ function getCardHeight(array $params): int { $defaultHeight = 195; $minimumHeight = 170; return max($minimumHeight, intval($params["card_height"] ?? $defaultHeight)); } /** * Format number using locale and short number if requested * * @param float $num The number to format * @param string $localeCode Locale code * @param bool $useShortNumbers Whether to use short numbers * @return string The formatted number */ function formatNumber(float $num, string $localeCode, bool $useShortNumbers): string { $numFormatter = new NumberFormatter($localeCode, NumberFormatter::DECIMAL); $suffix = ""; if ($useShortNumbers) { $units = ["", "K", "M", "B", "T"]; for ($i = 0; $num >= 1000; $i++) { $num /= 1000; } $suffix = $units[$i]; $num = round($num, 1); } return $numFormatter->format($num) . $suffix; } /** * Generate SVG output for a stats array * * @param array $stats Streak stats * @param array|NULL $params Request parameters * @return string The generated SVG Streak Stats card * * @throws InvalidArgumentException If a locale does not exist */ function generateCard(array $stats, array $params = null): string { $params = $params ?? $_REQUEST; // get requested theme $theme = getRequestedTheme($params); // get requested locale, default to English $localeCode = $params["locale"] ?? "en"; $localeTranslations = getTranslations($localeCode); // whether the locale is right-to-left $direction = $localeTranslations["rtl"] ?? false ? "rtl" : "ltr"; // get date format // locale date formatter (used only if date_format is not specified) $dateFormat = $params["date_format"] ?? ($localeTranslations["date_format"] ?? null); // read border_radius parameter, default to 4.5 if not set $borderRadius = $params["border_radius"] ?? 4.5; $showTotalContributions = ($params["hide_total_contributions"] ?? "") !== "true"; $showCurrentStreak = ($params["hide_current_streak"] ?? "") !== "true"; $showLongestStreak = ($params["hide_longest_streak"] ?? "") !== "true"; $numColumns = intval($showTotalContributions) + intval($showCurrentStreak) + intval($showLongestStreak); $cardWidth = getCardWidth($params, $numColumns); $rectWidth = $cardWidth - 1; $columnWidth = $numColumns > 0 ? $cardWidth / $numColumns : 0; $cardHeight = getCardHeight($params); $rectHeight = $cardHeight - 1; $heightOffset = ($cardHeight - 195) / 2; // X offsets for the bars between columns $barOffsets = [-999, -999]; for ($i = 0; $i < $numColumns - 1; $i++) { $barOffsets[$i] = $columnWidth * ($i + 1); } // offsets for the text in each column $columnOffsets = []; for ($i = 0; $i < $numColumns; $i++) { $columnOffsets[] = $columnWidth / 2 + $columnWidth * $i; } // reverse the column offsets if the locale is right-to-left if ($direction === "rtl") { $columnOffsets = array_reverse($columnOffsets); } $nextColumnIndex = 0; $totalContributionsOffset = $showTotalContributions ? $columnOffsets[$nextColumnIndex++] : -999; $currentStreakOffset = $showCurrentStreak ? $columnOffsets[$nextColumnIndex++] : -999; $longestStreakOffset = $showLongestStreak ? $columnOffsets[$nextColumnIndex++] : -999; // Y offsets for the bars $barHeightOffsets = [28 + $heightOffset / 2, 170 + $heightOffset]; // Y offsets for the numbers and dates $longestStreakHeightOffset = $totalContributionsHeightOffset = [ 48 + $heightOffset, 84 + $heightOffset, 114 + $heightOffset, ]; $currentStreakHeightOffset = [ 48 + $heightOffset, 108 + $heightOffset, 145 + $heightOffset, 71 + $heightOffset, 19.5 + $heightOffset, ]; $useShortNumbers = ($params["short_numbers"] ?? "") === "true"; // total contributions $totalContributions = formatNumber($stats["totalContributions"], $localeCode, $useShortNumbers); $firstContribution = formatDate($stats["firstContribution"], $dateFormat, $localeCode); $totalContributionsRange = $firstContribution . " - " . $localeTranslations["Present"]; // current streak $currentStreak = formatNumber($stats["currentStreak"]["length"], $localeCode, $useShortNumbers); $currentStreakStart = formatDate($stats["currentStreak"]["start"], $dateFormat, $localeCode); $currentStreakEnd = formatDate($stats["currentStreak"]["end"], $dateFormat, $localeCode); $currentStreakRange = $currentStreakStart; if ($currentStreakStart != $currentStreakEnd) { $currentStreakRange .= " - " . $currentStreakEnd; } // longest streak $longestStreak = formatNumber($stats["longestStreak"]["length"], $localeCode, $useShortNumbers); $longestStreakStart = formatDate($stats["longestStreak"]["start"], $dateFormat, $localeCode); $longestStreakEnd = formatDate($stats["longestStreak"]["end"], $dateFormat, $localeCode); $longestStreakRange = $longestStreakStart; if ($longestStreakStart != $longestStreakEnd) { $longestStreakRange .= " - " . $longestStreakEnd; } // if the translations contain over max characters or a newline, split the text into two tspan elements $maxCharsPerLineLabels = $numColumns > 0 ? intval(floor($cardWidth / $numColumns / 7.5)) : 0; $totalContributionsText = splitLines($localeTranslations["Total Contributions"], $maxCharsPerLineLabels, -9); if ($stats["mode"] === "weekly") { $currentStreakText = splitLines($localeTranslations["Week Streak"], $maxCharsPerLineLabels, -9); $longestStreakText = splitLines($localeTranslations["Longest Week Streak"], $maxCharsPerLineLabels, -9); } else { $currentStreakText = splitLines($localeTranslations["Current Streak"], $maxCharsPerLineLabels, -9); $longestStreakText = splitLines($localeTranslations["Longest Streak"], $maxCharsPerLineLabels, -9); } // if the ranges contain over max characters, split the text into two tspan elements $maxCharsPerLineDates = $numColumns > 0 ? intval(floor($cardWidth / $numColumns / 6)) : 0; $totalContributionsRange = splitLines($totalContributionsRange, $maxCharsPerLineDates, 0); $currentStreakRange = splitLines($currentStreakRange, $maxCharsPerLineDates, 0); $longestStreakRange = splitLines($longestStreakRange, $maxCharsPerLineDates, 0); // if days are excluded, add a note to the corner $excludedDays = ""; if (!empty($stats["excludedDays"])) { $offset = $direction === "rtl" ? $cardWidth - 5 : 5; $excludingDaysText = getExcludingDaysText($stats["excludedDays"], $localeTranslations, $localeCode); $excludedDays = " * {$excludingDaysText} "; } return " {$theme["backgroundGradient"]} {$totalContributions} {$totalContributionsText} {$totalContributionsRange} {$currentStreakText} {$currentStreakRange} {$currentStreak} {$longestStreak} {$longestStreakText} {$longestStreakRange} {$excludedDays} "; } /** * Generate SVG displaying an error message * * @param string $message The error message to display * @param array|NULL $params Request parameters * @return string The generated SVG error card */ function generateErrorCard(string $message, array $params = null): string { $params = $params ?? $_REQUEST; // get requested theme, use $_REQUEST if no params array specified $theme = getRequestedTheme($params); // read border_radius parameter, default to 4.5 if not set $borderRadius = $params["border_radius"] ?? 4.5; // read card_width parameter $cardWidth = getCardWidth($params); $rectWidth = $cardWidth - 1; $centerOffset = $cardWidth / 2; // read card_height parameter $cardHeight = getCardHeight($params); $rectHeight = $cardHeight - 1; $heightOffset = ($cardHeight - 195) / 2; $errorLabelOffset = $cardHeight / 2 + 10.5; return " {$theme["backgroundGradient"]} {$message} "; } /** * Remove animations from SVG * * @param string $svg The SVG for the card as a string * @return string The SVG without animations */ function removeAnimations(string $svg): string { $svg = preg_replace("/(