Browse Source

Merge pull request #2 from cjimmy/multiple-gradients

Add multiple layered gradients + invert colors
Jimmy Chion 3 years ago
parent
commit
78d757e9ae

+ 0 - 1
package-lock.json

@@ -5,7 +5,6 @@
   "requires": true,
   "packages": {
     "": {
-      "name": "grainy-gradients",
       "version": "0.1.0",
       "dependencies": {
         "@ant-design/icons": "^4.6.3",

+ 36 - 97
src/components/CodeBlocks/Css.tsx

@@ -1,49 +1,36 @@
-import { Form, Switch, Select } from 'antd';
+import { Form, Switch } from 'antd';
 import hljs from 'highlight.js/lib/core';
 import React from 'react';
-import styled from 'styled-components';
-import shallow from 'zustand/shallow';
-import { breakpoints } from '../layout';
 import { rgbToString } from './Output';
-import { SectionTitle, SliderInput, ChromePickerColor, ColorPicker } from './subcomponents';
-import { ColorType, useInputStore } from '~/components/store';
+import { SectionTitle } from './subcomponents';
+import { GradientControls } from '~/components/GradientControls';
+import { AnyGradientType, useInputStore } from '~/components/store';
 
 export const CssControls: React.FC = () => {
-  const [setCssProps, cssProps, filterProps] = useInputStore(
-    (state) => [state.setCssProps, state.cssProps, state.filterProps],
-    shallow
-  );
+  const [cssProps, setCssProps, filterProps] = useInputStore((state) => [
+    state.cssProps,
+    state.setCssProps,
+    state.filterProps,
+  ]);
   const { brightness, contrast } = filterProps;
-  const { gradientType, color1, color2, angle, showTransparency, posX, posY } = cssProps;
-
-  const setValues = (key: string, value: number | string | ColorType | boolean) => {
-    const toSet = {
-      gradientType,
-      color1,
-      color2,
-      angle,
-      showTransparency,
-      posX,
-      posY,
-    };
-    toSet[key] = value;
-    setCssProps(toSet);
-  };
+  const { gradients, showTransparency } = cssProps;
 
-  const gradientFirstParam =
-    gradientType === 'linear'
-      ? `${angle}deg`
-      : gradientType === 'radial'
-      ? `circle at ${posX}% ${posY}%`
-      : `from ${angle}deg at ${posX}% ${posY}%`;
+  const gradientsString = gradients
+    .filter((grad) => grad.isVisible)
+    .map(
+      (grad) =>
+        `${grad.type}-gradient(${getGradientFirstParam(grad)}, ${rgbToString(
+          grad.stops[0].color
+        )}, ${rgbToString(grad.stops[1].color)})`
+    );
 
   const gradientCss = `/* css gradient: second layer */
 {
   width: 250px;
   height: 250px;
-  background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(color1)}, ${rgbToString(
-    color2
-  )})${showTransparency ? ', url(/checkers.png)' : ''};
+  background: \n\t${gradientsString.join(',\n\t')}${
+    showTransparency ? ',\n\turl(/checkers.png)' : ''
+  };
   /* filter: contrast(${contrast}%) brightness(${brightness}%); */
 }`;
   return (
@@ -58,77 +45,29 @@ export const CssControls: React.FC = () => {
         />
       </pre>
       <Form>
-        <GradientControls>
-          <Form.Item label="Gradient type">
-            <Select
-              value={gradientType}
-              onChange={(v: string) => setValues('gradientType', v)}
-              style={{ width: 120 }}
-            >
-              <Select.Option value="linear">linear</Select.Option>
-              <Select.Option value="radial">radial</Select.Option>
-              <Select.Option value="conic">conic</Select.Option>
-            </Select>
-          </Form.Item>
-          <ColorPicker
-            label="Color 1"
-            color={color1}
-            style={{ padding: 0 }}
-            onChange={(c: ChromePickerColor) => setValues('color1', c.rgb)}
-          />
-          <ColorPicker
-            label="Color 2"
-            color={color2}
-            onChange={(c: ChromePickerColor) => setValues('color2', c.rgb)}
-          />
-        </GradientControls>
-
-        {['linear', 'conic'].includes(gradientType) && (
-          <SliderInput
-            label="angle"
-            name="angle"
-            min={0}
-            max={360}
-            tipFormatter={(v) => `${v}°`}
-            onChange={(val: number) => setValues('angle', val)}
-            value={typeof angle === 'number' ? angle : 0}
-          />
-        )}
-        {['radial', 'conic'].includes(gradientType) && (
-          <>
-            <SliderInput
-              label="position X"
-              name="position X"
-              min={-50}
-              max={150}
-              onChange={(val: number) => setValues('posX', val)}
-              value={typeof posX === 'number' ? posX : 0}
-            />
-            <SliderInput
-              label="position Y"
-              name="position Y"
-              min={-50}
-              max={150}
-              onChange={(val: number) => setValues('posY', val)}
-              value={typeof posY === 'number' ? posY : 0}
-            />
-          </>
-        )}
-        <Form.Item label="Show checkered">
+        <Form.Item label="Show checkered background" style={{ marginBottom: 4 }}>
           <Switch
             size="small"
             checked={showTransparency}
-            onChange={(e) => setValues('showTransparency', e)}
+            onChange={(e) => {
+              setCssProps({ ...cssProps, showTransparency: e });
+            }}
           />
         </Form.Item>
       </Form>
+      <GradientControls />
     </div>
   );
 };
 
-const GradientControls = styled.div`
-  display: flex;
-  @media screen and (max-width: ${breakpoints.md - 1}px) {
-    flex-direction: column;
+export const getGradientFirstParam = (grad: AnyGradientType): string => {
+  if ('angle' in grad && grad.type === 'linear') {
+    return `${grad.angle}deg`;
+  } else if (grad.type === 'radial' && 'posX' in grad) {
+    return `circle at ${grad.posX}% ${grad.posY}%`;
+  } else if (grad.type === 'conic' && 'angle' in grad && 'posX' in grad) {
+    return `from ${grad.angle}deg at ${grad.posX}% ${grad.posY}%`;
+  } else {
+    return '';
   }
-`;
+};

+ 27 - 16
src/components/CodeBlocks/Filter.tsx

@@ -1,3 +1,4 @@
+import { getGradientFirstParam } from '.';
 import { Form, Switch } from 'antd';
 import hljs from 'highlight.js/lib/core';
 import React, { useState } from 'react';
@@ -11,17 +12,17 @@ export const FilterControls: React.FC = () => {
     (state) => [state.svgProps, state.cssProps, state.filterProps, state.setFilterProps],
     shallow
   );
-  const { contrast, brightness } = filterProps;
+  const { contrast, brightness, invert } = filterProps;
   const [inlineSvg, setInlineSvg] = useState(false);
   const { size, baseFrequency, numOctaves } = svgProps;
 
-  const { gradientType, angle, color1, color2, posX, posY } = cssProps;
-  const gradientFirstParam =
-    gradientType === 'linear'
-      ? `${angle}deg`
-      : gradientType === 'radial'
-      ? `circle at ${posX}% ${posY}%`
-      : `from ${angle}deg at ${posX}% ${posY}%`;
+  const { gradients } = cssProps;
+  const gradientsString = gradients.map((grad) => {
+    return `${grad.type}-gradient(${getGradientFirstParam(grad)}, ${rgbToString(
+      grad.stops[0].color
+    )}, ${rgbToString(grad.stops[1].color)})`;
+  });
+
   const cleanSvgString = `<svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'><filter id='noiseFilter'><feTurbulence type='fractalNoise' baseFrequency='${baseFrequency}' numOctaves='${numOctaves}' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(#noiseFilter)'/></svg>`;
   const inlineSvgString = `url("data:image/svg+xml,${cleanSvgString.replace(
     symbols,
@@ -31,10 +32,10 @@ export const FilterControls: React.FC = () => {
 {
   width: 250px;
   height: 250px;
-  background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(color1)}, ${rgbToString(
-    color2
-  )}), ${inlineSvg ? inlineSvgString : 'url(/👆/that/noise.svg)'};
-  filter: contrast(${contrast}%) brightness(${brightness}%);
+  filter: contrast(${contrast}%) brightness(${brightness}%)${invert ? ' invert(100%)' : ''};
+  background: \n\t${gradientsString.join(',\n\t')},\n\t${
+    inlineSvg ? inlineSvgString : 'url(/👆/that/noise.svg)'
+  };
 }
   `;
 
@@ -59,9 +60,8 @@ export const FilterControls: React.FC = () => {
           tipFormatter={(v) => `${v}%`}
           onChange={(val: number) =>
             setFilterProps({
-              brightness,
+              ...filterProps,
               contrast: val,
-              inlineSvg,
             })
           }
           value={typeof contrast === 'number' ? contrast : 10}
@@ -75,13 +75,24 @@ export const FilterControls: React.FC = () => {
           tipFormatter={(v) => `${v}%`}
           onChange={(val: number) =>
             setFilterProps({
+              ...filterProps,
               brightness: val,
-              contrast,
-              inlineSvg,
             })
           }
           value={typeof brightness === 'number' ? brightness : 0}
         />
+        <Form.Item label="Invert" style={{ marginBottom: 6 }}>
+          <Switch
+            size="small"
+            checked={invert}
+            onChange={(v) =>
+              setFilterProps({
+                ...filterProps,
+                invert: v,
+              })
+            }
+          />
+        </Form.Item>
         <Form.Item label="Inline the SVG">
           <Switch size="small" checked={inlineSvg} onChange={(e) => setInlineSvg(e)} />
         </Form.Item>

+ 25 - 23
src/components/CodeBlocks/Output.tsx

@@ -1,6 +1,6 @@
+import { getGradientFirstParam } from '.';
 import React from 'react';
 import styled from 'styled-components';
-import shallow from 'zustand/shallow';
 import { breakpoints } from '~/components/layout';
 import { ColorType, useInputStore } from '~/components/store';
 
@@ -9,19 +9,15 @@ export const symbols = /[\r\n%#()<>?[\\\]^`{|}]/g;
 export const rgbToString = ({ r, g, b, a }: ColorType) => `rgba(${r},${g},${b},${a})`;
 
 const Output: React.FC = () => {
-  const [svgProps, cssProps, filterProps] = useInputStore(
-    (state) => [state.svgProps, state.cssProps, state.filterProps],
-    shallow
-  );
+  const [svgProps, cssProps, filterProps] = useInputStore((state) => [
+    state.svgProps,
+    state.cssProps,
+    state.filterProps,
+  ]);
   const { size, baseFrequency, numOctaves } = svgProps;
-  const { gradientType, color1, color2, angle, showTransparency, posX, posY } = cssProps;
-  const { brightness, contrast } = filterProps;
-  const gradientFirstParam =
-    gradientType === 'linear'
-      ? `${angle}deg`
-      : gradientType === 'radial'
-      ? `circle at ${posX}% ${posY}%`
-      : `from ${angle}deg at ${posX}% ${posY}%`;
+  const { gradients, showTransparency } = cssProps;
+  const { brightness, contrast, invert } = filterProps;
+
   const svgString = `<!-- svg: first layer -->
 <svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'>
   <filter id='noiseFilter'>
@@ -35,25 +31,31 @@ const Output: React.FC = () => {
   <rect width='100%' height='100%' filter='url(#noiseFilter)'/>
 </svg>`;
 
+  const gradientsString = gradients
+    .filter((grad) => grad.isVisible)
+    .map(
+      (grad) =>
+        `${grad.type}-gradient(${getGradientFirstParam(grad)}, ${rgbToString(
+          grad.stops[0].color
+        )}, ${rgbToString(grad.stops[1].color)})`
+    );
+
   const gradientCss = `/* css gradient: second layer */
 {
   width: 250px;
   height: 250px;
-  background: 
-    ${gradientType}-gradient(${gradientFirstParam}, 
-      ${rgbToString(color1)}, ${rgbToString(color2)})${
-    showTransparency ? ', url(/checkers.png)' : ''
-  };
-  /* filter: contrast(${contrast}%) brightness(${brightness}%); */
+  background: ${gradientsString.join(', ')} ${showTransparency ? ', url(/checkers.png)' : ''};
+  /* filter: contrast(${contrast}%) brightness(${brightness}%)${invert ? ' invert(100%)' : ''}; */
 }`;
 
   const liveCss = `
 width: 250px;
 height: 250px;
-background: ${gradientType}-gradient(${gradientFirstParam}, ${rgbToString(color1)}, ${rgbToString(
-    color2
-  )}), url("data:image/svg+xml,${svgString.replace(symbols, encodeURIComponent)}");
-filter: contrast(${contrast}%) brightness(${brightness}%);
+background: ${gradientsString.join(', ')}, url("data:image/svg+xml,${svgString.replace(
+    symbols,
+    encodeURIComponent
+  )}");
+filter: contrast(${contrast}%) brightness(${brightness}%)${invert ? ' invert(100%)' : ''};
 `;
 
   return (

+ 6 - 2
src/components/CodeBlocks/subcomponents/SliderInput.tsx

@@ -5,6 +5,8 @@ import { breakpoints } from '~/components/layout';
 interface ISliderInput {
   label: string;
   name: string;
+  id?: string;
+  disabled?: boolean;
   onChange: (val: number) => void;
   value: number;
   min: number;
@@ -13,9 +15,9 @@ interface ISliderInput {
   tipFormatter?: (val: number) => string;
 }
 export const SliderInput: React.FC<ISliderInput> = (props) => {
-  const { label, name, onChange, value, min, max, step, tipFormatter } = props;
+  const { label, name, id, onChange, value, min, max, step, tipFormatter, disabled } = props;
   return (
-    <Form.Item label={label} name={name} style={{ width: '100%', margin: 0 }}>
+    <Form.Item label={label} name={[name, id]} style={{ width: '100%', margin: 0 }}>
       <SliderAndInput>
         <SliderContainer>
           <Slider
@@ -25,6 +27,7 @@ export const SliderInput: React.FC<ISliderInput> = (props) => {
             max={max}
             onChange={onChange}
             value={value}
+            disabled={disabled}
           />
         </SliderContainer>
         <InputNumber
@@ -34,6 +37,7 @@ export const SliderInput: React.FC<ISliderInput> = (props) => {
           value={value}
           onChange={onChange}
           formatter={tipFormatter}
+          disabled={disabled}
         />
       </SliderAndInput>
     </Form.Item>

+ 0 - 1
src/components/CodeBlocks/subcomponents/index.tsx

@@ -1,4 +1,3 @@
-export * from './ColorPicker';
 export * from './Reset';
 export * from './SectionTitle';
 export * from './SliderInput';

+ 0 - 0
src/components/CodeBlocks/subcomponents/ColorPicker.tsx → src/components/GradientControls/ColorPicker.tsx


+ 57 - 0
src/components/GradientControls/GradientControls.tsx

@@ -0,0 +1,57 @@
+import { PlusOutlined } from '@ant-design/icons';
+import { Button } from 'antd';
+import React from 'react';
+import shallow from 'zustand/shallow';
+import { useInputStore, AnyGradientType, getRandomGradient } from '../store';
+import { GradientRow } from './GradientRow';
+
+export const GradientControls: React.FC = () => {
+  const [cssProps, setCssProps] = useInputStore(
+    (state) => [state.cssProps, state.setCssProps],
+    shallow
+  );
+
+  const updateStateOfSelf = React.useCallback(
+    (index: number, newData: AnyGradientType) => {
+      cssProps.gradients[index] = newData;
+      setCssProps({ ...cssProps });
+    },
+    [cssProps, setCssProps]
+  );
+
+  const deleteSelf = React.useCallback(
+    (index: number) => {
+      const newGradients = [
+        ...cssProps.gradients.slice(0, index),
+        ...cssProps.gradients.slice(index + 1),
+      ];
+      setCssProps({ ...cssProps, gradients: newGradients });
+    },
+    [cssProps, setCssProps]
+  );
+
+  const pushNewGradient = () => {
+    cssProps.gradients.push(getRandomGradient());
+    setCssProps(cssProps);
+  };
+
+  const gradientInterface = cssProps.gradients.map((grad, i) => (
+    <GradientRow
+      key={i}
+      gradient={grad}
+      nGradients={cssProps.gradients.length}
+      updateSelf={updateStateOfSelf}
+      deleteSelf={deleteSelf}
+      selfIndex={i}
+    />
+  ));
+
+  return (
+    <div>
+      {gradientInterface}
+      <Button icon={<PlusOutlined />} onClick={pushNewGradient}>
+        Add gradient
+      </Button>
+    </div>
+  );
+};

+ 162 - 0
src/components/GradientControls/GradientRow.tsx

@@ -0,0 +1,162 @@
+import { EyeOutlined, EyeInvisibleOutlined, DeleteOutlined } from '@ant-design/icons';
+import { Form, Select, Tooltip } from 'antd';
+import React from 'react';
+import styled from 'styled-components';
+import { ColorPicker, ChromePickerColor } from './ColorPicker';
+import { SliderInput } from '~/components/CodeBlocks/subcomponents';
+import {
+  AnyGradientType,
+  ColorStopType,
+  ConicGradientType,
+  RadialGradientType,
+} from '~/components/store';
+
+interface IGradientRow {
+  gradient: AnyGradientType;
+  selfIndex: number;
+  nGradients: number;
+  updateSelf: (index: number, newData: AnyGradientType) => void;
+  deleteSelf: (index: number) => void;
+}
+export const GradientRow: React.FC<IGradientRow> = ({
+  gradient,
+  selfIndex,
+  nGradients,
+  updateSelf,
+  deleteSelf,
+}) => {
+  const { type: gradientType, isVisible, stops } = gradient;
+
+  const updateProp = (k: string, v: string | boolean | ColorStopType[] | number) => {
+    const newGradient = Object.assign({}, gradient);
+    newGradient[k] = v;
+    // add default values for radial and conic gradients
+    if (k === 'type' && !('posX' in newGradient)) {
+      (newGradient as RadialGradientType | ConicGradientType).posX = 50;
+      (newGradient as RadialGradientType | ConicGradientType).posY = 50;
+    }
+    updateSelf(selfIndex, newGradient);
+  };
+
+  return (
+    <Container>
+      <Form name={`gradient-row-${selfIndex}`}>
+        <TopRow>
+          <TopRowLeft>
+            <Form.Item style={{ marginBottom: 0 }}>
+              <Select
+                value={gradientType}
+                onChange={(v: string) => updateProp('type', v)}
+                style={{ width: 130 }}
+                disabled={!isVisible}
+              >
+                <Select.Option value="linear">linear</Select.Option>
+                <Select.Option value="radial">radial</Select.Option>
+                <Select.Option value="conic">conic</Select.Option>
+              </Select>
+            </Form.Item>
+            <ColorPicker
+              label="Color 1"
+              color={stops[0].color}
+              style={{ padding: 0 }}
+              onChange={(c: ChromePickerColor) =>
+                updateProp('stops', [{ color: c.rgb, offset: 1 }, stops[1]])
+              }
+            />
+            <ColorPicker
+              label="Color 2"
+              color={stops[stops.length - 1].color}
+              onChange={(c: ChromePickerColor) =>
+                updateProp('stops', [stops[0], { color: c.rgb, offset: 1 }])
+              }
+            />
+          </TopRowLeft>
+          <TopRowRight>
+            <VisibilityIcon onClick={() => updateProp('isVisible', !isVisible)}>
+              {isVisible ? (
+                <Tooltip title="Hide">
+                  <EyeOutlined />
+                </Tooltip>
+              ) : (
+                <EyeInvisibleOutlined />
+              )}
+            </VisibilityIcon>
+            {nGradients > 1 && (
+              <VisibilityIcon onClick={() => deleteSelf(selfIndex)}>
+                <Tooltip title="Remove gradient">
+                  <DeleteOutlined />
+                </Tooltip>
+              </VisibilityIcon>
+            )}
+          </TopRowRight>
+        </TopRow>
+
+        {['linear', 'conic'].includes(gradientType) && 'angle' in gradient && (
+          <SliderInput
+            label="angle"
+            name="angle"
+            id={`${selfIndex}`}
+            min={0}
+            max={360}
+            tipFormatter={(v) => `${v}°`}
+            onChange={(val: number) => updateProp('angle', val)}
+            value={typeof gradient.angle === 'number' ? gradient.angle : 0}
+            disabled={!isVisible}
+          />
+        )}
+        {['radial', 'conic'].includes(gradientType) && 'posX' in gradient && (
+          <>
+            <SliderInput
+              label="position X"
+              name="position X"
+              id={`${selfIndex}`}
+              min={-50}
+              max={150}
+              onChange={(val: number) => updateProp('posX', val)}
+              value={typeof gradient.posX === 'number' ? gradient.posX : 0}
+              disabled={!isVisible}
+            />
+            <SliderInput
+              label="position Y"
+              name="position Y"
+              id={`${selfIndex}`}
+              min={-50}
+              max={150}
+              onChange={(val: number) => updateProp('posY', val)}
+              value={typeof gradient.posY === 'number' ? gradient.posY : 0}
+              disabled={!isVisible}
+            />
+          </>
+        )}
+      </Form>
+    </Container>
+  );
+};
+
+const VisibilityIcon = styled.div`
+  padding: 3px;
+  margin: 0 8px;
+  cursor: pointer;
+`;
+
+const TopRow = styled.div`
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+`;
+
+const TopRowLeft = styled.div`
+  display: flex;
+`;
+
+const TopRowRight = styled.div`
+  display: flex;
+`;
+
+const Container = styled.div`
+  background-color: #eee;
+  border: 1px solid #ccc;
+  padding: 12px;
+  margin: 12px 0;
+`;

+ 1 - 0
src/components/GradientControls/index.tsx

@@ -0,0 +1 @@
+export * from './GradientControls';

+ 104 - 65
src/components/store.tsx

@@ -1,5 +1,10 @@
 import create from 'zustand';
 
+function getRandomFrom<T>(arr: Array<T>): T {
+  const randIndex = Math.trunc(Math.random() * 10) % arr.length;
+  return arr[randIndex];
+}
+
 export type SvgPropsType = {
   size: number;
   baseFrequency: number;
@@ -7,94 +12,128 @@ export type SvgPropsType = {
 };
 
 export type ColorType = { r: number; g: number; b: number; a: number };
+export type AnyGradientType = LinearGradientType | RadialGradientType | ConicGradientType;
 
 export type CssPropsType = {
-  gradientType: string;
-  angle: number;
-  color1: ColorType;
-  color2: ColorType;
   showTransparency: boolean;
+  gradients: AnyGradientType[];
+};
+
+export type ColorStopType = {
+  color: ColorType;
+  offset: number;
+};
+
+type BaseGradientType = {
+  type: string;
+  stops: ColorStopType[];
+  isVisible: boolean;
+  isRepeating?: boolean;
+};
+
+export type LinearGradientType = BaseGradientType & {
+  angle: number;
+};
+
+export type RadialGradientType = BaseGradientType & {
   posX: number;
   posY: number;
 };
 
+export type ConicGradientType = BaseGradientType & {
+  posX: number;
+  posY: number;
+  angle: number;
+};
+
 export type FilterPropsType = {
   contrast: number;
   brightness: number;
-  inlineSvg: boolean;
+  invert: boolean;
 };
 
 export type InputState = {
   svgProps: SvgPropsType;
-  setSvgProps: ({ size, baseFrequency, numOctaves }: SvgPropsType) => void;
+  setSvgProps: (props: SvgPropsType) => void;
   cssProps: CssPropsType;
-  setCssProps: ({
-    gradientType,
-    angle,
-    color1,
-    color2,
-    showTransparency,
-    posX,
-    posY,
-  }: CssPropsType) => void;
+  setCssProps: (props: CssPropsType) => void;
   filterProps: FilterPropsType;
-  setFilterProps: ({ contrast, brightness, inlineSvg }: FilterPropsType) => void;
+  setFilterProps: (props: FilterPropsType) => void;
   resetAllProps: () => void;
 };
 
-export const useInputStore = create<InputState>((set) => ({
-  svgProps: {
-    size: 250,
-    baseFrequency: 0.65,
-    numOctaves: 3,
-  },
-  setSvgProps: ({ size, baseFrequency, numOctaves }) =>
-    set({
-      svgProps: { size, baseFrequency, numOctaves },
-    }),
+const initialSvgProps = {
+  size: 250,
+  baseFrequency: 0.65,
+  numOctaves: 3,
+};
 
-  cssProps: {
-    gradientType: 'linear',
-    angle: 112,
-    color1: { r: 0, g: 0, b: 255, a: 1 },
-    color2: { r: 0, g: 0, b: 0, a: 0 },
-    showTransparency: true,
+export const defaultGradient = {
+  type: 'linear',
+  isVisible: true,
+  angle: 0,
+  stops: [
+    {
+      color: { r: 0, g: 0, b: 255, a: 1 },
+      offset: 0,
+    },
+    {
+      color: { r: 0, g: 0, b: 0, a: 0 },
+      offset: 1,
+    },
+  ],
+};
+
+export function getRandomGradient(): AnyGradientType {
+  return {
+    type: getRandomFrom(['linear', 'radial', 'conic']),
+    isVisible: true,
+    angle: getRandomFrom([0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]),
     posX: 50,
     posY: 50,
-  },
-  setCssProps: ({ gradientType, angle, color1, color2, showTransparency, posX, posY }) =>
-    set({
-      cssProps: { gradientType, angle, color1, color2, showTransparency, posX, posY },
-    }),
-  filterProps: {
-    contrast: 170,
-    brightness: 1000,
-    inlineSvg: false,
-  },
-  setFilterProps: ({ contrast, brightness, inlineSvg }) =>
-    set({
-      filterProps: { contrast, brightness, inlineSvg },
-    }),
-  resetAllProps: () =>
-    set({
-      svgProps: {
-        size: 250,
-        baseFrequency: 0.65,
-        numOctaves: 3,
-      },
-      cssProps: {
-        gradientType: 'linear',
-        angle: 112,
-        color1: { r: 0, g: 0, b: 255, a: 1 },
-        color2: { r: 0, g: 0, b: 0, a: 0 },
-        showTransparency: true,
-        posX: 50,
-        posY: 50,
+    stops: [
+      {
+        color: {
+          r: getRandomFrom([0, 255]),
+          g: getRandomFrom([0, 255]),
+          b: getRandomFrom([0, 255]),
+          a: 1,
+        },
+        offset: 0,
       },
-      filterProps: {
-        contrast: 170,
-        brightness: 1000,
-        inlineSvg: false,
+      {
+        color: {
+          r: getRandomFrom([0, 255]),
+          g: getRandomFrom([0, 255]),
+          b: getRandomFrom([0, 255]),
+          a: 0,
+        },
+        offset: 1,
       },
+    ],
+  };
+}
+
+const initialCssProps = {
+  showTransparency: false,
+  gradients: [defaultGradient],
+};
+const initialFilterProps = {
+  contrast: 170,
+  brightness: 1000,
+  invert: false,
+};
+export const useInputStore = create<InputState>((set) => ({
+  svgProps: initialSvgProps,
+  setSvgProps: (props) => set({ svgProps: props }),
+  cssProps: initialCssProps,
+  setCssProps: (props) => set({ cssProps: props }),
+  filterProps: initialFilterProps,
+  setFilterProps: (props) => set({ filterProps: props }),
+  resetAllProps: () =>
+    set({
+      svgProps: initialSvgProps,
+      cssProps: initialCssProps,
+      filterProps: initialFilterProps,
     }),
 }));

+ 1 - 0
src/styles/globals.css

@@ -17,6 +17,7 @@ pre {
   padding: 12px;
   word-wrap: break-word;
   margin: 0;
+  border-radius: 2px;
 }
 
 .hljs {