<div class="app">
    <div class="content">
        <div class="part part-base"></div>
        <div class="part part-single">
            <button class="button button-single">
                <i class="fa fa-code"></i>
            </button>
        </div>
        <div class="part part-multiple">
            <button class="button button-multiple">
                <i class="fa fa-code"></i>
            </button>
        </div>
    </div>
    <h1 class="part-heading heading-base">Base</h1>
    <h1 class="part-heading heading-current">Current</h1>
    <h1 class="part-heading heading-multiple">Multiple</h1>
    <div class="overlay">
        <h1 class="overlay-heading">Css gradient maker 2</h1>
        <button class="overlay-button">Press here to start</button>
    </div>
</div>
@import url('https://fonts.googleapis.com/css?family=Prompt:700')

$font: 'Prompt', sans-serif
$headingSize: 48px
$one-third: 33.333333
$breakpoint: 867px

=headingTopPos($heading, $pos1, $pos2)
    .#{$heading}
        @media (max-width: 866px)
            top: $pos1
        @media (min-width: $breakpoint)
            left: $pos2

.app
    *
        box-sizing: border-box
    h1, span, label
        font-family: $font
    
.content
    height: 100%
    display: flex
    flex-direction: column
    @media (min-width: $breakpoint)
        flex-direction: row

// PARTS
        
.part
    position: relative
    width: 100%
    height: $one-third + vh
    @media (min-width: $breakpoint)
        flex-direction: column
        width: $one-third + vw
        height: 100vh
        
.part-base > div
    display: flex
    align-items: center
    justify-content: center
    &:hover
        span
            color: rgba(0, 0, 0, 0.5)
            mix-blend-mode: color-burn

        
// HEADINGS
.part-heading
    position: absolute
    z-index: 1
    width: 100%
    text-align: center
    margin: 0
    font-size: 2rem
    mix-blend-mode: difference
    color: #212121
    @media (min-width: $breakpoint)
        width: 33.3333%
        top: calc(50vh - 24px)
 
$basePosSm: calc((33.33333% / 2) - 24px)
$currentPosSm: calc(50% - 24px)
$multiplePosSm: calc((33.33333% * 2.5) - 24px)

$basePosMd: 0
$currentPosMd: 33.33333%
$multiplePosMd: 33.33333% * 2
        
+headingTopPos(heading-base, $basePosSm, $basePosMd)
+headingTopPos(heading-current, $currentPosSm, $currentPosMd)
+headingTopPos(heading-multiple, $multiplePosSm, $multiplePosMd)
    
    
// overlay
.overlay
    position: absolute
    top: 0
    left: 0
    right: 0
    bottom: 0
    z-index: 10
    display: flex
    align-items: center
    justify-content: center
    flex-wrap: wrap
    align-content: center
    flex-direction: column
    background: radial-gradient(at 1% 1%, rgb(0, 80, 252), rgb(60, 82, 246), rgb(85, 85, 241), rgb(103, 88, 236), rgb(119, 91, 230), rgb(132, 94, 225), rgb(143, 97, 220), rgb(154, 101, 214), rgb(164, 104, 209), rgb(173, 108, 204), rgb(182, 112, 198), rgb(190, 116, 193), rgb(198, 120, 187), rgb(205, 123, 182), rgb(212, 127, 176), rgb(219, 132, 171), rgb(226, 136, 165), rgb(233, 140, 159), rgb(239, 144, 153), rgb(245, 148, 148), rgb(252, 153, 142), rgb(252, 156, 138), rgb(252, 160, 135), rgb(253, 164, 131), rgb(253, 168, 128), rgb(253, 172, 124), rgb(253, 175, 120), rgb(253, 179, 117), rgb(254, 183, 113), rgb(254, 186, 109), rgb(254, 190, 105), rgb(254, 194, 101), rgb(254, 197, 96), rgb(253, 201, 92), rgb(253, 204, 87), rgb(253, 208, 82), rgb(253, 211, 77), rgb(253, 215, 71), rgb(252, 218, 65), rgb(252, 222, 58))
    
.overlay-heading
    // width: 100%
    margin: 0
    font-size: 48px
    color: #212121
    mix-blend-mode: soft-light
    text-align: center
    margin-bottom: 30px
   
    
=button
    box-sizing: border-box
    font-family: 'Prompt'
    outline: none
    border: 2px solid black
    border-radius: 4px
    padding: 10px
    font-size: 24px
    background-color: transparent
    mix-blend-mode: soft-light
    &:focus, &:hover
        outline: none
        cursor: pointer
    
.overlay-button
    +button
    
.button
    +button
    width: 50px
    height: 50px
    position: absolute
    z-index: 1
    bottom: 5px
    right: calc(50% - 25px)
    
.sample
    transition: 200ms all ease-in-out
    font-family: 'Prompt'
    font-size: 14px
    color: transparent
    text-align: center
View Compiled
// Labels
const ADD_GRADIENT = 'Add new'
const REMOVE_GRADIENT = 'Remove current'
const LIST = 'List'
const BACKGROUND_BLEND_MODE = 'Blend mode'
const ADD_COLOR = 'Add color'
const REMOVE_COLOR = 'Remove color'
const OPACITY = 'Opacity'
const CLEAR_COLORS = 'Clear colors'
const SAMPLES = 'Samples'
const INTERPOLATION = 'Interpolation'
const MODE = 'Mode'
const LIGHTNESS_CORRECTION = 'Lightness correction'
const CSS_GRADIENT_TYPE = 'Css gradient type'
const POSITION = 'Position'
const TOP = 'Top'
const LEFT = 'Left'
const SHAPE = 'Shape'
const EXTENT_KEYWORD = 'Extent keyword'
const WITH_ANGLE = 'With angle'
const ANGLE = 'Angle'

// dom elements
const base = document.querySelector('.part.part-base')
const single = document.querySelector('.part.part-single')
const multiple = document.querySelector('.part.part-multiple')
const getSingle = document.querySelector('.button.button-single')
const getMultiple = document.querySelector('.button.button-multiple')
const overlay = document.querySelector('.overlay')
const overlayButton = document.querySelector('.overlay-button')

// utils
const utils = {
    stringifyResults: (colors, opacity) => colors.map(color => {
        let r = Math.floor(color._rgb[0]),
            g = Math.floor(color._rgb[1]),
            b = Math.floor(color._rgb[2]),
            a = opacity / 100
        return `rgba(${r}, ${g}, ${b}, ${a})`
    }),
    showSingle: () => {
        getSingle.addEventListener('click', () => {
            let style = single.style
            document.execCommand('Copy')
            alert(style.background)
        })
    },
    showMultiple: () => {
        getMultiple.addEventListener('click', () => {
            let style = multiple.style
            document.execCommand('Copy')
            alert(`background: ${style.background}; background-blend-mode: ${style.backgroundBlendMode};`)
        })
    },
    displayOnResize: () => {
        window.addEventListener('resize', () => {
            if (gradientPanel) {
                gradientController.display()
            }
        })
    }
}

// single gradient
let gradientPanel

const defaultColors = [
    '#fc0023', 
    '#00d60c'
]

const gradientInitialConfig = [
    {
        name: ADD_COLOR,
        type: 'button',
        callback: () => gradientController.addColor()
    },
    {
        name: REMOVE_COLOR,
        type: 'button',
        callback: () => gradientController.removeColor()
    },
    {
        name: OPACITY,
        type: 'range',
        items: [0, 100, 75, 1]
    },
    {
        name: SAMPLES,
        type: 'range',
        items: [10, 40, 10, 10]
    },
    {
        name: INTERPOLATION,
        type: 'select',
        items: [
            'linear', 
            'bezier'
        ]
    },
    {
        name: MODE,
        type: 'select',
        items: [
            'none',
            'lch', 
            'hsv', 
            'lab', 
            'rgb', 
            'hsl', 
            'hsi', 
            'hcl'
        ]
    },
    {
        name: LIGHTNESS_CORRECTION,
        type: 'checkbox',
        value: false
    },
    {
        name: CSS_GRADIENT_TYPE,
        type: 'select',
        items: [
            'linear', 
            'radial'
        ]
    },
    {
        name: POSITION,
        type: 'checkbox',
        value: false
    },
    {
        name: TOP,
        type: 'range',
        items: [1, 100, 50, 1]
    },
    {
        name: LEFT,
        type: 'range',
        items: [1, 100, 50, 1]
    },
    {
        name: SHAPE,
        type: 'select',
        items: [
            'ellipse', 
            'circle'
        ]
    },
    {
        name: EXTENT_KEYWORD,
        type: 'select',
        items: [
            'none', 
            'closest-side', 
            'closest-corner', 
            'farthest-side', 
            'farthest-corner'
        ]
    },
    {
        name: WITH_ANGLE,
        type: 'checkbox',
        value: false
    },
    {
        name: ANGLE,
        type: 'range',
        items: [1, 360, 180, 1]
    },
    {
        name: `Color 1`,
        type: 'color',
        value: defaultColors[0]
    },
    {
        name: `Color 2`,
        type: 'color',
        value: defaultColors[1]
    }
]

const gradientController = {
    addColor: () => {
        let g = listController.getCurrentGradient()
        if (g.index < g.maxIndex) {
            g.index++
            gradientPanel.color(`Color ${g.index + 1}`, '#ffcc00')
        }
    },
    removeColor: () => {
        let g = listController.getCurrentGradient()
        if (g.index > 1) {
            gradientPanel.remove(`Color ${g.index + 1}`)
            g.index--
        }
    },
    modifyRules: () => {
        const v = gradientPanel.getActiveValues()
        
        switch(v[INTERPOLATION]) {
            case 'linear':
                gradientPanel.show(MODE)
            break
            case 'bezier':
                gradientPanel.hide(MODE)
            break
        }
        
        switch (v[CSS_GRADIENT_TYPE]) {
            case 'linear':
                gradientPanel.show(WITH_ANGLE)
                gradientPanel.hide(SHAPE)
                gradientPanel.hide(EXTENT_KEYWORD)
                gradientPanel.hide(POSITION)
                gradientPanel.hide(TOP)
                gradientPanel.hide(LEFT)
                if (v[WITH_ANGLE] === true) {
                    gradientPanel.show(ANGLE)
                }
                if (v[WITH_ANGLE] === false) {
                    gradientPanel.hide(ANGLE)
                }
            break
            case 'radial':
                gradientPanel.hide(WITH_ANGLE)
                gradientPanel.hide(ANGLE)
                gradientPanel.show(POSITION)
                if (v[POSITION] === true) {
                    gradientPanel.show(TOP)
                    gradientPanel.show(LEFT)
                }
                if (v[POSITION] === false) {
                    gradientPanel.hide(TOP)
                    gradientPanel.hide(LEFT)
                }
                if (v[SHAPE] == 'circle') {
                    gradientPanel.hide(EXTENT_KEYWORD)
                }
                if (v[SHAPE] == 'ellipse') {
                    gradientPanel.show(EXTENT_KEYWORD)
                }
                if (v[EXTENT_KEYWORD] !== 'none') {
                    gradientPanel.hide(SHAPE)
                }
                if (v[EXTENT_KEYWORD] === 'none') {
                    gradientPanel.show(SHAPE)
                }
            break
        }
    },
    createBase: () => {
        const v = gradientPanel.getActiveValues()
        const colors = Object.keys(v)
            .filter(key => /Color [0-9]/.test(key) === true)
            .map(key => gradientPanel.getValue(key))

        let scale
        let base = []
        let endString
        
        switch (v[INTERPOLATION]) {
            case 'linear':
                if (v[MODE] !== 'none') {
                    if (v[LIGHTNESS_CORRECTION]) {
                        scale = chroma
                            .scale(colors)
                            .mode(v[MODE])
                            .correctLightness()
                    } else {
                        scale = chroma
                            .scale(colors)
                            .mode(v[MODE])
                    }
                } else {
                    if (v[LIGHTNESS_CORRECTION]) {
                        scale = chroma
                            .scale(colors)
                            .correctLightness()
                    } else {
                        scale = chroma
                            .scale(colors)
                    }
                }
            break
            case 'bezier':
                    if (v[LIGHTNESS_CORRECTION]) {
                        scale = chroma
                            .bezier(colors)
                            .scale()
                            .correctLightness()
                    } else {
                        scale = chroma
                            .bezier(colors)
                    }
            break
        }
        
        for (let i = 0; i < v[SAMPLES]; i++) {
            base.push(scale(i / v[SAMPLES]));
        }
        return utils.stringifyResults(base, v[OPACITY])
    },
    createSingleString: base => {
        const v = gradientPanel.getActiveValues()
        let endString
        let $angle = v[ANGLE] && v[WITH_ANGLE] && v[CSS_GRADIENT_TYPE] === 'linear' ? v[ANGLE] + 'deg, ' : ''
        let $shape = () => {
            if (v[CSS_GRADIENT_TYPE] === 'radial') {
                if (v[SHAPE] && v[SHAPE] !== 'ellipse' && v[POSITION] === false) {
                    return v[SHAPE] + ', '
                }
                if (v[SHAPE] && v[SHAPE] !== 'ellipse' && v[POSITION] === true) {
                    return `${v[SHAPE]} at ${v[LEFT]}% ${v[TOP]}%, `
                }
                if (v[SHAPE] && v[SHAPE] === 'ellipse' && v[POSITION] === true && v[EXTENT_KEYWORD] === 'none') {
                    return `${v[SHAPE]} at ${v[LEFT]}% ${v[TOP]}%, `
                }
                if (v[SHAPE] && v[SHAPE] === 'ellipse' && v[POSITION] === true && v[EXTENT_KEYWORD] !== 'none') {
                    return ''
                }
            }
            return ''
        }
        let $extent = () => {
            let afterExtent = v[POSITION] === true ? ` at ${v[LEFT]}% ${v[TOP]}%, ` : ', '
            if (v[EXTENT_KEYWORD] !== 'none') {
                return v[EXTENT_KEYWORD] + afterExtent
            }
            return ''
        }
        endString = `${v[CSS_GRADIENT_TYPE]}-gradient(${$angle}${$shape()}${$extent()}${base})`

        return endString
    },
    render: (result, endString, samples, width) => {
        while (base.firstChild) {
            if (base.firstChild.nodeName !== 'h1') {
                base.removeChild(
                    base.firstChild
                )
            }
        }
        for (let i = 0; i < result.length; i++) {
            let style
            let color = result[i];
            let div = document.createElement('div')
            if (width < 867) {
                style = `background-color: ${color}; width: 100vw; height: ${100 / samples}%`
            } else {
                style = `background-color: ${color}; height: ${100 / samples}vh;`
            }
            div.style = style
            div.innerHTML = `<span class="sample">${color}</span>`
            base.appendChild(div)
        }
        single.style.background = endString
    },
    display: () => {
        let base = gradientController.createBase()
        let css = gradientController.createSingleString(base)
        let samples = gradientPanel.getValue(SAMPLES)
        gradientController.render(base, css, samples, window.innerWidth)
        gradientController.save(css)
    },
    save: css => {
        let index = listPanel.getIndex(LIST)
        listState.gradients[index].config = gradientPanel.getConfig()
        listState.gradients[index].values = gradientPanel.getActiveValues()
        listState.gradients[index].css = css ? css : ''
    },
    decide: currentGradient => {
        if (Object.keys(currentGradient.values).length > 0) {
            gradientPanel.loadConfig(currentGradient.config)
            for (let key in currentGradient.values) {
                if (currentGradient.config[key].type === 'select') {
                    let value = currentGradient.values[key]
                    let index = currentGradient.config[key].items.findIndex(
                        (el, i, arr) => el === value
                    )
                    gradientPanel.setIndex(key, index)
                } else {
                    gradientPanel.setValue(key, currentGradient.values[key])
                }
            }
        } else {
            gradientPanel.loadConfig(gradientInitialConfig)
        }
    },
    multiple: () => {
        let css = ''
        if (listState.gradients.length < 2) {
            css = listState.gradients[0].css
        } else {
            listState.gradients.forEach(g => {
                css += listState.gradients.indexOf(g) === listState.gradients.length - 1 ?
                    `${g.css}` : `${g.css}, `
            })
        }
        multiple.style.background = css
        multiple.style.backgroundBlendMode = listPanel.getValue(BACKGROUND_BLEND_MODE)
    }
}

const createGradient = () => {
    if (gradientPanel !== undefined) {
        gradientPanel.destroy()
        gradientPanel = undefined
    }
    let g = listController.getCurrentGradient()
    gradientPanel = new SmartSettings(g.name, 220, 5)
    gradientController.decide(g)
    gradientController.display()
    gradientController.modifyRules()
    gradientController.multiple()
    gradientPanel.watch(() => {
        gradientController.display()
        gradientController.modifyRules()
        gradientController.multiple()
    })
}

// list
let listPanel

const emptyGradient = name => {
    return {
        name: name,
        index: 1,
        maxIndex: 3,
        css: '',
        config: gradientInitialConfig,
        values: {}
    }
}

const gradientNames = () => listState.gradients.map(g => g.name)

const listState = {
    gradients: [emptyGradient('Gradient 1')],
    index: 0,
    maxIndex: 2
}

const listConfig = [
    {
        type: "button",
        name: ADD_GRADIENT,
        callback: () => listController.addGradient()
    },
    {
        type: "button",
        name: REMOVE_GRADIENT,
        callback: () => listController.removeGradient()
    },
    {
        type: 'select',
        name: LIST,
        items: gradientNames()
    },
    {
        type: 'select',
        name: BACKGROUND_BLEND_MODE,
        items: [
            'normal', 
            'multiply', 
            'screen', 
            'overlay', 
            'darken', 
            'lighten', 
            'color-dodge', 
            'color-burn', 
            'hard-light', 
            'soft-light', 
            'difference', 
            'exclusion',
            'hue', 
            'saturation', 
            'color', 
            'luminosity'
        ]
    }
]

const listController = {
    addGradient: () => {
        if (listState.index < listState.maxIndex) {
            listState.index++
            let gradient = emptyGradient(`Gradient ${listState.index + 1}`)
            listState.gradients.push(gradient)
            listPanel.setItems(LIST, gradientNames())
        }
    },
    removeGradient: () => {
        if (listState.index > 0) {
            listState.gradients = listState.gradients.filter(g => {
                return listState.gradients.indexOf(g) !== listState.gradients.length - 1
            })
            listState.index--
            listPanel.setItems(LIST, gradientNames())
        }
    },
    modifyRules: () => {
        if (listState.index < 1) {
            listPanel.disable(LIST)
            listPanel.hide(REMOVE_GRADIENT)
        }
        if (listState.index >= 1) {
            listPanel.enable(LIST)
            listPanel.show(REMOVE_GRADIENT)
        }
        if (listState.index === listState.maxIndex) {
            listPanel.disable(ADD_GRADIENT)
        } else {
            listPanel.enable(ADD_GRADIENT)
        }
    },
    getCurrentGradient: () => {
        let i = listPanel.getIndex(LIST)
        return listState.gradients[i]
    }
}

const init = () => {
    overlay.classList.add('hide')
    listPanel = new SmartSettings('Layers', 5, 5)
    listPanel.loadConfig(listConfig)
    listPanel.watch(() => {
        listController.modifyRules()
        createGradient()
    })
    listController.modifyRules()
    createGradient()
}

overlayButton.addEventListener('click', e => init())

utils.showSingle()
utils.showMultiple()
utils.displayOnResize()
View Compiled
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.7/chroma.min.js
  2. https://unpkg.com/smartsettings@1.2.3/dist/smartsettings.umd.js