Full Code of mistic100/tinygradient for AI

master 085e07ebf5af cached
10 files
38.6 KB
10.5k tokens
23 symbols
1 requests
Download .txt
Repository: mistic100/tinygradient
Branch: master
Commit: 085e07ebf5af
Files: 10
Total size: 38.6 KB

Directory structure:
gitextract_9n36xi6a/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── index.js
├── package.json
├── tests/
│   └── basics.js
└── types.d.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: npm
    directory: "/"
    schedule:
      interval: daily
    assignees:
      - mistic100
    commit-message:
      prefix: 'chore'
      include: 'scope'
  - package-ecosystem: github-actions
    directory: '/'
    schedule:
      interval: weekly
    assignees:
      - mistic100
    commit-message:
      prefix: 'chore'
      include: 'scope'
  


================================================
FILE: .github/workflows/main.yml
================================================
name: CI

on: [push, pull_request]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    - name: build
      run: |
        npm install
        npm run test


================================================
FILE: .gitignore
================================================
.idea
*.iml
node_modules
yarn.lock
yarn-error.log


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014-2025 Damien Sorel

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: README.md
================================================
# tinygradient

[![npm version](https://img.shields.io/npm/v/tinygradient.svg?style=flat-square)](https://www.npmjs.com/package/tinygradient)
[![jsDelivr CDN](https://data.jsdelivr.com/v1/package/npm/tinygradient/badge)](https://www.jsdelivr.com/package/npm/tinygradient)
[![GZIP size](https://img.shields.io/bundlephobia/minzip/tinygradient?label=gzip%20size)](https://bundlephobia.com/result?p=tinygradient)
[![Build Status](https://github.com/mistic100/tinygradient/workflows/CI/badge.svg)](https://github.com/mistic100/tinygradient/actions)

Easily generate color gradients with an unlimited number of color stops and steps. 

[Live demo](https://mistic100.github.io/tinygradient/)

## Installation

```
$ npm install tinygradient
```

### Dependencies

- [TinyColor](https://github.com/bgrins/TinyColor)

## Usage

The gradient can be generated using RGB or HSV interpolation. HSV usually produces brighter colors.

### Initialize gradient

The `tinygradient` constructor takes a list or an array of colors stops.

```javascript
// using varargs
const gradient = tinygradient('red', 'green', 'blue');

// using array
const gradient = tinygradient([
  '#ff0000',
  '#00ff00',
  '#0000ff'
]);
```

The colors are parsed with TinyColor, [multiple formats are accepted](https://github.com/bgrins/TinyColor/blob/master/README.md#accepted-string-input).

```javascript
const gradient = tinygradient([
  tinycolor('#ff0000'),       // tinycolor object
  {r: 0, g: 255, b: 0},       // RGB object
  {h: 240, s: 1, v: 1, a: 1}, // HSVa object
  'rgb(120, 120, 0)',         // RGB CSS string
  'gold'                      // named color
]);
```

You can also specify the position of each color stop (between `0` and `1`). If no position is specified, stops are distributed equidistantly.

```javascript
const gradient = tinygradient([
  {color: '#d8e0de', pos: 0},
  {color: '#255B53', pos: 0.8},
  {color: '#000000', pos: 1}
]);
```

### Generate gradient

Each method takes at least the number of desired steps.
> The generated gradients might have one more step in certain conditions.

```javascript
// RGB interpolation
const colorsRgb = gradient.rgb(9);
```
![rgb](https://raw.githubusercontent.com/mistic100/tinygradient/master/images/rgb.png)

```javascript
// HSV clockwise interpolation
const colorsHsv = gradient.hsv(9);
```
![hsv](https://raw.githubusercontent.com/mistic100/tinygradient/master/images/hsv.png)

```javascript
// HSV counter-clockwise interpolation
const colorsHsv = gradient.hsv(9, true);
```
![hsv2](https://raw.githubusercontent.com/mistic100/tinygradient/master/images/hsv2.png)

There are also two methods which will automatically choose between clockwise and counter-clockwise.

```javascript
// HSV interpolation using shortest arc between colors
const colorsHsv = gradient.hsv(9, 'short');

// HSV interpolation using longest arc between colors
const colorsHsv = gradient.hsv(9, 'long');
```

Each method returns an array of TinyColor objects, [see available methods](https://github.com/bgrins/TinyColor/blob/master/README.md#methods).

### Get CSS gradient string

The `css` method will output a valid W3C string (without vendors prefix) to use with `background-image` CSS property.

```javascript
// linear gradient to right (default)
const gradientStr = gradient.css();

// radial gradient ellipse at top left
const gradientStr = gradient.css('radial', 'farthest-corner ellipse at top left');
```

### Get color at a specific position

Returns a single TinyColor object from a defined position in the gradient (from 0 to 1).

```javascript
// with RGB interpolation
colorAt55Percent = gradient.rgbAt(0.55);

// with HSV interpolation
colorAt55Percent = gradient.hsvAt(0.55);
```

### Reversing gradient

Returns a new instance of TinyGradient with reversed colors.

```javascript
const reversedGradient = gradient.reverse();
```

### Loop the gradient

Returns a new instance of TinyGradient with looped colors.

```javascript
const loopedGradient = gradient.loop();
```

### Position-only stops

I is possible to define stops with the `pos` property only and no `color`. This allows to define the position of the mid-point between the previous and the next stop.

```js
const gradient = tinygradient([
  {color: 'black', pos: 0},
  {pos: 0.8}, // #808080 will be at 80% instead of 50%
  {color: 'white', pos: 1}
]);
```


## License
This library is available under the MIT license.


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <title>tinygradient</title>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css"/>
    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap-colorpicker@3.0.3/dist/css/bootstrap-colorpicker.min.css"/>

    <style>
        footer {
            padding-top: 2rem;
            padding-bottom: 2rem;
            margin-top: 3rem;
            background-color: #e9ecef;
        }

        .colorpicker.colorpicker-popup {
            -moz-user-select: none;
        }

        .out-list .out {
            width: 100%;
            height: 20px;
            margin-bottom: 20px;
            display: flex;
            justify-content: space-between;
        }

        .out-list .out.css {
            margin-bottom: 0;
        }

        .out-list .out div {
            flex-grow: 1;
            position: relative;
        }

        .out-list .out div>span  {
            content: attr(title);
            position: absolute;
            bottom: -15px;
            font-family: monospace;
            font-size: 12px;
        }
        .out-list .out div>span::selection {
            color: white;
            background: #444;
        }
    </style>
</head>

<body>

<section class="jumbotron text-center">
    <div class="container">
        <h1 class="jumbotron-heading">tinygradient</h1>
        <p class="lead text-muted">simple gradient generator</p>
        <p>
            <a href="https://github.com/mistic100/tinygradient" class="btn btn-primary btn-lg">
                <img src="https://octodex.github.com/images/original.png" height="32" /> GitHub
            </a>
        </p>
    </div>
</section>

<div class="container">
    <h2>Playground</h2>

    <div class="row">
        <div class="col-4">
            <ul class="list-group mb-3" id="colors-list">
                <li class="list-group-item list-group-item-secondary" style="padding: 0.25rem 1.25rem;">
                    <div class="form-row">
                        <div class="col-7">
                            Color
                        </div>
                        <div class="col-5">
                            Position
                        </div>
                    </div>
                </li>
            </ul>

            <button class="btn btn-primary mb-3" type="button" id="add-color">new color</button>

            <div class="form-group form-inline">
                <label for="steps">Steps</label>
                <input type="number" class="ml-2 form-control" min="2" id="steps" name="steps">
            </div>
        </div>

        <div class="col-8">
            <div class="card mb-4">
                <ul class="list-group list-group-flush out-list" id="playground-output">
                </ul>
            </div>
        </div>
    </div>
</div>

<div class="container">
    <h2>Colorbrewer</h2>

    <div class="row">
        <div class="col-4">
            <div class="form-group form-inline">
                <label for="cb-scheme">Scheme</label>
                <select class="ml-2 form-control" id="cb-scheme" name="cb-scheme"></select>
            </div>

            <div class="form-group form-inline">
                <label for="cb-steps">Steps</label>
                <input type="number" class="ml-2 form-control" min="3" id="cb-steps" name="cb-steps">
            </div>
        </div>

        <div class="col-8">
            <div class="card mb-4">
                <ul class="list-group list-group-flush out-list" id="cb-playground-output">
                </ul>
            </div>
        </div>
    </div>
</div>

<script type="text/template" id="playground-item">
    <li class="list-group-item">
        <div class="form-row">
            <div class="col-7">
                <input class="form-control" type="text" name="color">
            </div>
            <div class="col-3">
                <input class="form-control" type="number" min="0" max="1" step="0.1" name="pos">
            </div>
            <div class="col-2">
                <button class="btn btn-outline-danger" data-dismiss="color">del.</button>
            </div>
        </div>
    </li>
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.6/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/js/bootstrap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-colorpicker@3.0.3/dist/js/bootstrap-colorpicker.min.js"></script>

<script type="importmap">
    {
        "imports": {
            "colorbrewer": "https://cdn.jsdelivr.net/npm/colorbrewer@1.5.9/+esm",
            "randomcolor": "https://cdn.jsdelivr.net/npm/randomcolor/+esm",
            "tinycolor2": "https://cdn.jsdelivr.net/npm/tinycolor2@1.6.0/esm/tinycolor.min.js",
            "tinygradient": "./index.js"
        }
    }
</script>

<script type="module">
    import colorbrewer from 'colorbrewer';
    import randomColor from 'randomcolor';
    import tinycolor from 'tinycolor2';
    import tinygradient from 'tinygradient';

    const tpl = document.querySelector('#playground-item').innerHTML;
    const list = $('#colors-list');

    let steps = 9;

    function addColor(color) {
        color = color || randomColor();

        const item = $(tpl);
        item.find('[name=color]').val(color).colorpicker();

        list.append(item);

        computePos();
    }

    function computePos() {
        const pos = list.find('[name=pos]');

        pos.each(function(i) {
           this.value = i / (pos.length - 1);
        });
    }

    function setOutputFromPlayground() {
        const pos = list.find('[name=pos]')
            .map(function () {
                const val = parseFloat(this.value);
                return isNaN(val) ? undefined : val;
            })
            .get();

        const colors = list.find('[name=color]')
            .map(function () {
                return this.value;
            })
            .get()
            .map(function(color, i) {
                return {
                    color: color,
                    pos: pos[i]
                };
            });

        setOutput(colors, steps);
    }

    function setOutput(colors, steps) {
        let html = '';

        if (colors.length < 2) {
            html = 'Not enough colors';
        } else {
            try {
                const grad = tinygradient(colors);

                // CSS
                html += '<li class="list-group-item"><h4>CSS reference</h4>';
                html += '<div class="out css" style="background:' + grad.css() + ';"></div>';
                html += '</li>';

                // RGB
                html += '<li class="list-group-item"><h4>RGB interpolation</h4>';
                html += '<div class="out rgb">';
                grad.rgb(steps).forEach((color) => {
                    html += '<div style="background:' + color.toRgbString() + ';"><span>' + color.toHexString() + '</span></div>';
                });
                html += '</div></li>';

                // RGB loop
                html += '<li class="list-group-item"><h4>RGB loop</h4>';
                html += '<div class="out rgb">';
                grad.loop().rgb(Math.max(steps, colors.length * 2 - 1)).forEach((color) => {
                    html += '<div style="background:' + color.toRgbString() + ';"><span>' + color.toHexString() + '</span></div>';
                });
                html += '</div></li>';

                // HSV
                html += '<li class="list-group-item"><h4>HSV short interpolation</h4>';
                html += '<div class="out hsv">';
                grad.hsv(steps, 'short').forEach((color) => {
                    html += '<div style="background:' + color.toRgbString() + ';"><span>' + color.toHexString() + '</span></div>';
                });
                html += '</div></li>';

                // HSV2
                html += '<li class="list-group-item"><h4>HSV long interpolation</h4>';
                html += '<div class="out hsv2">';
                grad.hsv(steps, 'long').forEach((color) => {
                    html += '<div style="background:' + color.toRgbString() + ';"><span>' + color.toHexString() + '</span></div>';
                });
                html += '</div></li>';

            } catch (error) {
                html = error;
            }
        }

        document.querySelector('#playground-output').innerHTML = html;
    }

    list
        .on('colorpickerChange colorpickerCreate', function (e) {
            e.colorpicker.element.css('background-color', e.color.toString());
            e.colorpicker.element.css('color', tinycolor.mostReadable(e.color.toString(), ['white', 'black']).toHexString());
            e.colorpicker.element[0].value = e.color.toString();
            setOutputFromPlayground();
        })
        .on('change', '[name=pos]', function() {
            setOutputFromPlayground();
        })
        .on('click', '[data-dismiss="color"]', function () {
            $(this).closest('li').remove();
            computePos();
            setOutputFromPlayground();
        });

    $('[name=steps]')
        .on('change input', function () {
            steps = this.value;
            setOutputFromPlayground();
        })
        .val(steps);

    addColor('#00E5BC');
    addColor('#BF0022');

    ////////////////////////////////////////

    let cbScheme = 'BuGn';
    let cbSteps = 9;

    function setCbOutput() {
        const colors = list.find('[name=color]')
            .map(function () {
                return this.value;
            })
            .get();

        let html = '';

        const cbVariant = Math.min(cbSteps, Math.max.apply(null, Object.keys(colorbrewer[cbScheme])));
        const grad = tinygradient(colorbrewer[cbScheme][cbVariant]);

        html += '<li class="list-group-item">';
        html += '<div class="out rgb">';
        grad.rgb(cbSteps).forEach((color) => {
            html += '<div style="background:' + color.toRgbString() + ';"><span>' + color.toHexString() + '</span></div>';
        });
        html += '</div></li>';

        document.querySelector('#cb-playground-output').innerHTML = html;
    }

    (() => {
        let html = '';

        $.each(colorbrewer.schemeGroups, (group, schemes) => {
           html+= '<optgroup label="' + group + '">';
           schemes.forEach((scheme) => {
              html += '<option>' + scheme + '</option>';
           });
           html+= '</optgroup>';
        });

        $('[name=cb-scheme]').html(html);
    })();

    $('[name=cb-scheme]')
        .on('change', function () {
            cbScheme = this.value;
            setCbOutput();
        })
        .val(cbScheme);

    $('[name=cb-steps]')
        .on('change', function () {
            cbSteps = this.value;
            setCbOutput();
        })
        .val(cbSteps);

    $('#add-color').on('click', () => addColor());

    setCbOutput();
</script>

</body>
</html>


================================================
FILE: index.js
================================================
import tinycolor from 'tinycolor2';

/**
 * @typedef {Object} TinyGradient.StopInput
 * @property {ColorInput} color
 * @property {number} pos
 */

/**
 * @typedef {Object} TinyGradient.StepValue
 * @type {number} [r]
 * @type {number} [g]
 * @type {number} [b]
 * @type {number} [h]
 * @type {number} [s]
 * @type {number} [v]
 * @type {number} [a]
 */

/**
 * @type {StepValue}
 */
const RGBA_MAX = { r: 256, g: 256, b: 256, a: 1 };

/**
 * @type {StepValue}
 */
const HSVA_MAX = { h: 360, s: 1, v: 1, a: 1 };

/**
 * Linearly compute the step size between start and end (not normalized)
 * @param {StepValue} start
 * @param {StepValue} end
 * @param {number} steps - number of desired steps
 * @return {StepValue}
 */
function stepize(start, end, steps) {
    let step = {};

    for (let k in start) {
        if (start.hasOwnProperty(k)) {
            step[k] = steps === 0 ? 0 : (end[k] - start[k]) / steps;
        }
    }

    return step;
}

/**
 * Compute the final step color
 * @param {StepValue} step - from `stepize`
 * @param {StepValue} start
 * @param {number} i - color index
 * @param {StepValue} max - rgba or hsva of maximum values for each channel
 * @return {StepValue}
 */
function interpolate(step, start, i, max) {
    let color = {};

    for (let k in start) {
        if (start.hasOwnProperty(k)) {
            color[k] = step[k] * i + start[k];
            color[k] = color[k] < 0 ? color[k] + max[k] : (max[k] !== 1 ? color[k] % max[k] : color[k]);
        }
    }

    return color;
}

/**
 * Generate gradient with RGBa interpolation
 * @param {StopInput} stop1
 * @param {StopInput} stop2
 * @param {number} steps
 * @return {tinycolor[]} color1 included, color2 excluded
 */
function interpolateRgb(stop1, stop2, steps) {
    const start = stop1.color.toRgb();
    const end = stop2.color.toRgb();
    const step = stepize(start, end, steps);
    let gradient = [stop1.color];

    for (let i = 1; i < steps; i++) {
        const color = interpolate(step, start, i, RGBA_MAX);
        gradient.push(tinycolor(color));
    }

    return gradient;
}

/**
 * Generate gradient with HSVa interpolation
 * @param {StopInput} stop1
 * @param {StopInput} stop2
 * @param {number} steps
 * @param {boolean|'long'|'short'} mode
 * @return {tinycolor[]} color1 included, color2 excluded
 */
function interpolateHsv(stop1, stop2, steps, mode) {
    const start = stop1.color.toHsv();
    const end = stop2.color.toHsv();

    // rgb interpolation if one of the steps in grayscale
    if (start.s === 0 || end.s === 0) {
        return interpolateRgb(stop1, stop2, steps);
    }

    let trigonometric;
    if (typeof mode === 'boolean') {
        trigonometric = mode;
    }
    else {
        const trigShortest = (start.h < end.h && end.h - start.h < 180) || (start.h > end.h && start.h - end.h > 180);
        trigonometric = (mode === 'long' && trigShortest) || (mode === 'short' && !trigShortest);
    }

    const step = stepize(start, end, steps);
    let gradient = [stop1.color];

    // recompute hue
    let diff;
    if ((start.h <= end.h && !trigonometric) || (start.h >= end.h && trigonometric)) {
        diff = end.h - start.h;
    }
    else if (trigonometric) {
        diff = 360 - end.h + start.h;
    }
    else {
        diff = 360 - start.h + end.h;
    }
    step.h = Math.pow(-1, trigonometric ? 1 : 0) * Math.abs(diff) / steps;

    for (let i = 1; i < steps; i++) {
        const color = interpolate(step, start, i, HSVA_MAX);
        gradient.push(tinycolor(color));
    }

    return gradient;
}

/**
 * Compute substeps between each stops
 * @param {StopInput[]} stops
 * @param {number} steps
 * @return {number[]}
 */
function computeSubsteps(stops, steps) {
    const l = stops.length;

    // validation
    steps = parseInt(steps, 10);

    if (isNaN(steps) || steps < 2) {
        throw new Error('Invalid number of steps (< 2)');
    }
    if (steps < l) {
        throw new Error('Number of steps cannot be inferior to number of stops');
    }

    // compute substeps from stop positions
    let substeps = [];

    for (let i = 1; i < l; i++) {
        const step = (steps - 1) * (stops[i].pos - stops[i - 1].pos);
        substeps.push(Math.max(1, Math.round(step)));
    }

    // adjust number of steps
    let totalSubsteps = 1;
    for (let n = l - 1; n--;) totalSubsteps += substeps[n];

    while (totalSubsteps !== steps) {
        if (totalSubsteps < steps) {
            const min = Math.min.apply(null, substeps);
            substeps[substeps.indexOf(min)]++;
            totalSubsteps++;
        }
        else {
            const max = Math.max.apply(null, substeps);
            substeps[substeps.indexOf(max)]--;
            totalSubsteps--;
        }
    }

    return substeps;
}

/**
 * Compute the color at a specific position
 * @param {StopInput[]} stops
 * @param {number} pos
 * @param {string} method
 * @param {StepValue} max
 * @returns {tinycolor}
 */
function computeAt(stops, pos, method, max) {
    if (pos < 0 || pos > 1) {
        throw new Error('Position must be between 0 and 1');
    }

    let start, end;
    for (let i = 0, l = stops.length; i < l - 1; i++) {
        if (pos >= stops[i].pos && pos < stops[i + 1].pos) {
            start = stops[i];
            end = stops[i + 1];
            break;
        }
    }

    if (!start) {
        start = end = stops[stops.length - 1];
    }

    const step = stepize(start.color[method](), end.color[method](), (end.pos - start.pos) * 100);
    const color = interpolate(step, start.color[method](), (pos - start.pos) * 100, max);
    return tinycolor(color);
}

class TinyGradient {
    /**
     * @param {StopInput[]|ColorInput[]} stops
     * @returns {TinyGradient}
     */
    constructor(stops) {
        // validation
        if (stops.length < 2) {
            throw new Error('Invalid number of stops (< 2)');
        }

        const havingPositions = stops[0].pos !== undefined;
        let l = stops.length;
        let p = -1;
        let lastColorLess = false;
        // create tinycolor objects and clean positions
        this.stops = stops.map((stop, i) => {
            const hasPosition = stop.pos !== undefined;
            if (havingPositions ^ hasPosition) {
                throw new Error('Cannot mix positionned and not posionned color stops');
            }

            if (hasPosition) {
                const hasColor = stop.color !== undefined;
                if (!hasColor && (lastColorLess || i === 0 || i === l - 1)) {
                    throw new Error('Cannot define two consecutive position-only stops');
                }
                lastColorLess = !hasColor;

                stop = {
                    color    : hasColor ? tinycolor(stop.color) : null,
                    colorLess: !hasColor,
                    pos      : stop.pos
                };

                if (stop.pos < 0 || stop.pos > 1) {
                    throw new Error('Color stops positions must be between 0 and 1');
                }
                else if (stop.pos < p) {
                    throw new Error('Color stops positions are not ordered');
                }
                p = stop.pos;
            }
            else {
                stop = {
                    color: tinycolor(stop.color !== undefined ? stop.color : stop),
                    pos  : i / (l - 1)
                };
            }

            return stop;
        });

        if (this.stops[0].pos !== 0) {
            this.stops.unshift({
                color: this.stops[0].color,
                pos  : 0
            });
            l++;
        }
        if (this.stops[l - 1].pos !== 1) {
            this.stops.push({
                color: this.stops[l - 1].color,
                pos  : 1
            });
        }
    }

    /**
     * Return new instance with reversed stops
     * @return {TinyGradient}
     */
    reverse() {
        let stops = [];

        this.stops.forEach((stop) => {
            stops.push({
                color: stop.color,
                pos  : 1 - stop.pos
            });
        });

        return new TinyGradient(stops.reverse());
    }

    /**
     * Return new instance with looped stops
     * @return {TinyGradient}
     */
    loop() {
        let stops1 = [];
        let stops2 = [];

        this.stops.forEach((stop) => {
            stops1.push({
                color: stop.color,
                pos  : stop.pos / 2
            });
        });

        this.stops.slice(0, -1).forEach((stop) => {
            stops2.push({
                color: stop.color,
                pos  : 1 - stop.pos / 2
            });
        });

        return new TinyGradient(stops1.concat(stops2.reverse()));
    }

    /**
     * Generate gradient with RGBa interpolation
     * @param {number} steps
     * @return {tinycolor[]}
     */
    rgb(steps) {
        const substeps = computeSubsteps(this.stops, steps);
        let gradient = [];

        this.stops.forEach((stop, i) => {
            if (stop.colorLess) {
                stop.color = interpolateRgb(this.stops[i - 1], this.stops[i + 1], 2)[1];
            }
        });

        for (let i = 0, l = this.stops.length; i < l - 1; i++) {
            const rgb = interpolateRgb(this.stops[i], this.stops[i + 1], substeps[i]);
            gradient.splice(gradient.length, 0, ...rgb);
        }

        gradient.push(this.stops[this.stops.length - 1].color);

        return gradient;
    }

    /**
     * Generate gradient with HSVa interpolation
     * @param {number} steps
     * @param {boolean|'long'|'short'} [mode=false]
     *    - false to step in clockwise
     *    - true to step in trigonometric order
     *    - 'short' to use the shortest way
     *    - 'long' to use the longest way
     * @return {tinycolor[]}
     */
    hsv(steps, mode) {
        const substeps = computeSubsteps(this.stops, steps);
        let gradient = [];

        this.stops.forEach((stop, i) => {
            if (stop.colorLess) {
                stop.color = interpolateHsv(this.stops[i - 1], this.stops[i + 1], 2, mode)[1];
            }
        });

        for (let i = 0, l = this.stops.length; i < l - 1; i++) {
            const hsv = interpolateHsv(this.stops[i], this.stops[i + 1], substeps[i], mode);
            gradient.splice(gradient.length, 0, ...hsv);
        }

        gradient.push(this.stops[this.stops.length - 1].color);

        return gradient;
    }

    /**
     * Generate CSS3 command (no prefix) for this gradient
     * @param {String} [mode=linear] - 'linear' or 'radial'
     * @param {String} [direction] - default is 'to right' or 'ellipse at center'
     * @return {String}
     */
    css(mode, direction) {
        mode = mode || 'linear';
        direction = direction || (mode === 'linear' ? 'to right' : 'ellipse at center');

        let css = mode + '-gradient(' + direction;
        this.stops.forEach((stop) => {
            css += ', ' + (stop.colorLess ? '' : stop.color.toRgbString() + ' ') + (stop.pos * 100) + '%';
        });
        css += ')';
        return css;
    }

    /**
     * Returns the color at specific position with RGBa interpolation
     * @param {number} pos, between 0 and 1
     * @return {tinycolor}
     */
    rgbAt(pos) {
        return computeAt(this.stops, pos, 'toRgb', RGBA_MAX);
    }

    /**
     * Returns the color at specific position with HSVa interpolation
     * @param {number} pos, between 0 and 1
     * @return {tinycolor}
     */
    hsvAt(pos) {
        return computeAt(this.stops, pos, 'toHsv', HSVA_MAX);
    }
}

/**
 * @param {StopInput[]|ColorInput[]|StopInput...|ColorInput...} stops
 * @returns {TinyGradient}
 */
function tinygradient(stops) {
    // varargs
    if (arguments.length === 1) {
        if (!Array.isArray(arguments[0])) {
            throw new Error('"stops" is not an array');
        }
        stops = arguments[0];
    }
    else {
        stops = Array.prototype.slice.call(arguments);
    }

    return new TinyGradient(stops);
}

export {
    tinygradient,
    tinygradient as default,
};


================================================
FILE: package.json
================================================
{
  "name": "tinygradient",
  "version": "2.0.1",
  "author": {
    "name": "Damien \"Mistic\" Sorel",
    "email": "contact@git.strangeplanet.fr",
    "url": "https://www.strangeplanet.fr"
  },
  "description": "Fast and small gradients manipulation, built on top of TinyColor",
  "license": "MIT",
  "homepage": "https://github.com/mistic100/tinygradient",
  "type": "module",
  "main": "index.js",
  "types": "types.d.ts",
  "files": [
    "index.js",
    "types.d.ts"
  ],
  "dependencies": {
    "tinycolor2": "^1.6.0"
  },
  "devDependencies": {
    "@types/tinycolor2": "^1.4.6",
    "alive-server": "^1.3.0",
    "mocha": "^11.1.0"
  },
  "keywords": [
    "color",
    "gradient"
  ],
  "repository": {
    "type": "git",
    "url": "git://github.com/mistic100/tinygradient.git"
  },
  "bugs": {
    "url": "https://github.com/mistic100/tinygradient/issues"
  },
  "scripts": {
    "test": "mocha tests/*",
    "serve": "alive-server"
  }
}


================================================
FILE: tests/basics.js
================================================
import tinygradient from '../index.js';
import assert from 'assert';

describe('TinyGradient', () => {
    it('should throw an error on invalid steps/colors number', () => {
        assert.throws(() => {
            tinygradient('red');
        });
        assert.throws(() => {
            tinygradient(['red']);
        });
        assert.throws(() => {
            let grad = tinygradient('red', 'blue');
            grad.rgb(1);
        });
        assert.throws(() => {
            let grad = tinygradient('red', 'blue', 'green');
            grad.rgb(2);
        });
    });

    it('should accept varargs and array', () => {
        let grad1 = tinygradient('red', 'green', 'blue', 'yellow', 'black');
        let grad2 = tinygradient(['red', 'green', 'blue', 'yellow', 'black']);

        assert.deepStrictEqual(
            grad1.stops.map(c => c.color.toRgb()),
            grad2.stops.map(c => c.color.toRgb())
        );
    });

    it('should reverse gradient', () => {
        let grad1 = tinygradient('red', 'green', 'blue', 'yellow', 'black');
        let grad2 = grad1.reverse();

        assert.deepStrictEqual(
            grad1.stops.map(c => c.color.toRgb()),
            grad2.stops.reverse().map(c => c.color.toRgb())
        );
    });

    it('should generate 11 steps gradient from black to grey in RGB', () => {
        let grad = tinygradient({ r: 0, g: 0, b: 0 }, { r: 100, g: 100, b: 100 });
        let res = grad.rgb(11);

        assert.strictEqual(11, res.length);
        assert.deepStrictEqual({ r: 0, g: 0, b: 0, a: 1 }, res[0].toRgb(), 'black');
        assert.deepStrictEqual({ r: 50, g: 50, b: 50, a: 1 }, res[5].toRgb(), 'dark gray');
        assert.deepStrictEqual({ r: 100, g: 100, b: 100, a: 1 }, res[10].toRgb(), 'gray');
    });

    it('should generate 13 steps gradient from red to red in HSV', () => {
        let grad = tinygradient([
            { h: 0, s: 1, v: 1 },
            { h: 120, s: 1, v: 1 },
            { h: 240, s: 1, v: 1 },
            { h: 0, s: 1, v: 1 }
        ]);
        let res = grad.hsv(13);

        assert.strictEqual(13, res.length);
        assert.deepStrictEqual({ h: 60, s: 1, v: 1, a: 1 }, res[2].toHsv(), 'yellow');
        assert.deepStrictEqual({ h: 180, s: 1, v: 1, a: 1 }, res[6].toHsv(), 'cyan');
        assert.deepStrictEqual({ h: 300, s: 1, v: 1, a: 1 }, res[10].toHsv(), 'magenta');
    });

    it('should generate CSS gradient command for 3 colors', () => {
        let grad = tinygradient('#f00', '#0f0', '#00f');
        let res = grad.css();
        assert.strictEqual('linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(0, 255, 0) 50%, rgb(0, 0, 255) 100%)', res, 'default');

        grad = tinygradient('rgba(255,0,0,0.5)', 'rgba(0,255,0,0.5)', 'rgba(0,0,255,0.5)');
        res = grad.css('radial', 'ellipse farthest-corner');
        assert.strictEqual('radial-gradient(ellipse farthest-corner, rgba(255, 0, 0, 0.5) 0%, rgba(0, 255, 0, 0.5) 50%, rgba(0, 0, 255, 0.5) 100%)', res, 'radial with alpha');
    });

    it('should returns a single color at specific position', () => {
        let grad = tinygradient('white', 'black');
        let res = grad.rgbAt(0.5);
        assert.deepStrictEqual({ r: 128, g: 128, b: 128, a: 1 }, res.toRgb(), 'rgb');

        grad = tinygradient('red', 'blue');
        res = grad.hsvAt(0.5);
        assert.deepStrictEqual({ h: 120, s: 1, v: 1, a: 1 }, res.toHsv(), 'hsv');
    });

    it('should loop a gradient', () => {
        let grad = tinygradient({ r: 0, g: 0, b: 0 }, { r: 255, g: 255, b: 255 });
        let res = grad.loop().rgb(5);

        assert.strictEqual(5, res.length);
        assert.deepStrictEqual({ r: 0, g: 0, b: 0, a: 1 }, res[0].toRgb(), 'black');
        assert.deepStrictEqual({ r: 255, g: 255, b: 255, a: 1 }, res[2].toRgb(), 'white');
        assert.deepStrictEqual(res[0].toRgb(), res[4].toRgb(), 'black');
        assert.deepStrictEqual(res[1].toRgb(), res[3].toRgb(), 'black');
    });

    it('should allow positionned stops', () => {
        let grad = tinygradient([{ color: 'black', pos: 0 }, { color: 'white', pos: 0.5 }]);

        assert.deepStrictEqual(
            grad.rgb(5).map((c) => c.toHex()),
            ['000000', '808080', 'ffffff', 'ffffff', 'ffffff']
        );
    });

    it('should allow position only stops', () => {
        // reference
        let grad1 = tinygradient([{ color: 'black', pos: 0 }, { color: 'white', pos: 1 }]);

        assert.deepStrictEqual(
            grad1.rgb(5).map((c) => c.toHex()),
            ['000000', '404040', '808080', 'bfbfbf', 'ffffff']
        );

        // with position stop
        let grad2 = tinygradient([{ color: 'black', pos: 0 }, { pos: 0.2 }, { color: 'white', pos: 1 }]);

        assert.deepStrictEqual(
            grad2.rgb(5).map((c) => c.toHex()),
            ['000000', '808080', 'aaaaaa', 'd5d5d5', 'ffffff']
        );
    });

    it('should prevent consecutive position stops', () => {
        assert.throws(() => {
            tinygradient([{ color: 'black', pos: 0 }, { pos: 0.2 }, { pos: 0.4 }, { color: 'white', pos: 1 }]);
        });
        assert.throws(() => {
            tinygradient([{ pos: 0.4 }, { color: 'white', pos: 1 }]);
        });
        assert.throws(() => {
            tinygradient([{ color: 'black', pos: 0 }, { pos: 0.2 }]);
        });
    });

    it('should prevent misordered stops', () => {
        assert.throws(() => {
            tinygradient([{ color: 'black', pos: 0.5 }, { color: 'white', pos: 0 }]);
        });
    });

    it('should allow equal position stops', () => {
        let grad = tinygradient([
            { color: 'black', pos: 0 },
            { color: 'white', pos: 0.5 },
            { color: 'black', pos: 0.5 },
            { color: 'white', pos: 1 },
        ]);

        assert.deepStrictEqual(
            grad.rgb(8).map((c) => c.toHex()),
            ['000000', '555555', 'aaaaaa', 'ffffff', '000000', '555555', 'aaaaaa', 'ffffff']
        );
    });

    it('should force RGB interpolation when a color is grey', () => {
        let grad = tinygradient('rgba(86, 86, 86)', 'rgb(45, 163, 185)');

        assert.deepStrictEqual(
            grad.hsv(5).map((c) => c.toHex()),
            grad.rgb(5).map((c) => c.toHex()),
        );
    });
});


================================================
FILE: types.d.ts
================================================
/*!
 * TinyGradient 1.1.2
 * Copyright 2014-2020 Damien "Mistic" Sorel (http://www.strangeplanet.fr)
 * Licensed under MIT (http://opensource.org/licenses/MIT)
 */

import * as tinycolor from 'tinycolor2';

declare namespace tinygradient {

    type ArcMode = boolean | 'short' | 'long';

    type CssMode = 'linear' | 'radial';

    type StopInput = {
        color?: tinycolor.ColorInput
        pos?: number
    }

    interface Instance {
        stops: StopInput[]

        /**
         * Return new instance with reversed stops
         * @return {Instance}
         */
        reverse(): Instance;

        /**
         * Return new instance with looped stops
         * @return {Instance}
         */
        loop(): Instance;

        /**
         * Generate gradient with RGBa interpolation
         * @param {int} steps
         * @return {tinycolor.Instance[]}
         */
        rgb(steps: number): tinycolor.Instance[];

        /**
         * Generate gradient with HSVa interpolation
         * @param {int} steps
         * @param {ArcMode} [mode=false]
         *    - false to step in clockwise
         *    - true to step in trigonometric order
         *    - 'short' to use the shortest way
         *    - 'long' to use the longest way
         * @return {tinycolor.Instance[]}
         */
        hsv(steps: number, mode: ArcMode): tinycolor.Instance[];

        /**
         * Generate CSS3 command (no prefix) for this gradient
         * @param {CssMode} [mode=linear] - 'linear' or 'radial'
         * @param {String} [direction] - default is 'to right' or 'ellipse at center'
         * @return {String}
         */
        css(mode?: CssMode, direction?: string): string;

        /**
         * Returns the color at specific position with RGBa interpolation
         * @param {float} pos, between 0 and 1
         * @return {tinycolor.Instance}
         */
        rgbAt(pos: number): tinycolor.Instance;

        /**
         * Returns the color at specific position with HSVa interpolation
         * @param {float} pos, between 0 and 1
         * @return {tinycolor.Instance}
         */
        hsvAt(pos: number): tinycolor.Instance;

    }

    interface Constructor {
        /**
         * @class tinygradient
         * @param {StopInput} stops
         */
        new (stops: StopInput[]): Instance;
        new (...stops: StopInput[]): Instance;
        (stops: StopInput[]): Instance;
        (...stops: StopInput[]): Instance;

        /**
         * @class tinygradient
         * @param {tinycolor.ColorInput[]} stops
         */
        new (stops: tinycolor.ColorInput[]): Instance;
        new (...stops: tinycolor.ColorInput[]): Instance;
        (stops: tinycolor.ColorInput[]): Instance;
        (...stops: tinycolor.ColorInput[]): Instance;
    }
}

declare const tinygradient: tinygradient.Constructor;
export default tinygradient;
export { tinygradient };
Download .txt
gitextract_9n36xi6a/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── index.js
├── package.json
├── tests/
│   └── basics.js
└── types.d.ts
Download .txt
SYMBOL INDEX (23 symbols across 2 files)

FILE: index.js
  constant RGBA_MAX (line 23) | const RGBA_MAX = { r: 256, g: 256, b: 256, a: 1 };
  constant HSVA_MAX (line 28) | const HSVA_MAX = { h: 360, s: 1, v: 1, a: 1 };
  function stepize (line 37) | function stepize(start, end, steps) {
  function interpolate (line 57) | function interpolate(step, start, i, max) {
  function interpolateRgb (line 77) | function interpolateRgb(stop1, stop2, steps) {
  function interpolateHsv (line 99) | function interpolateHsv(stop1, stop2, steps, mode) {
  function computeSubsteps (line 147) | function computeSubsteps(stops, steps) {
  function computeAt (line 196) | function computeAt(stops, pos, method, max) {
  class TinyGradient (line 219) | class TinyGradient {
    method constructor (line 224) | constructor(stops) {
    method reverse (line 291) | reverse() {
    method loop (line 308) | loop() {
    method rgb (line 334) | rgb(steps) {
    method hsv (line 364) | hsv(steps, mode) {
    method css (line 390) | css(mode, direction) {
    method rgbAt (line 407) | rgbAt(pos) {
    method hsvAt (line 416) | hsvAt(pos) {
  function tinygradient (line 425) | function tinygradient(stops) {

FILE: types.d.ts
  type ArcMode (line 11) | type ArcMode = boolean | 'short' | 'long';
  type CssMode (line 13) | type CssMode = 'linear' | 'radial';
  type StopInput (line 15) | type StopInput = {
  type Instance (line 20) | interface Instance {
  type Constructor (line 78) | interface Constructor {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (42K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 395,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: \"/\"\n    schedule:\n      interval: daily\n    assignees:\n   "
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 199,
    "preview": "name: CI\n\non: [push, pull_request]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions/check"
  },
  {
    "path": ".gitignore",
    "chars": 50,
    "preview": ".idea\n*.iml\nnode_modules\nyarn.lock\nyarn-error.log\n"
  },
  {
    "path": "LICENSE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2025 Damien Sorel\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 4416,
    "preview": "# tinygradient\n\n[![npm version](https://img.shields.io/npm/v/tinygradient.svg?style=flat-square)](https://www.npmjs.com/"
  },
  {
    "path": "index.html",
    "chars": 11143,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n\n    <title>tinygradient</title>\n\n    <link rel=\"stylesheet\" hr"
  },
  {
    "path": "index.js",
    "chars": 12084,
    "preview": "import tinycolor from 'tinycolor2';\n\n/**\n * @typedef {Object} TinyGradient.StopInput\n * @property {ColorInput} color\n * "
  },
  {
    "path": "package.json",
    "chars": 950,
    "preview": "{\n  \"name\": \"tinygradient\",\n  \"version\": \"2.0.1\",\n  \"author\": {\n    \"name\": \"Damien \\\"Mistic\\\" Sorel\",\n    \"email\": \"con"
  },
  {
    "path": "tests/basics.js",
    "chars": 6266,
    "preview": "import tinygradient from '../index.js';\nimport assert from 'assert';\n\ndescribe('TinyGradient', () => {\n    it('should th"
  },
  {
    "path": "types.d.ts",
    "chars": 2913,
    "preview": "/*!\n * TinyGradient 1.1.2\n * Copyright 2014-2020 Damien \"Mistic\" Sorel (http://www.strangeplanet.fr)\n * Licensed under M"
  }
]

About this extraction

This page contains the full source code of the mistic100/tinygradient GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (38.6 KB), approximately 10.5k tokens, and a symbol index with 23 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!