@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