├── src
├── vite-env.d.ts
├── counter.ts
├── utils.ts
├── main.ts
├── typescript.svg
├── style.css
└── gradient.ts
├── vite.config.ts
├── package.json
├── README.md
├── .gitignore
├── tsconfig.json
├── .github
└── workflows
│ └── main.yml
├── public
└── vite.svg
├── index.html
└── pnpm-lock.yaml
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 |
3 | export default defineConfig({
4 | base: '/gradient-picker/',
5 | })
--------------------------------------------------------------------------------
/src/counter.ts:
--------------------------------------------------------------------------------
1 | export function setupCounter(element: HTMLButtonElement) {
2 | let counter = 0
3 | const setCounter = (count: number) => {
4 | counter = count
5 | element.innerHTML = `count is ${counter}`
6 | }
7 | element.addEventListener('click', () => setCounter(counter + 1))
8 | setCounter(0)
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gradient-picker",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "typescript": "^4.9.3",
13 | "vite": "^4.0.0"
14 | }
15 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Gradient Picker
3 |
4 | A website that allows you to create your own gradient
5 |
6 | Open website
7 |
8 | 
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | export function createElement(tagName: K, attrs?: Record, styles?: Record): HTMLElementTagNameMap[K] {
3 | const el = document.createElement(tagName)
4 |
5 | for(const attr in attrs) {
6 | el.setAttribute(attr, attrs[attr])
7 | }
8 |
9 | for(const style in styles) {
10 | el.style.setProperty(style, styles[style])
11 | }
12 |
13 | return el
14 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ESNext", "DOM"],
7 | "moduleResolution": "Node",
8 | "strict": true,
9 | "resolveJsonModule": true,
10 | "isolatedModules": true,
11 | "esModuleInterop": true,
12 | "noEmit": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noImplicitReturns": true,
16 | "skipLibCheck": true
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build and deploy
2 | on:
3 | push:
4 | branches: [ master ]
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-node@v2
12 |
13 | - uses: pnpm/action-setup@v2.2.2
14 | with:
15 | version: 7.13.3
16 | run_install: true
17 |
18 | - name: Install and Build 🔧
19 | run: |
20 | pnpm run build
21 |
22 | - name: Deploy 🚀
23 | uses: JamesIves/github-pages-deploy-action@v4.2.5
24 | with:
25 | branch: gh-pages
26 | folder: ./dist
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { GradientPicker } from './gradient'
2 | import './style.css'
3 |
4 | const app = document.getElementById('app')!
5 | const preview = document.getElementById('preview')!
6 | const colorHandlers = document.createElement('div')
7 | colorHandlers.classList.add('color__handlers')
8 |
9 | // TODO: publish UI library
10 | new GradientPicker({
11 | el: app,
12 | previewEl: preview,
13 | colorHandlersEl: colorHandlers,
14 | })
15 |
16 | const copyCssButton = document.getElementById('copy-css')
17 | copyCssButton?.addEventListener('click', () => {
18 | let textarea = document.querySelector("#css") as HTMLTextAreaElement;
19 | navigator.clipboard.writeText(textarea.value);
20 |
21 | // Show "Copied!" text
22 | const copiedEl = document.getElementById('copied')
23 | copiedEl?.classList.add('show')
24 | setTimeout(() => copiedEl?.classList.remove('show'), 500);
25 | })
--------------------------------------------------------------------------------
/src/typescript.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Gradient Picker
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
GradientPicker
17 |
18 |
19 |
20 |
21 |
25 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | margin: 0;
4 | padding: 0;
5 | }
6 | :root {
7 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
8 | font-size: 16px;
9 | line-height: 24px;
10 | font-weight: 400;
11 |
12 | background-color: #242424;
13 |
14 | font-synthesis: none;
15 | text-rendering: optimizeLegibility;
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | -webkit-text-size-adjust: 100%;
19 | }
20 |
21 | .center-both {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | }
26 |
27 | a {
28 | font-weight: 500;
29 | color: #646cff;
30 | text-decoration: inherit;
31 | }
32 | a:hover {
33 | color: #535bf2;
34 | }
35 |
36 | body {
37 | margin: 0;
38 | min-width: 320px;
39 | min-height: 100vh;
40 | font-family: 'Noto Sans Tangsa';
41 | }
42 |
43 | .container {
44 | width: min(500px, 100%);
45 | }
46 |
47 | h1 {
48 | font-size: clamp(3em, 5vw, 4em);
49 | font-family: 'Kaushan Script', 'arial';
50 | color: white;
51 | line-height: 1.1;
52 | margin-bottom: 2rem;
53 | text-align: center;
54 | text-shadow: 3px 2px 5px #311e1e;
55 | }
56 |
57 | a {
58 | color: rgb(241, 241, 241);
59 | margin-top: 2rem;
60 | display: block;
61 | filter: drop-shadow(1px 3px 2px rgb(83, 83, 83));
62 | }
63 | a:hover {
64 | color: white;
65 | }
66 | #app {
67 | height: 100vh;
68 | width: 100%;
69 | margin: 0 auto;
70 | padding: 2rem;
71 | text-align: center;
72 | }
73 |
74 | .card {
75 | background-color: white;
76 | padding: 2rem 1rem 1rem;
77 | border-radius: 1rem;
78 | }
79 |
80 | .color-preview {
81 | height: 3rem;
82 | border: 1px solid rgb(188, 188, 188);
83 | border-radius: 50rem;
84 | box-shadow: 1px 1px 3px rgb(140, 138, 138);
85 | cursor: crosshair;
86 | position: relative;
87 | }
88 | .color__handlers {
89 | height: 100%;
90 | border-radius: 50rem;
91 | overflow: hidden;
92 | position: relative;
93 | z-index: 3;
94 | }
95 | .color__handler {
96 | height: 100%;
97 | position: absolute;
98 | left: var(--handler-position);
99 | width: 5px;
100 | transform: translateX(-2.5px);
101 | background-color: rgba(31, 30, 30, 0.623);
102 | }
103 |
104 | .color__handler:hover {
105 | cursor: col-resize;
106 | }
107 |
108 | .color__handler-remover {
109 | width: 1.2rem;
110 | border: 1px solid #555;
111 | height: 1.2rem;
112 | cursor: pointer;
113 | background-color: white;
114 | box-shadow: 1px 1px 2px #ccc;;
115 | border-radius: 50%;
116 | position: absolute;
117 | top: -1.5rem;
118 | left: var(--handler-position);
119 | transform: translateX(-50%)
120 | }
121 | .color__handler-remover:after {
122 | content: 'x';
123 | color: #555;
124 | position: absolute;
125 | left: 4px;
126 | top: -5px;
127 | }
128 | .color__handler-remover:before {
129 | content: '';
130 | top: 100%;
131 | /* background-color: rgb(72, 72, 72); */
132 | position: absolute;
133 | width: 1px;
134 | height: 20px;
135 | z-index: 1;
136 | }
137 | .color__handler-remover:hover {
138 | border-color: rgb(229, 64, 64);
139 | }
140 | .color__handler-remover:hover:after {
141 | color: rgb(229, 64, 64);
142 | }
143 | .color__input {
144 | border: none;
145 | border-radius: 50%;
146 | transform: translate(-2px,-2px);
147 | }
148 | .color__input-wrapper {
149 | width: 1.3rem;
150 | height: 1.3rem;
151 | border: 1px solid white;
152 | border-radius: 50%;
153 | overflow: hidden;
154 | border: .1rem solid white;
155 | outline: 1px solid #bbb;
156 | position: absolute;
157 | left: var(--handler-position);
158 | cursor: pointer;
159 | top: 110%;
160 | transform: translateX(-50%);
161 | }
162 | .options {
163 | display: flex;
164 | gap: 1rem;
165 | }
166 | select, textarea {
167 | padding: .5rem;
168 | width: 100%
169 | }
170 | select {
171 | background-color: white;
172 | border: 1px solid #928b8b;
173 | border-radius: .5rem;
174 | margin-top: 2rem;
175 | }
176 | .css-box__header {
177 | border: 1px solid #928b8b;
178 | display: flex;
179 | justify-content: space-between;
180 | padding: .5rem;
181 | margin-top: 1rem;;
182 | border-top-left-radius: .5rem;
183 | border-top-right-radius: .5rem;
184 | }
185 | .css-box__header h6 {
186 | margin: 0;
187 | }
188 | textarea {
189 | border: none;
190 | border-bottom-left-radius: .5rem;
191 | border: 1px solid #928b8b;
192 | border-top: none;
193 | border-bottom-right-radius: .5rem;
194 | }
195 | #copied {
196 | display: none;
197 | color: green;
198 | margin-right: .5rem;
199 | }
200 | .show {
201 | display: inline-block !important;
202 | }
203 | #copy-css {
204 | color: white;
205 | background-color: #535bf2;
206 | padding: .3rem .5rem;
207 | border-radius: .4rem;
208 | cursor: pointer;
209 | border: none;
210 | }
211 | #copy-css:hover {
212 | background-color: #6169ff;
213 | }
--------------------------------------------------------------------------------
/src/gradient.ts:
--------------------------------------------------------------------------------
1 | import { createElement } from "./utils"
2 |
3 | interface Props {
4 | el: HTMLElement
5 | previewEl: HTMLElement
6 | colorHandlersEl: HTMLElement
7 | }
8 |
9 | type GradientDirection = "top" | "left" | "center" | "bottom" | "right"
10 | type GradientType = "linear" | "radial"
11 | type GradientStop = {
12 | color: string
13 | position: number
14 | }
15 |
16 | export class GradientPicker {
17 | direction: GradientDirection = "right"
18 | el: Props['el']
19 | previewEl: Props['previewEl']
20 | colorHandlersEl: Props['colorHandlersEl']
21 | type: GradientType = "linear"
22 | stops: GradientStop[] = []
23 | isDragging = false
24 |
25 | constructor({ el, previewEl, colorHandlersEl }: Props) {
26 | this.el = el
27 | this.previewEl = previewEl
28 | this.colorHandlersEl = colorHandlersEl
29 | this.previewEl.append(this.colorHandlersEl)
30 | this.addColorStop("#3494E6", .5)
31 | this.addColorStop("#EC6EAD", 99)
32 |
33 | this.listener()
34 | }
35 |
36 | /**
37 | * Add color to the gradient
38 | * @param color The color string (HEX/RGB/any supported css color format)
39 | * @param position The position of the stop (0-100)
40 | */
41 | addColorStop(color: string, position: number) {
42 | this.stops.push({ color, position })
43 | this.createStopHandler(this.stops.length-1)
44 | this.updateElementBackground()
45 | }
46 |
47 | changeGradientType(type: GradientType) {
48 | this.type = type
49 | this.updateElementBackground()
50 | }
51 |
52 | getGradientString(type: GradientType = this.type, direction: GradientDirection = this.direction) {
53 | const round = (num: number) => Math.round(num * 100) / 100
54 | const colorConcat = [...this.stops]
55 | .sort((a,b) => a.position - b.position)
56 | .map(stop => ` ${stop.color} ${round(stop.position)}%`).join(',')
57 |
58 | if(type === 'radial') {
59 | const radialPositions: Record = {
60 | "bottom": "at center bottom",
61 | "center": "",
62 | "left": "at left center",
63 | "right": "at center right",
64 | "top": "at center top",
65 | }
66 | return `radial-gradient(circle ${radialPositions[direction]}, ${colorConcat})`
67 | }
68 |
69 | return `linear-gradient(to ${direction},${colorConcat})`
70 | }
71 |
72 | private updateElementBackground() {
73 | const gradientString = this.getGradientString()
74 | this.el.style.backgroundImage = gradientString
75 | this.previewEl.style.backgroundImage = this.getGradientString('linear', 'right')
76 | let cssTextbox = document.getElementById('css')!
77 | cssTextbox.textContent = gradientString
78 | }
79 |
80 | private createStopHandler(stopIndex: number) {
81 | const colorStop = this.stops[stopIndex]
82 |
83 | // Handler bar
84 | const handler = createElement('div', { class: 'color__handler', 'data-index': stopIndex.toString() }, { '--handler-position': `${colorStop.position}%` })
85 |
86 | // Handler remover
87 | const handlerButtons = createElement('div', { class: 'color__handler-buttons', 'data-index': stopIndex.toString() }, { '--handler-position': `${colorStop.position}%` })
88 | const handlerRemover = createElement('div', { class: 'color__handler-remover' })
89 |
90 | // Color picker
91 | const inputColorWrapper = createElement('div', {
92 | type: 'color',
93 | class: 'color__input-wrapper',
94 | })
95 | const inputColor = createElement('input', {
96 | type: 'color',
97 | class: 'color__input',
98 | value: colorStop.color
99 | })
100 | inputColorWrapper.append(inputColor)
101 |
102 | inputColor.addEventListener('input', e => this.onColorChange(e as InputEvent, stopIndex))
103 | handler.addEventListener('mousedown', e => this.onHandlerMouseDown(e))
104 | handler.addEventListener('mouseup', e => this.onHandlerMouseUp(e))
105 | this.previewEl.addEventListener('mousemove', e => this.onHandlerMouseMove(e))
106 | handlerRemover.addEventListener('click', () => {
107 | this.stops.splice(stopIndex, 1)
108 | handler.remove()
109 | handlerButtons.remove()
110 | this.updateElementBackground()
111 | })
112 |
113 | handlerButtons.append(handlerRemover, inputColorWrapper)
114 | this.colorHandlersEl.append(handler)
115 | this.previewEl.append(handlerButtons)
116 | }
117 |
118 | onHandlerMouseDown(event: MouseEvent) {
119 | let handlerEl = event.target as HTMLElement
120 | handlerEl.classList.add('active')
121 | this.isDragging = true
122 | }
123 |
124 | onHandlerMouseMove(event: MouseEvent) {
125 | if(!this.isDragging) return
126 |
127 | let handlerEl = document.querySelector('.color__handler.active')
128 | if(!handlerEl?.classList.contains('active')) return
129 | const stopIndex = ~~(handlerEl.getAttribute('data-index') || 0)
130 |
131 | const newStopPosition = this.getPercentage(event.clientX)
132 |
133 | if(newStopPosition < 0.5 || newStopPosition > 99.5) return
134 |
135 | this.changePosition(stopIndex, newStopPosition)
136 | this.updateElementBackground()
137 | }
138 |
139 | onHandlerMouseUp(event: MouseEvent) {
140 | let handlerEl = event.target as HTMLElement
141 | handlerEl.classList.remove('active')
142 | this.isDragging = false
143 | }
144 |
145 | changeColor(stopIndex: number, color: string) {
146 | this.stops[stopIndex].color = color
147 | }
148 |
149 | changePosition(stopIndex: number, position: number) {
150 | this.stops[stopIndex].position = position
151 | this.previewEl.querySelectorAll(`div[data-index='${stopIndex}']`).forEach((el) => (el as HTMLElement).style.setProperty('--handler-position', position+'%'))
152 | }
153 |
154 | onColorChange(event: InputEvent, index: number) {
155 | this.changeColor(index, (event.target as HTMLInputElement).value)
156 | this.updateElementBackground()
157 | }
158 |
159 | getPercentage(mouseX: number) {
160 | const rect = this.previewEl.getBoundingClientRect()
161 | const clickPosition = mouseX - rect.x
162 | const elementWidth = getComputedStyle(this.previewEl).width.slice(0, -2)
163 | const newStopPosition = clickPosition/~~elementWidth * 100
164 |
165 | return newStopPosition
166 | }
167 |
168 | listener() {
169 | this.previewEl.addEventListener('click', e => {
170 | if((e.target as HTMLElement).classList.contains('color__handler') || this.isDragging) return
171 | if(!this.colorHandlersEl.contains(e.target as HTMLElement)) return
172 |
173 | const newStopPosition = this.getPercentage(e.clientX)
174 |
175 | this.addColorStop("#333333", newStopPosition)
176 | })
177 | const directionInput = document.getElementById('direction') as HTMLInputElement
178 | const typeInput = document.getElementById('type') as HTMLInputElement
179 |
180 | directionInput?.addEventListener('input', () => {
181 | this.direction = directionInput.value as GradientDirection
182 | this.updateElementBackground()
183 | })
184 | typeInput?.addEventListener('input', () => {
185 | this.type = typeInput.value as GradientType
186 |
187 | this.updateElementBackground()
188 | })
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: 5.4
2 |
3 | specifiers:
4 | typescript: ^4.9.3
5 | vite: ^4.0.0
6 |
7 | devDependencies:
8 | typescript: 4.9.4
9 | vite: 4.0.4
10 |
11 | packages:
12 |
13 | /@esbuild/android-arm/0.16.17:
14 | resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==}
15 | engines: {node: '>=12'}
16 | cpu: [arm]
17 | os: [android]
18 | requiresBuild: true
19 | dev: true
20 | optional: true
21 |
22 | /@esbuild/android-arm64/0.16.17:
23 | resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==}
24 | engines: {node: '>=12'}
25 | cpu: [arm64]
26 | os: [android]
27 | requiresBuild: true
28 | dev: true
29 | optional: true
30 |
31 | /@esbuild/android-x64/0.16.17:
32 | resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==}
33 | engines: {node: '>=12'}
34 | cpu: [x64]
35 | os: [android]
36 | requiresBuild: true
37 | dev: true
38 | optional: true
39 |
40 | /@esbuild/darwin-arm64/0.16.17:
41 | resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==}
42 | engines: {node: '>=12'}
43 | cpu: [arm64]
44 | os: [darwin]
45 | requiresBuild: true
46 | dev: true
47 | optional: true
48 |
49 | /@esbuild/darwin-x64/0.16.17:
50 | resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==}
51 | engines: {node: '>=12'}
52 | cpu: [x64]
53 | os: [darwin]
54 | requiresBuild: true
55 | dev: true
56 | optional: true
57 |
58 | /@esbuild/freebsd-arm64/0.16.17:
59 | resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==}
60 | engines: {node: '>=12'}
61 | cpu: [arm64]
62 | os: [freebsd]
63 | requiresBuild: true
64 | dev: true
65 | optional: true
66 |
67 | /@esbuild/freebsd-x64/0.16.17:
68 | resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==}
69 | engines: {node: '>=12'}
70 | cpu: [x64]
71 | os: [freebsd]
72 | requiresBuild: true
73 | dev: true
74 | optional: true
75 |
76 | /@esbuild/linux-arm/0.16.17:
77 | resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==}
78 | engines: {node: '>=12'}
79 | cpu: [arm]
80 | os: [linux]
81 | requiresBuild: true
82 | dev: true
83 | optional: true
84 |
85 | /@esbuild/linux-arm64/0.16.17:
86 | resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==}
87 | engines: {node: '>=12'}
88 | cpu: [arm64]
89 | os: [linux]
90 | requiresBuild: true
91 | dev: true
92 | optional: true
93 |
94 | /@esbuild/linux-ia32/0.16.17:
95 | resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==}
96 | engines: {node: '>=12'}
97 | cpu: [ia32]
98 | os: [linux]
99 | requiresBuild: true
100 | dev: true
101 | optional: true
102 |
103 | /@esbuild/linux-loong64/0.16.17:
104 | resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==}
105 | engines: {node: '>=12'}
106 | cpu: [loong64]
107 | os: [linux]
108 | requiresBuild: true
109 | dev: true
110 | optional: true
111 |
112 | /@esbuild/linux-mips64el/0.16.17:
113 | resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==}
114 | engines: {node: '>=12'}
115 | cpu: [mips64el]
116 | os: [linux]
117 | requiresBuild: true
118 | dev: true
119 | optional: true
120 |
121 | /@esbuild/linux-ppc64/0.16.17:
122 | resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==}
123 | engines: {node: '>=12'}
124 | cpu: [ppc64]
125 | os: [linux]
126 | requiresBuild: true
127 | dev: true
128 | optional: true
129 |
130 | /@esbuild/linux-riscv64/0.16.17:
131 | resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==}
132 | engines: {node: '>=12'}
133 | cpu: [riscv64]
134 | os: [linux]
135 | requiresBuild: true
136 | dev: true
137 | optional: true
138 |
139 | /@esbuild/linux-s390x/0.16.17:
140 | resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==}
141 | engines: {node: '>=12'}
142 | cpu: [s390x]
143 | os: [linux]
144 | requiresBuild: true
145 | dev: true
146 | optional: true
147 |
148 | /@esbuild/linux-x64/0.16.17:
149 | resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==}
150 | engines: {node: '>=12'}
151 | cpu: [x64]
152 | os: [linux]
153 | requiresBuild: true
154 | dev: true
155 | optional: true
156 |
157 | /@esbuild/netbsd-x64/0.16.17:
158 | resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==}
159 | engines: {node: '>=12'}
160 | cpu: [x64]
161 | os: [netbsd]
162 | requiresBuild: true
163 | dev: true
164 | optional: true
165 |
166 | /@esbuild/openbsd-x64/0.16.17:
167 | resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==}
168 | engines: {node: '>=12'}
169 | cpu: [x64]
170 | os: [openbsd]
171 | requiresBuild: true
172 | dev: true
173 | optional: true
174 |
175 | /@esbuild/sunos-x64/0.16.17:
176 | resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==}
177 | engines: {node: '>=12'}
178 | cpu: [x64]
179 | os: [sunos]
180 | requiresBuild: true
181 | dev: true
182 | optional: true
183 |
184 | /@esbuild/win32-arm64/0.16.17:
185 | resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==}
186 | engines: {node: '>=12'}
187 | cpu: [arm64]
188 | os: [win32]
189 | requiresBuild: true
190 | dev: true
191 | optional: true
192 |
193 | /@esbuild/win32-ia32/0.16.17:
194 | resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==}
195 | engines: {node: '>=12'}
196 | cpu: [ia32]
197 | os: [win32]
198 | requiresBuild: true
199 | dev: true
200 | optional: true
201 |
202 | /@esbuild/win32-x64/0.16.17:
203 | resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==}
204 | engines: {node: '>=12'}
205 | cpu: [x64]
206 | os: [win32]
207 | requiresBuild: true
208 | dev: true
209 | optional: true
210 |
211 | /esbuild/0.16.17:
212 | resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==}
213 | engines: {node: '>=12'}
214 | hasBin: true
215 | requiresBuild: true
216 | optionalDependencies:
217 | '@esbuild/android-arm': 0.16.17
218 | '@esbuild/android-arm64': 0.16.17
219 | '@esbuild/android-x64': 0.16.17
220 | '@esbuild/darwin-arm64': 0.16.17
221 | '@esbuild/darwin-x64': 0.16.17
222 | '@esbuild/freebsd-arm64': 0.16.17
223 | '@esbuild/freebsd-x64': 0.16.17
224 | '@esbuild/linux-arm': 0.16.17
225 | '@esbuild/linux-arm64': 0.16.17
226 | '@esbuild/linux-ia32': 0.16.17
227 | '@esbuild/linux-loong64': 0.16.17
228 | '@esbuild/linux-mips64el': 0.16.17
229 | '@esbuild/linux-ppc64': 0.16.17
230 | '@esbuild/linux-riscv64': 0.16.17
231 | '@esbuild/linux-s390x': 0.16.17
232 | '@esbuild/linux-x64': 0.16.17
233 | '@esbuild/netbsd-x64': 0.16.17
234 | '@esbuild/openbsd-x64': 0.16.17
235 | '@esbuild/sunos-x64': 0.16.17
236 | '@esbuild/win32-arm64': 0.16.17
237 | '@esbuild/win32-ia32': 0.16.17
238 | '@esbuild/win32-x64': 0.16.17
239 | dev: true
240 |
241 | /fsevents/2.3.2:
242 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
243 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
244 | os: [darwin]
245 | requiresBuild: true
246 | dev: true
247 | optional: true
248 |
249 | /function-bind/1.1.1:
250 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
251 | dev: true
252 |
253 | /has/1.0.3:
254 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
255 | engines: {node: '>= 0.4.0'}
256 | dependencies:
257 | function-bind: 1.1.1
258 | dev: true
259 |
260 | /is-core-module/2.11.0:
261 | resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
262 | dependencies:
263 | has: 1.0.3
264 | dev: true
265 |
266 | /nanoid/3.3.4:
267 | resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
268 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
269 | hasBin: true
270 | dev: true
271 |
272 | /path-parse/1.0.7:
273 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
274 | dev: true
275 |
276 | /picocolors/1.0.0:
277 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
278 | dev: true
279 |
280 | /postcss/8.4.21:
281 | resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
282 | engines: {node: ^10 || ^12 || >=14}
283 | dependencies:
284 | nanoid: 3.3.4
285 | picocolors: 1.0.0
286 | source-map-js: 1.0.2
287 | dev: true
288 |
289 | /resolve/1.22.1:
290 | resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
291 | hasBin: true
292 | dependencies:
293 | is-core-module: 2.11.0
294 | path-parse: 1.0.7
295 | supports-preserve-symlinks-flag: 1.0.0
296 | dev: true
297 |
298 | /rollup/3.10.0:
299 | resolution: {integrity: sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==}
300 | engines: {node: '>=14.18.0', npm: '>=8.0.0'}
301 | hasBin: true
302 | optionalDependencies:
303 | fsevents: 2.3.2
304 | dev: true
305 |
306 | /source-map-js/1.0.2:
307 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
308 | engines: {node: '>=0.10.0'}
309 | dev: true
310 |
311 | /supports-preserve-symlinks-flag/1.0.0:
312 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
313 | engines: {node: '>= 0.4'}
314 | dev: true
315 |
316 | /typescript/4.9.4:
317 | resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
318 | engines: {node: '>=4.2.0'}
319 | hasBin: true
320 | dev: true
321 |
322 | /vite/4.0.4:
323 | resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
324 | engines: {node: ^14.18.0 || >=16.0.0}
325 | hasBin: true
326 | peerDependencies:
327 | '@types/node': '>= 14'
328 | less: '*'
329 | sass: '*'
330 | stylus: '*'
331 | sugarss: '*'
332 | terser: ^5.4.0
333 | peerDependenciesMeta:
334 | '@types/node':
335 | optional: true
336 | less:
337 | optional: true
338 | sass:
339 | optional: true
340 | stylus:
341 | optional: true
342 | sugarss:
343 | optional: true
344 | terser:
345 | optional: true
346 | dependencies:
347 | esbuild: 0.16.17
348 | postcss: 8.4.21
349 | resolve: 1.22.1
350 | rollup: 3.10.0
351 | optionalDependencies:
352 | fsevents: 2.3.2
353 | dev: true
354 |
--------------------------------------------------------------------------------