From 3388b8c6ed8fb4179b0fba4ad5892125a2b3bf55 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1s=20L=C3=B3pez?=
Date: Sat, 25 May 2024 12:28:05 +0200
Subject: [PATCH] first commit
---
.editorconfig | 18 ++
.gitattributes | 14 +
.github/FUNDING.yml | 1 +
.github/workflows/formats.yml | 49 ++++
.github/workflows/tests.yml | 45 +++
.gitignore | 9 +
CHANGELOG.md | 4 +
CONTRIBUTING.md | 38 +++
LICENSE.md | 21 ++
README.md | 270 ++++++++++++++++++
composer.json | 66 +++++
docs/colority.png | Bin 0 -> 22608 bytes
phpstan.neon.dist | 6 +
phpunit.xml.dist | 16 ++
pint.json | 58 ++++
rector.php | 24 ++
src/Colors/Color.php | 23 ++
src/Colors/HexColor.php | 48 ++++
src/Colors/HslColor.php | 110 +++++++
src/Colors/RgbColor.php | 109 +++++++
src/Concerns/ResolvesContrastRatioColor.php | 59 ++++
src/Contracts/TransformableColor.php | 18 ++
src/Contracts/ValueColorParser.php | 20 ++
src/Services/ColorityManager.php | 107 +++++++
src/Support/Algorithms/ContrastRatioScore.php | 63 ++++
.../Algorithms/LuminosityContrastRatio.php | 70 +++++
src/Support/ColorityAlias.php | 12 +
src/Support/Facades/Colority.php | 32 +++
src/Support/Parsers/HexValueColorParser.php | 42 +++
src/Support/Parsers/HslValueColorParser.php | 39 +++
src/Support/Parsers/RgbValueColorParser.php | 39 +++
src/Support/ValueColorParserResolver.php | 52 ++++
tests/ArchTest.php | 15 +
tests/Colors/HexColorTest.php | 48 ++++
tests/Colors/HslColorTest.php | 52 ++++
tests/Colors/RgbColorTest.php | 27 ++
.../ResolvesContrastRatioColorTest.php | 118 ++++++++
tests/Services/ColorityManagerTest.php | 142 +++++++++
.../LuminosityContrastRatioTest.php | 32 +++
tests/Support/ColorityAliasTest.php | 9 +
tests/Support/Facades/ColorityTest.php | 13 +
41 files changed, 1938 insertions(+)
create mode 100644 .editorconfig
create mode 100644 .gitattributes
create mode 100644 .github/FUNDING.yml
create mode 100644 .github/workflows/formats.yml
create mode 100644 .github/workflows/tests.yml
create mode 100644 .gitignore
create mode 100644 CHANGELOG.md
create mode 100644 CONTRIBUTING.md
create mode 100755 LICENSE.md
create mode 100644 README.md
create mode 100644 composer.json
create mode 100644 docs/colority.png
create mode 100644 phpstan.neon.dist
create mode 100644 phpunit.xml.dist
create mode 100644 pint.json
create mode 100644 rector.php
create mode 100644 src/Colors/Color.php
create mode 100644 src/Colors/HexColor.php
create mode 100644 src/Colors/HslColor.php
create mode 100644 src/Colors/RgbColor.php
create mode 100644 src/Concerns/ResolvesContrastRatioColor.php
create mode 100644 src/Contracts/TransformableColor.php
create mode 100644 src/Contracts/ValueColorParser.php
create mode 100644 src/Services/ColorityManager.php
create mode 100644 src/Support/Algorithms/ContrastRatioScore.php
create mode 100644 src/Support/Algorithms/LuminosityContrastRatio.php
create mode 100644 src/Support/ColorityAlias.php
create mode 100644 src/Support/Facades/Colority.php
create mode 100644 src/Support/Parsers/HexValueColorParser.php
create mode 100644 src/Support/Parsers/HslValueColorParser.php
create mode 100644 src/Support/Parsers/RgbValueColorParser.php
create mode 100644 src/Support/ValueColorParserResolver.php
create mode 100644 tests/ArchTest.php
create mode 100644 tests/Colors/HexColorTest.php
create mode 100644 tests/Colors/HslColorTest.php
create mode 100644 tests/Colors/RgbColorTest.php
create mode 100644 tests/Concerns/ResolvesContrastRatioColorTest.php
create mode 100644 tests/Services/ColorityManagerTest.php
create mode 100644 tests/Support/Algorithms/LuminosityContrastRatioTest.php
create mode 100644 tests/Support/ColorityAliasTest.php
create mode 100644 tests/Support/Facades/ColorityTest.php
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..26fa993
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+; This file is for unifying the coding style for different editors and IDEs.
+; More information at http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.yml]
+indent_size = 2
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3ee7ee0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,14 @@
+/docs export-ignore
+/tests export-ignore
+/scripts export-ignore
+/.github export-ignore
+/.php_cs export-ignore
+.editorconfig export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+phpstan.neon.dist export-ignore
+phpunit.xml.dist export-ignore
+rector.php export-ignore
+CHANGELOG.md export-ignore
+CONTRIBUTING.md export-ignore
+README.md export-ignore
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..a1f77c4
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://www.paypal.com/paypalme/tomloprod
diff --git a/.github/workflows/formats.yml b/.github/workflows/formats.yml
new file mode 100644
index 0000000..393394e
--- /dev/null
+++ b/.github/workflows/formats.yml
@@ -0,0 +1,49 @@
+name: Formats
+
+on: ['push', 'pull_request']
+
+jobs:
+ ci:
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.2]
+ dependency-version: [prefer-lowest, prefer-stable]
+
+ name: Formats P${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
+
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: dom, mbstring, zip
+ coverage: pcov
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ shell: bash
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: dependencies-php-${{ matrix.php }}-os-${{ matrix.os }}-version-${{ matrix.dependency-version }}-composer-${{ hashFiles('composer.json') }}
+ restore-keys: dependencies-php-${{ matrix.php }}-os-${{ matrix.os }}-version-${{ matrix.dependency-version }}-composer-
+
+ - name: Install Composer dependencies
+ run: composer update --${{ matrix.dependency-version }} --no-interaction --prefer-dist
+
+ - name: Coding Style Checks
+ run: composer test:lint
+
+ - name: Type Checks
+ run: composer test:types
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..50a3090
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,45 @@
+name: Tests
+
+on: ['push', 'pull_request']
+
+jobs:
+ ci:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ php: [8.2, 8.3]
+ dependency-version: [prefer-lowest, prefer-stable]
+
+ name: Tests P${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }}
+
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: dom, mbstring, zip
+ coverage: none
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ shell: bash
+ run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: dependencies-php-${{ matrix.php }}-os-${{ matrix.os }}-version-${{ matrix.dependency-version }}-composer-${{ hashFiles('composer.json') }}
+ restore-keys: dependencies-php-${{ matrix.php }}-os-${{ matrix.os }}-version-${{ matrix.dependency-version }}-composer-
+
+ - name: Install Composer dependencies
+ run: composer update --${{ matrix.dependency-version }} --no-interaction --prefer-dist
+
+ - name: Integration Tests
+ run: php ./vendor/bin/pest
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f288c8c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/.phpunit.result.cache
+/.phpunit.cache
+/.php-cs-fixer.cache
+/.php-cs-fixer.php
+/composer.lock
+/phpunit.xml
+/vendor/
+*.swp
+*.swo
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f88947c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+## Version 1.0.0
+> XX May, 2024
+
+- First Colority version
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d46d0d9
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,38 @@
+# 🧑🤝🧑 Contributing
+
+Contributions are welcome, and are accepted via pull requests.
+Please review these guidelines before submitting any pull requests.
+
+## Process
+
+1. Fork the project
+1. Create a new branch
+1. Code, test, commit and push
+1. Open a pull request detailing your changes.
+
+## Guidelines
+
+Colority uses a few tools to ensure the code quality and consistency. [Pest](https://pestphp.com) is the testing framework of choice, and we also use [PHPStan](https://phpstan.org) for static analysis. Pest's type coverage is at 100%, and the test suite is also at 100% coverage.
+
+In terms of code style, we use [Laravel Pint](https://laravel.com/docs/11.x/pint) to ensure the code is consistent and follows the Laravel conventions. We also use [Rector](https://getrector.org) to ensure the code is up to date with the latest PHP version.
+
+You run these tools individually using the following commands:
+
+```bash
+# Lint the code using Pint
+composer lint
+composer test:lint
+
+# Refactor the code using Rector
+composer refactor
+composer test:refactor
+
+# Run PHPStan
+composer test:types
+
+# Run the test suite
+composer test:unit
+
+# Run all the tools
+composer test
+```
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100755
index 0000000..d3fa7fc
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Tomás López
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d449da7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+------
+## 🎨 **About Colority**
+
+Colority is a lightweight PHP library designed to handle color transformations, validations and manipulations with ease.
+
+
+It allows you to instantiate concrete objects according to the color format (*RGB, HSL, Hexadecimal*) and convert from one format to another.
+
+Additionally, it lets you check if a **background color meets the WCAG 2.0 accessibility standard** regarding the color contrast ratio in text and UI.
+
+Furthermore, it includes multiple functions such as the following:
+
+- Generate the **best foreground color** (*white, black, or from a user-provided list*) for a background color, ensuring the best possible contrast ratio. (*important for improving text visibility on, for example, colored badges*).
+- Generate a **fixed color based on a string.** Useful for generating a color associated with, for example, a username.
+- Allows you to obtain a **random color similar to a given color.**
+
+## **✨ Getting Started**
+
+### Instantiating Color objects
+
+You can convert value colors (*strings or, additionally, depending on the color type, arrays*) to specific `Color` objects.
+
+```php
+/** @var RgbColor $rgbColor */
+$rgbColor = colority()->fromRgb('rgb(255,255,255)');
+$rgbColor = colority()->fromRgb('255,255,255');
+$rgbColor = colority()->fromRgb([255, 255, 255]);
+
+/** @var HexColor $hexColor */
+$hexColor = colority()->fromHex('#51B389');
+$hexColor = colority()->fromHex('51B389');
+$hexColor = colority()->fromHex('#ABC');
+
+/** @var HslColor $hslColor */
+$hslColor = colority()->fromRgb('hsl(168.31deg, 49.58%, 46.67%)');
+$hslColor = colority()->fromRgb('168.31, 49.58, 46.67');
+$hslColor = colority()->fromRgb([168.31, 49.58, 46.67]);
+```
+If you cannot specify the original format of the value color, you can use the `parse` method. This will detect what type of color it is and instantiate a new object or, if the received string does not match any type of color, it will return `NULL`:
+```php
+/** @var RgbColor|null $rgbColor */
+$rgbColor = colority()->parse('rgb(255,255,255)');
+
+/** @var HexColor|null $hexColor */
+$hexColor = colority()->parse('#51B389');
+
+/** @var HslColor|null $hslColor */
+$hslColor = colority()->parse('hsl(168.31deg, 49.58%, 46.67%)');
+```
+
+### Contrast ratio (*WCAG 2.0 standard*)
+
+When you have the `Color` object, you will be able to use all its methods. Below, we describe two of them related to the contrast ratio.
+
+#### getBestForegroundColor
+
+Returns a `Color` object with the most suitable foreground color (*using the Luminosity Contrast Ratio algorithm*).
+
+You can pass an array with `Color` objects as a parameter, so it chooses the foreground color with the best contrast ratio. If no parameter is specified, it will default to white or black.
+
+```php
+/** @var HexColor $hexColor */
+$hexColor = colority()->fromHex('#51B389');
+
+/** @var HexColor $bestForegroundHexColor (black or white) */
+$bestForegroundHexColor = $hexColor->getBestForegroundColor();
+
+/** @var HexColor $bestForegroundHexColor (#A63F3F, #3FA684 or #6E3FA6) */
+$bestForegroundHexColor = $hexColor->getBestForegroundColor([
+ new HexColor('#A63F3F'),
+ new HexColor('#3FA684'),
+ new HexColor('#6E3FA6'),
+]);
+```
+
+#### getContrastRatio
+
+Returns the contrast ratio (*higher is better contrast, lower is worse*) between the color invoking this method and the color passed as a parameter. If no color is passed as a parameter, the contrast ratio against black as foreground will be determined.
+
+```php
+/** @var HexColor $hexColor */
+$hexColor = colority()->fromHex('#51B389');
+
+/** @var float $contrastRatio Contrast ratio with black as the foreground color. */
+$contrastRatio = $hexColor->getContrastRatio();
+
+/** @var float $contrastRatio Contrast ratio with #3FA684 as the foreground color. */
+$contrastRatio = $hexColor->getContrastRatio(new HexColor('#3FA684'));
+```
+
+### Color validation
+The concrete `Color` classes have a static method called `getParser()` which returns an instance of `ValueColorParser`.
+
+The `parse` method returns a string with the value color adapted to work correctly with Colority or throws an `InvalidArgumentException` when it's not valid.
+
+```php
+/** @var ValueColorParser $hexParser */
+$hexParser = HexColor::getParser();
+
+// will throw InvalidArgumentException
+$valueColor = $hexParser->parse('Not a valid value color');
+
+// will return #FFFFFF
+$valueColor = $hexParser->parse('#FFF');
+```
+
+You can use the specific parser for any type of color:
+
+```php
+$hslParser = HslColor::getParser();
+
+$rgbParser = RgbColor::getParser();
+
+$hexParser = HexColor::getParser();
+```
+
+### Color conversion
+Colority allows you to convert a Color object to any other `Color` object of the desired format.
+```php
+/** @var HexColor|null $hexColor */
+$hexColor = colority()->fromHex('#51B389');
+
+/** @var HexColor $hexColor */
+$hexColor = $hexColor->toHex();
+
+/** @var RgbColor $rgbColor */
+$rgbColor = $hexColor->toRgb();
+
+/** @var HslColor $hslColor */
+$hslColor = $hexColor->toHsl();
+```
+
+### Color utilities
+
+#### textToColor
+
+Generate a fixed color based on a string.
+
+```php
+/** @var HslColor $hslColor */
+$hslColor = colority()->textToColor("Hi, I'm Tomás");
+```
+> **🧙 Advise**
+> Useful for generating a color associated with, for example, a username, mail address, etc, since a string will always return the same color.
+
+#### getSimilarColor
+Allows you to obtain a random color similar (*in the same color palette*) to a given color.
+
+```php
+/** @var HexColor|null $hexColor */
+$hexColor = colority()->fromHex('#51B389');
+
+/** @var HexColor|null $similarHexColor */
+$similarHexColor = colority()->getSimilarColor($hexColor);
+```
+
+### Ways of using Colority
+You can use Colority either with the aliases `colority()`
+```php
+/** @var HexColor $hexColor */
+$hexColor = colority()->fromHex('#CCC');
+```
+
+or by directly invoking the static methods of the `Colority` facade:
+
+```php
+/** @var HexColor $hexColor */
+$hexColor = Colority::fromHex('#CCC');
+```
+You decide how to use it 🙂
+
+
+## **🧱 Architecture**
+Colority is composed of several types of elements. Below are some features of each of these elements.
+
+### `Colority`
+
+`Tomloprod\Colority\Support\Facades\Colority` is a facade that acts as a simplified interface for using the rest of the Colority elements.
+
+#### Methods
+```php
+
+Colority::parse(string $valueColor): Color|null
+
+Colority::fromHex(string $hexValue): HexColor
+
+Colority::fromRgb(string|array $rgbValue): RgbColor
+
+Colority::fromHsl(string|array $hslValue): HslColor
+
+Colority::textToColor(string $text): HslColor
+
+Colority::getSimilarColor(Color $color, int $hueRange = 30, int $saturationRange = 10, int $lightnessRange = 10): Color
+```
+
+### `Color`
+All concrete color classes extend the abstract class `Color`. Concrete color classes:
+- `Tomloprod\Colority\Colors\HexColor`
+- `Tomloprod\Colority\Colors\HslColor`
+- `Tomloprod\Colority\Colors\RgbColor`
+
+#### Methods
+
+```php
+/** @var HexColor $hexColor */
+$hexColor = Colority::fromHex('#CCCCCC');
+
+$hexColor->toHex(): HexColor;
+$hexColor->toRgb(): RgbColor;
+$hexColor->toHsl(): HslColor;
+
+// Returns the value color in string format. Example: #CCCCCC
+$hexColor->getValueColor(): string;
+```
+
+For the `HslColor` and `RgbColor` objects, you also have a method `getArrayValueColor` that will return the value color in array format:
+
+```php
+/** @var RgbColor $rgbColor */
+$rgbColor = Colority::fromRgb('255,255,255');
+
+/** @var array $arrayValueColor [255,255,255] */
+$arrayValueColor = $rgbColor->getArrayValueColor();
+```
+
+On the other hand, the `HslColor` object has an additional method called `getValueColorWithMeasureUnits`, which returns the value color, but with units of measurement (*useful for, for example, using it in a CSS style*):
+```php
+/** @var HslColor $hslColor */
+$hslColor = Colority::fromHsl('hsl(32.4, 60.48, 51.37)');
+
+/** @var string $valueColorWithMeasureUnits hsl(32.4deg,60.48%,51.37%) */
+$valueColorWithMeasureUnits = $hslColor->getValueColorWithMeasureUnits();
+
+/** @var string $valueColor hsl(32.4,60.48,51.37) */
+$valueColor = $hslColor->getValueColor(): string;
+
+```
+
+
+## **🚀 Installation & Requirements**
+
+> **Requires [PHP 8.2+](https://php.net/releases/)**
+
+You may use [Composer](https://getcomposer.org) to install Colority into your PHP project:
+
+```bash
+composer require tomloprod/colority
+```
+
+## **🧑🤝🧑 Contributing**
+
+Contributions are welcome, and are accepted via pull requests.
+Please [review these guidelines](./CONTRIBUTING.md) before submitting any pull requests.
+
+------
+
+**Colority** was created by **[Tomás López](https://twitter.com/tomloprod)** and open-sourced under the **[MIT license](https://opensource.org/licenses/MIT)**.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..212bbb4
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,66 @@
+{
+ "name": "tomloprod/colority",
+ "description": "Colority is a lightweight PHP library designed to handle color transformations, validations and manipulations with ease.",
+ "type": "library",
+ "keywords": [
+ "utility",
+ "colors",
+ "conversion-utiliy",
+ "wcag-contrast"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Tomás López",
+ "email": "tomloprod@gmail.com"
+ }
+ ],
+ "require": {
+ "php": "^8.2.0",
+ "ext-mbstring": "*"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.15.2",
+ "pestphp/pest": "^2.34",
+ "pestphp/pest-plugin-type-coverage": "^2.8",
+ "rector/rector": "^1.0.4"
+ },
+ "autoload": {
+ "psr-4": {
+ "Tomloprod\\Colority\\": "src/"
+ },
+ "files": [
+ "src/Support/ColorityAlias.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "config": {
+ "sort-packages": true,
+ "preferred-install": "dist",
+ "allow-plugins": {
+ "pestphp/pest-plugin": true
+ }
+ },
+ "scripts": {
+ "lint": "pint",
+ "refactor": "rector",
+ "test:lint": "pint --test",
+ "test:refactor": "rector --dry-run",
+ "test:types": "phpstan analyse",
+ "test:type-coverage": "pest --type-coverage --min=100",
+ "test:unit": "pest --coverage --min=100",
+ "test": [
+ "@test:lint",
+ "@test:refactor",
+ "@test:types",
+ "@test:type-coverage",
+ "@test:unit"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/colority.png b/docs/colority.png
new file mode 100644
index 0000000000000000000000000000000000000000..7fa7905dde6dc43843a2f4af3a9b5dc9e6d07b95
GIT binary patch
literal 22608
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