├── .gitignore
├── README.md
├── draggable.css
├── draggable.js
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── public
├── bee.png
├── bumblebee.png
├── caterpillar.png
├── daisy.png
├── flower.png
├── leaf.png
├── saffron.png
├── sun.png
└── sunflower.png
├── style.css
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .vscode
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # draggable.js
3 |
4 | A lightweight JavaScript library that adds drag, resize, and rotate functionality to HTML elements.
5 |
6 | ## Setup
7 |
8 | 1. Include the required files:
9 |
10 | ```html
11 |
12 |
13 |
14 |
15 |
Drag me
16 |
17 | ```
18 |
19 | 2. Add the `draggable` class to any element you want to make interactive:
20 |
21 |
22 |
23 | ## Features
24 |
25 | - Drag elements by clicking/touching and moving
26 | - Resize and rotate using the handle in the bottom right corner
27 | - Multi-touch support for mobile devices
28 | - Works with both images and other HTML elements
29 | - Transparent image areas are click-through
30 |
31 | ## Callbacks
32 |
33 | You can hook into drag events using these functions:
34 |
35 | ```javascript
36 | import { setDragStartCallback, setDragMoveCallback, setDragEndCallback } from './draggable.js';
37 | setDragStartCallback((element, x, y, scale, angle) => {
38 | // Called when dragging starts
39 | });
40 | setDragMoveCallback((element, x, y, scale, angle) => {
41 | // Called while dragging
42 | });
43 | setDragEndCallback((element, x, y, scale, angle) => {
44 | // Called when dragging ends
45 | });
46 | ```
47 |
48 |
49 | Each callback receives:
50 | - element: The DOM element being dragged
51 | - x: Current x position
52 | - y: Current y position
53 | - scale: Current scale factor
54 | - angle: Current rotation angle (in radians)
55 |
56 | ## Notes
57 |
58 | - Elements are automatically centered using transform: translate(-50%, -50%)
59 | - Z-index is managed automatically to bring dragged elements to front
60 | - Touch events are handled with passive: false to prevent scrolling while dragging
61 |
--------------------------------------------------------------------------------
/draggable.css:
--------------------------------------------------------------------------------
1 | .draggable {
2 | position: absolute;
3 | transform: translate(-50%, -50%);
4 | white-space: pre;
5 |
6 | /* filter: drop-shadow(1px 1px 3px white); */
7 | display: block;
8 | user-select: none;
9 | margin: 0;
10 | top: 50%;
11 | left: 50%;
12 | touch-action: manipulation;
13 | }
14 |
15 | .handle-container {
16 | border: 1px solid rgba(0, 0, 0, 0.15);
17 | position: absolute;
18 | cursor: nwse-resize;
19 | z-index: 10000;
20 | display: block;
21 | user-select: none;
22 | margin: 0;
23 | pointer-events: none;
24 | }
25 | .resize-handle {
26 | position: absolute;
27 | width: 12px;
28 | height: 12px;
29 | border: 1px solid black;
30 | box-sizing: border-box;
31 | z-index: 1000000;
32 | background-color: white;
33 | transform: translate(-50%, -50%);
34 | display: block;
35 | user-select: none;
36 | margin: 0;
37 | pointer-events: all;
38 | right: -12px;
39 | bottom: -12px;
40 | border-radius: 100%;
41 | cursor: grabbing;
42 | }
43 |
--------------------------------------------------------------------------------
/draggable.js:
--------------------------------------------------------------------------------
1 | // you don't need to edit this file, but you can look through it to see how the draggable works!
2 | // -Max
3 |
4 | let dragStartCallback = function (element, x, y, scale, rotation) {
5 | // the default drag callback does nothing
6 | };
7 | let dragMoveCallback = function (element, x, y, scale, rotation) {
8 | // the default drag callback does nothing
9 | };
10 | let dragEndCallback = function (element, x, y, scale, rotation) {
11 | // the default drag callback does nothing
12 | };
13 |
14 | function setDragStartCallback(callback) {
15 | if (typeof callback === "function") {
16 | dragStartCallback = callback;
17 | } else {
18 | throw new Error("drag callback must be a function!");
19 | }
20 | }
21 | function setDragMoveCallback(callback) {
22 | if (typeof callback === "function") {
23 | dragMoveCallback = callback;
24 | } else {
25 | throw new Error("drag callback must be a function!");
26 | }
27 | }
28 | function setDragEndCallback(callback) {
29 | if (typeof callback === "function") {
30 | dragEndCallback = callback;
31 | } else {
32 | throw new Error("drag callback must be a function!");
33 | }
34 | }
35 | let activeElement = null;
36 | let mousedown = false;
37 | let handle_state = false;
38 | let offset_x;
39 | let offset_y;
40 |
41 | window.last_z = 1;
42 | let initialDistance;
43 | let initialScale;
44 | let initialWidth;
45 | let initialHeight;
46 | let initialAngle;
47 |
48 | let resizeHandle = document.createElement("div");
49 | let handleContainer = document.createElement("div");
50 | resizeHandle.classList.add("resize-handle");
51 | handleContainer.classList.add("handle-container");
52 | handleContainer.appendChild(resizeHandle);
53 | document.body.appendChild(handleContainer);
54 |
55 | var isTouchDevice = "ontouchstart" in document.documentElement;
56 | // this moves the outline rectangle to match the current element
57 | function updateHandleContainer() {
58 | if (!activeElement) {
59 | handleContainer.style.left = "-1000px";
60 | return;
61 | }
62 | let styles = window.getComputedStyle(activeElement);
63 | let scale = getCurrentScale(activeElement);
64 | let rotate = getCurrentRotation(activeElement);
65 | handleContainer.style.left = styles.left;
66 | handleContainer.style.top = styles.top;
67 | handleContainer.style.width = parseFloat(styles.width) * scale + "px";
68 | handleContainer.style.height = parseFloat(styles.height) * scale + "px";
69 | handleContainer.style.transform = `
70 | translate(-50%,-50%)
71 | rotate(${rotate * (180 / Math.PI)}deg)`;
72 | }
73 |
74 | // Add this at the start of the file, after other variable declarations
75 | let maxZIndex = 1;
76 | const DRAG_Z_INDEX = 100000;
77 |
78 | function startAction(ev, isMouse) {
79 | let touches = Array.from(ev.touches);
80 | let firstTouch = touches[0];
81 | if (firstTouch.target.classList.contains("resize-handle")) {
82 | ev.preventDefault();
83 | initialScale = getCurrentScale(activeElement);
84 | initialAngle = getCurrentRotation(activeElement);
85 | let styles = window.getComputedStyle(activeElement);
86 | dragStartCallback(
87 | activeElement,
88 | parseFloat(styles.left),
89 | parseFloat(styles.top),
90 | initialScale,
91 | initialAngle
92 | );
93 | }
94 | if (firstTouch.target.classList.contains("draggable")) {
95 | if (firstTouch.target.tagName === "IMG") {
96 | ev.preventDefault();
97 | }
98 | let selectedElement = checkImageCoord(firstTouch.target, ev);
99 | if (!selectedElement || !selectedElement.classList.contains("draggable")) {
100 | return;
101 | }
102 | activeElement = selectedElement;
103 |
104 | let bounds = selectedElement.getBoundingClientRect();
105 | if (isMouse) {
106 | updateHandleContainer();
107 | }
108 | offset_x = firstTouch.clientX - bounds.left;
109 | offset_y = firstTouch.clientY - bounds.top;
110 |
111 | // Set extremely high z-index during drag
112 | activeElement.style.zIndex = DRAG_Z_INDEX;
113 | initialWidth = bounds.width;
114 | initialHeight = bounds.height;
115 | initialScale = getCurrentScale(activeElement);
116 | initialAngle = getCurrentRotation(activeElement);
117 |
118 | let secondTouch = touches[1];
119 | if (secondTouch) {
120 | let p1 = { x: firstTouch.clientX, y: firstTouch.clientY };
121 | let p2 = { x: secondTouch.clientX, y: secondTouch.clientY };
122 | let pDifference = sub(p1, p2);
123 | let pMid = add(p1, scale(pDifference, 0.5));
124 |
125 | initialDistance = distance(p1, p2);
126 | initialAngle = angle(pDifference) - getCurrentRotation(selectedElement);
127 | offset_x = pMid.x - bounds.left;
128 | offset_y = pMid.y - bounds.top;
129 | }
130 | let styles = window.getComputedStyle(activeElement);
131 |
132 | dragStartCallback(
133 | activeElement,
134 | parseFloat(styles.left),
135 | parseFloat(styles.top),
136 | initialScale,
137 | initialAngle
138 | );
139 | }
140 | }
141 | document.body.addEventListener("touchstart", function (ev) {
142 | if (activeElement) {
143 | handleContainer.style.left = "-1000px";
144 | }
145 | startAction(ev, false);
146 | });
147 |
148 | document.body.addEventListener("mousedown", function (ev) {
149 | if (isTouchDevice) {
150 | return;
151 | }
152 |
153 | if (ev.target.classList.contains("resize-handle") && activeElement) {
154 | let styles = window.getComputedStyle(activeElement);
155 | let scale = getCurrentScale(activeElement);
156 | let rotate = getCurrentRotation(activeElement);
157 | let size = {
158 | x: parseFloat(styles.width) * scale,
159 | y: parseFloat(styles.height) * scale
160 | };
161 |
162 | handleContainer.style.transform = `
163 | translate(-50%,-50%)
164 | rotate(${rotate * (180 / Math.PI)}deg)`;
165 |
166 | initialDistance = magnitude(size);
167 |
168 | handle_state = "resize";
169 | } else {
170 | handle_state = false;
171 | if (activeElement) {
172 | handleContainer.style.left = "-1000px";
173 | activeElement = false;
174 | }
175 | }
176 |
177 | mousedown = true;
178 |
179 | ev.touches = [ev];
180 | startAction(ev, true);
181 | });
182 | document.body.addEventListener("touchend", function (ev) {
183 | if (activeElement) {
184 | handleContainer.style.left = "-1000px";
185 | // Set to next available z-index when drag ends
186 | maxZIndex++;
187 | activeElement.style.zIndex = maxZIndex;
188 | let styles = window.getComputedStyle(activeElement);
189 | dragEndCallback(
190 | activeElement,
191 | parseFloat(styles.left),
192 | parseFloat(styles.top),
193 | getCurrentScale(activeElement),
194 | getCurrentRotation(activeElement)
195 | );
196 | }
197 |
198 | activeElement = null;
199 | });
200 | document.body.addEventListener("mouseup", function (ev) {
201 | mousedown = false;
202 | handle_state = false;
203 |
204 | if (!activeElement) return;
205 | // Set to next available z-index when drag ends
206 | maxZIndex++;
207 | activeElement.style.zIndex = maxZIndex;
208 | initialScale = getCurrentScale(activeElement);
209 | initialAngle = getCurrentRotation(activeElement);
210 | let styles = window.getComputedStyle(activeElement);
211 | dragEndCallback(
212 | activeElement,
213 | parseFloat(styles.left),
214 | parseFloat(styles.top),
215 | getCurrentScale(activeElement),
216 | getCurrentRotation(activeElement)
217 | );
218 | });
219 |
220 | function moveAction(ev, isMouse) {
221 | if (!activeElement) {
222 | return;
223 | }
224 |
225 | let touches = Array.from(ev.touches);
226 | let firstTouch = touches[0];
227 |
228 | let x = firstTouch.clientX - offset_x + initialWidth / 2;
229 | let y = firstTouch.clientY - offset_y + initialHeight / 2;
230 |
231 | let newScale = initialScale;
232 | let newAngle = initialAngle;
233 |
234 | let secondTouch = touches[1];
235 | if (secondTouch) {
236 | let p1 = { x: firstTouch.clientX, y: firstTouch.clientY };
237 | let p2 = { x: secondTouch.clientX, y: secondTouch.clientY };
238 | let pDifference = sub(p1, p2);
239 | let pMid = add(p1, scale(pDifference, 0.5));
240 |
241 | let newDistance = distance(p1, p2);
242 | newAngle = angle(pDifference) - initialAngle;
243 | newScale = initialScale * (newDistance / initialDistance);
244 | x = pMid.x - offset_x + initialWidth / 2;
245 | y = pMid.y - offset_y + initialHeight / 2;
246 | }
247 |
248 | if (handle_state === "resize") {
249 | let b = activeElement.getBoundingClientRect();
250 | let p1 = { x: firstTouch.clientX, y: firstTouch.clientY };
251 | let center = { x: b.left + b.width / 2, y: b.top + b.height / 2 };
252 | let p2 = add(center, sub(p1, center));
253 | let newDistance = distance(p1, p2);
254 |
255 | newScale = initialScale * (newDistance / initialDistance);
256 |
257 | let pDifference = sub(p1, p2);
258 | let handleAngle = angle(pDifference);
259 |
260 | let styles = window.getComputedStyle(activeElement);
261 | let w = parseFloat(styles.width);
262 | let h = parseFloat(styles.height);
263 | let a = Math.atan2(h, w) + Math.PI;
264 |
265 | newAngle = handleAngle - a;
266 | } else if (!handle_state) {
267 | activeElement.style.left = x + "px";
268 | activeElement.style.top = y + "px";
269 | }
270 |
271 | activeElement.style.transform = `
272 | translate(-50%,-50%)
273 | scale(${newScale})
274 | rotate(${newAngle * (180 / Math.PI)}deg)`;
275 |
276 | dragMoveCallback(activeElement, x, y, newScale, newAngle);
277 |
278 | if (isMouse) {
279 | try {
280 | updateHandleContainer();
281 | } catch (e) {
282 | console.log(e);
283 | }
284 | }
285 | }
286 | document.body.addEventListener("mousemove", function (ev) {
287 | ev.touches = [ev];
288 |
289 | if (mousedown) {
290 | moveAction(ev, true);
291 | }
292 | });
293 |
294 | document.body.addEventListener(
295 | "touchmove",
296 | function (ev) {
297 | ev.preventDefault();
298 | moveAction(ev);
299 | },
300 | { passive: false }
301 | );
302 | let canvas = document.createElement("canvas");
303 | let ctx = canvas.getContext("2d");
304 | // document.body.appendChild(ctx.canvas); // used for debugging
305 |
306 | // this function checks if a pixel location in an image is opaque
307 | // if it's not, it attemps to find the next image below it until
308 | // it finds one
309 | function checkImageCoord(img_element, event) {
310 | // non-image elements are always considered opaque
311 | if (img_element.tagName !== "IMG") {
312 | return img_element;
313 | }
314 | img_element.crossOrigin = "anonymous";
315 | let touches = Array.from(event.touches);
316 | let firstTouch = touches[0];
317 |
318 | // Get click coordinates
319 | let x = firstTouch.clientX;
320 | let y = firstTouch.clientY;
321 | let w = (ctx.canvas.width = window.innerWidth);
322 | let h = (ctx.canvas.height = window.innerHeight);
323 |
324 | ctx.clearRect(0, 0, w, h);
325 |
326 | let scale = getCurrentScale(img_element);
327 | let rotation = getCurrentRotation(img_element);
328 |
329 | let styles = window.getComputedStyle(img_element);
330 | ctx.translate(parseFloat(styles.left), parseFloat(styles.top));
331 | ctx.scale(scale, scale);
332 | ctx.rotate(rotation);
333 |
334 | ctx.drawImage(
335 | img_element,
336 | -img_element.width / 2,
337 | -img_element.height / 2,
338 | img_element.width,
339 | img_element.height
340 | );
341 | ctx.resetTransform();
342 | let alpha = 1;
343 | try {
344 | alpha = ctx.getImageData(x, y, 1, 1).data[3]; // [0]R [1]G [2]B [3]A
345 | if (!img_element.complete) {
346 | alpha = 1;
347 | }
348 | } catch (e) {
349 | console.warn(`add crossorigin="anonymous" to your img`);
350 | }
351 | // If pixel is transparent, then retrieve the element underneath
352 | // and trigger it's click event
353 | if (alpha === 0) {
354 | img_element.style.pointerEvents = "none";
355 | let nextTarget = document.elementFromPoint(
356 | firstTouch.clientX,
357 | firstTouch.clientY
358 | );
359 | let nextEl = null;
360 | if (nextTarget.classList.contains("draggable")) {
361 | nextEl = checkImageCoord(nextTarget, event);
362 | }
363 | img_element.style.pointerEvents = "auto";
364 | return nextEl;
365 | } else {
366 | //image is opaque at location
367 | return img_element;
368 | }
369 | }
370 |
371 | function getTransform(el) {
372 | try {
373 | let st = window.getComputedStyle(el, null);
374 | let tr =
375 | st.getPropertyValue("-webkit-transform") ||
376 | st.getPropertyValue("-moz-transform") ||
377 | st.getPropertyValue("-ms-transform") ||
378 | st.getPropertyValue("-o-transform") ||
379 | st.getPropertyValue("transform") ||
380 | "FAIL";
381 |
382 | return tr.split("(")[1].split(")")[0].split(",");
383 | } catch (e) {
384 | console.log(e);
385 | return [0, 0, 0, 0];
386 | }
387 | }
388 | function getCurrentScale(el) {
389 | let values = getTransform(el);
390 |
391 | return Math.sqrt(values[0] * values[0] + values[1] * values[1]);
392 | }
393 |
394 | function getCurrentRotation(el) {
395 | let values = getTransform(el);
396 |
397 | return Math.atan2(values[1], values[0]);
398 | }
399 |
400 | function add(a, b) {
401 | return { x: a.x + b.x, y: a.y + b.y };
402 | }
403 |
404 | function sub(a, b) {
405 | return { x: b.x - a.x, y: b.y - a.y };
406 | }
407 |
408 | function scale(a, s) {
409 | return { x: a.x * s, y: a.y * s };
410 | }
411 | function magnitude(a) {
412 | return Math.sqrt(Math.pow(a.x, 2) + Math.pow(a.y, 2));
413 | }
414 | function angle(b) {
415 | return Math.atan2(b.y, b.x); //radians
416 | }
417 |
418 | function distance(a, b) {
419 | return magnitude(sub(a, b));
420 | }
421 |
422 | export { setDragStartCallback, setDragMoveCallback, setDragEndCallback };
423 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | collage template with draggable.js
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |

14 |
edit me
15 |
I love eating leaves
16 |
17 |
18 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | import {
2 | setDragStartCallback,
3 | setDragMoveCallback,
4 | setDragEndCallback
5 | } from "./draggable.js";
6 |
7 | // if you want to attach extra logic to the drag motions, you can use these callbacks:
8 | setDragStartCallback(function (element, x, y, scale, angle) {
9 | // console.log(element)
10 | });
11 | setDragMoveCallback(function (element, x, y, scale, angle) {
12 | // console.log(element)
13 | });
14 | setDragEndCallback(function (element, x, y, scale, angle) {
15 | // console.log(element)
16 | });
17 |
18 | // Add event listener for spawner elements
19 | document.addEventListener('mousedown', handleSpawnerDrag);
20 | document.addEventListener('touchstart', handleSpawnerDrag);
21 |
22 | function handleSpawnerDrag(event) {
23 | const target = event.target;
24 |
25 | if (target.classList.contains('spawner')) {
26 | event.preventDefault();
27 |
28 | // Get touch or mouse coordinates
29 | const clientX = event.type === 'mousedown' ? event.clientX : event.touches[0].clientX;
30 | const clientY = event.type === 'mousedown' ? event.clientY : event.touches[0].clientY;
31 |
32 | // Create clone
33 | const clone = target.cloneNode(true);
34 | clone.classList.remove('spawner');
35 | clone.classList.add('draggable');
36 |
37 | // Position clone at same location as original
38 | const rect = target.getBoundingClientRect();
39 | clone.style.position = 'absolute';
40 | clone.style.left = (rect.left + rect.width/2) + 'px';
41 | clone.style.top = (rect.top + rect.height/2) + 'px';
42 | clone.style.width = rect.width + 'px';
43 | clone.style.zIndex = window.last_z++;
44 |
45 | // Add clone to document
46 | document.getElementById('target').appendChild(clone);
47 |
48 | // Trigger drag start on clone
49 | if (event.type === 'mousedown') {
50 | const mouseEvent = new MouseEvent('mousedown', {
51 | bubbles: true,
52 | cancelable: true,
53 | clientX: clientX,
54 | clientY: clientY
55 | });
56 | clone.dispatchEvent(mouseEvent);
57 | } else {
58 | const touch = new Touch({
59 | identifier: event.touches[0].identifier,
60 | target: clone,
61 | clientX: clientX,
62 | clientY: clientY,
63 | pageX: event.touches[0].pageX,
64 | pageY: event.touches[0].pageY
65 | });
66 |
67 | const touchEvent = new TouchEvent('touchstart', {
68 | bubbles: true,
69 | cancelable: true,
70 | touches: [touch],
71 | targetTouches: [touch],
72 | changedTouches: [touch]
73 | });
74 | clone.dispatchEvent(touchEvent);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "version": "0.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "vite",
9 | "version": "0.0.0",
10 | "dependencies": {
11 | "vite": "^4.5.1"
12 | }
13 | },
14 | "node_modules/@esbuild/android-arm": {
15 | "version": "0.18.20",
16 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
17 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
18 | "cpu": [
19 | "arm"
20 | ],
21 | "optional": true,
22 | "os": [
23 | "android"
24 | ],
25 | "engines": {
26 | "node": ">=12"
27 | }
28 | },
29 | "node_modules/@esbuild/android-arm64": {
30 | "version": "0.18.20",
31 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
32 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
33 | "cpu": [
34 | "arm64"
35 | ],
36 | "optional": true,
37 | "os": [
38 | "android"
39 | ],
40 | "engines": {
41 | "node": ">=12"
42 | }
43 | },
44 | "node_modules/@esbuild/android-x64": {
45 | "version": "0.18.20",
46 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
47 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
48 | "cpu": [
49 | "x64"
50 | ],
51 | "optional": true,
52 | "os": [
53 | "android"
54 | ],
55 | "engines": {
56 | "node": ">=12"
57 | }
58 | },
59 | "node_modules/@esbuild/darwin-arm64": {
60 | "version": "0.18.20",
61 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
62 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
63 | "cpu": [
64 | "arm64"
65 | ],
66 | "optional": true,
67 | "os": [
68 | "darwin"
69 | ],
70 | "engines": {
71 | "node": ">=12"
72 | }
73 | },
74 | "node_modules/@esbuild/darwin-x64": {
75 | "version": "0.18.20",
76 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
77 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
78 | "cpu": [
79 | "x64"
80 | ],
81 | "optional": true,
82 | "os": [
83 | "darwin"
84 | ],
85 | "engines": {
86 | "node": ">=12"
87 | }
88 | },
89 | "node_modules/@esbuild/freebsd-arm64": {
90 | "version": "0.18.20",
91 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
92 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
93 | "cpu": [
94 | "arm64"
95 | ],
96 | "optional": true,
97 | "os": [
98 | "freebsd"
99 | ],
100 | "engines": {
101 | "node": ">=12"
102 | }
103 | },
104 | "node_modules/@esbuild/freebsd-x64": {
105 | "version": "0.18.20",
106 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
107 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
108 | "cpu": [
109 | "x64"
110 | ],
111 | "optional": true,
112 | "os": [
113 | "freebsd"
114 | ],
115 | "engines": {
116 | "node": ">=12"
117 | }
118 | },
119 | "node_modules/@esbuild/linux-arm": {
120 | "version": "0.18.20",
121 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
122 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
123 | "cpu": [
124 | "arm"
125 | ],
126 | "optional": true,
127 | "os": [
128 | "linux"
129 | ],
130 | "engines": {
131 | "node": ">=12"
132 | }
133 | },
134 | "node_modules/@esbuild/linux-arm64": {
135 | "version": "0.18.20",
136 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
137 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
138 | "cpu": [
139 | "arm64"
140 | ],
141 | "optional": true,
142 | "os": [
143 | "linux"
144 | ],
145 | "engines": {
146 | "node": ">=12"
147 | }
148 | },
149 | "node_modules/@esbuild/linux-ia32": {
150 | "version": "0.18.20",
151 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
152 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
153 | "cpu": [
154 | "ia32"
155 | ],
156 | "optional": true,
157 | "os": [
158 | "linux"
159 | ],
160 | "engines": {
161 | "node": ">=12"
162 | }
163 | },
164 | "node_modules/@esbuild/linux-loong64": {
165 | "version": "0.18.20",
166 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
167 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
168 | "cpu": [
169 | "loong64"
170 | ],
171 | "optional": true,
172 | "os": [
173 | "linux"
174 | ],
175 | "engines": {
176 | "node": ">=12"
177 | }
178 | },
179 | "node_modules/@esbuild/linux-mips64el": {
180 | "version": "0.18.20",
181 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
182 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
183 | "cpu": [
184 | "mips64el"
185 | ],
186 | "optional": true,
187 | "os": [
188 | "linux"
189 | ],
190 | "engines": {
191 | "node": ">=12"
192 | }
193 | },
194 | "node_modules/@esbuild/linux-ppc64": {
195 | "version": "0.18.20",
196 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
197 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
198 | "cpu": [
199 | "ppc64"
200 | ],
201 | "optional": true,
202 | "os": [
203 | "linux"
204 | ],
205 | "engines": {
206 | "node": ">=12"
207 | }
208 | },
209 | "node_modules/@esbuild/linux-riscv64": {
210 | "version": "0.18.20",
211 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
212 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
213 | "cpu": [
214 | "riscv64"
215 | ],
216 | "optional": true,
217 | "os": [
218 | "linux"
219 | ],
220 | "engines": {
221 | "node": ">=12"
222 | }
223 | },
224 | "node_modules/@esbuild/linux-s390x": {
225 | "version": "0.18.20",
226 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
227 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
228 | "cpu": [
229 | "s390x"
230 | ],
231 | "optional": true,
232 | "os": [
233 | "linux"
234 | ],
235 | "engines": {
236 | "node": ">=12"
237 | }
238 | },
239 | "node_modules/@esbuild/linux-x64": {
240 | "version": "0.18.20",
241 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
242 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
243 | "cpu": [
244 | "x64"
245 | ],
246 | "optional": true,
247 | "os": [
248 | "linux"
249 | ],
250 | "engines": {
251 | "node": ">=12"
252 | }
253 | },
254 | "node_modules/@esbuild/netbsd-x64": {
255 | "version": "0.18.20",
256 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
257 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
258 | "cpu": [
259 | "x64"
260 | ],
261 | "optional": true,
262 | "os": [
263 | "netbsd"
264 | ],
265 | "engines": {
266 | "node": ">=12"
267 | }
268 | },
269 | "node_modules/@esbuild/openbsd-x64": {
270 | "version": "0.18.20",
271 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
272 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
273 | "cpu": [
274 | "x64"
275 | ],
276 | "optional": true,
277 | "os": [
278 | "openbsd"
279 | ],
280 | "engines": {
281 | "node": ">=12"
282 | }
283 | },
284 | "node_modules/@esbuild/sunos-x64": {
285 | "version": "0.18.20",
286 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
287 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
288 | "cpu": [
289 | "x64"
290 | ],
291 | "optional": true,
292 | "os": [
293 | "sunos"
294 | ],
295 | "engines": {
296 | "node": ">=12"
297 | }
298 | },
299 | "node_modules/@esbuild/win32-arm64": {
300 | "version": "0.18.20",
301 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
302 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
303 | "cpu": [
304 | "arm64"
305 | ],
306 | "optional": true,
307 | "os": [
308 | "win32"
309 | ],
310 | "engines": {
311 | "node": ">=12"
312 | }
313 | },
314 | "node_modules/@esbuild/win32-ia32": {
315 | "version": "0.18.20",
316 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
317 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
318 | "cpu": [
319 | "ia32"
320 | ],
321 | "optional": true,
322 | "os": [
323 | "win32"
324 | ],
325 | "engines": {
326 | "node": ">=12"
327 | }
328 | },
329 | "node_modules/@esbuild/win32-x64": {
330 | "version": "0.18.20",
331 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
332 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
333 | "cpu": [
334 | "x64"
335 | ],
336 | "optional": true,
337 | "os": [
338 | "win32"
339 | ],
340 | "engines": {
341 | "node": ">=12"
342 | }
343 | },
344 | "node_modules/esbuild": {
345 | "version": "0.18.20",
346 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
347 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
348 | "hasInstallScript": true,
349 | "bin": {
350 | "esbuild": "bin/esbuild"
351 | },
352 | "engines": {
353 | "node": ">=12"
354 | },
355 | "optionalDependencies": {
356 | "@esbuild/android-arm": "0.18.20",
357 | "@esbuild/android-arm64": "0.18.20",
358 | "@esbuild/android-x64": "0.18.20",
359 | "@esbuild/darwin-arm64": "0.18.20",
360 | "@esbuild/darwin-x64": "0.18.20",
361 | "@esbuild/freebsd-arm64": "0.18.20",
362 | "@esbuild/freebsd-x64": "0.18.20",
363 | "@esbuild/linux-arm": "0.18.20",
364 | "@esbuild/linux-arm64": "0.18.20",
365 | "@esbuild/linux-ia32": "0.18.20",
366 | "@esbuild/linux-loong64": "0.18.20",
367 | "@esbuild/linux-mips64el": "0.18.20",
368 | "@esbuild/linux-ppc64": "0.18.20",
369 | "@esbuild/linux-riscv64": "0.18.20",
370 | "@esbuild/linux-s390x": "0.18.20",
371 | "@esbuild/linux-x64": "0.18.20",
372 | "@esbuild/netbsd-x64": "0.18.20",
373 | "@esbuild/openbsd-x64": "0.18.20",
374 | "@esbuild/sunos-x64": "0.18.20",
375 | "@esbuild/win32-arm64": "0.18.20",
376 | "@esbuild/win32-ia32": "0.18.20",
377 | "@esbuild/win32-x64": "0.18.20"
378 | }
379 | },
380 | "node_modules/fsevents": {
381 | "version": "2.3.3",
382 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
383 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
384 | "hasInstallScript": true,
385 | "optional": true,
386 | "os": [
387 | "darwin"
388 | ],
389 | "engines": {
390 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
391 | }
392 | },
393 | "node_modules/nanoid": {
394 | "version": "3.3.7",
395 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
396 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
397 | "funding": [
398 | {
399 | "type": "github",
400 | "url": "https://github.com/sponsors/ai"
401 | }
402 | ],
403 | "bin": {
404 | "nanoid": "bin/nanoid.cjs"
405 | },
406 | "engines": {
407 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
408 | }
409 | },
410 | "node_modules/picocolors": {
411 | "version": "1.0.0",
412 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
413 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
414 | },
415 | "node_modules/postcss": {
416 | "version": "8.4.32",
417 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
418 | "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
419 | "funding": [
420 | {
421 | "type": "opencollective",
422 | "url": "https://opencollective.com/postcss/"
423 | },
424 | {
425 | "type": "tidelift",
426 | "url": "https://tidelift.com/funding/github/npm/postcss"
427 | },
428 | {
429 | "type": "github",
430 | "url": "https://github.com/sponsors/ai"
431 | }
432 | ],
433 | "dependencies": {
434 | "nanoid": "^3.3.7",
435 | "picocolors": "^1.0.0",
436 | "source-map-js": "^1.0.2"
437 | },
438 | "engines": {
439 | "node": "^10 || ^12 || >=14"
440 | }
441 | },
442 | "node_modules/rollup": {
443 | "version": "3.29.4",
444 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
445 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
446 | "bin": {
447 | "rollup": "dist/bin/rollup"
448 | },
449 | "engines": {
450 | "node": ">=14.18.0",
451 | "npm": ">=8.0.0"
452 | },
453 | "optionalDependencies": {
454 | "fsevents": "~2.3.2"
455 | }
456 | },
457 | "node_modules/source-map-js": {
458 | "version": "1.0.2",
459 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
460 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
461 | "engines": {
462 | "node": ">=0.10.0"
463 | }
464 | },
465 | "node_modules/vite": {
466 | "version": "4.5.1",
467 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
468 | "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
469 | "dependencies": {
470 | "esbuild": "^0.18.10",
471 | "postcss": "^8.4.27",
472 | "rollup": "^3.27.1"
473 | },
474 | "bin": {
475 | "vite": "bin/vite.js"
476 | },
477 | "engines": {
478 | "node": "^14.18.0 || >=16.0.0"
479 | },
480 | "funding": {
481 | "url": "https://github.com/vitejs/vite?sponsor=1"
482 | },
483 | "optionalDependencies": {
484 | "fsevents": "~2.3.2"
485 | },
486 | "peerDependencies": {
487 | "@types/node": ">= 14",
488 | "less": "*",
489 | "lightningcss": "^1.21.0",
490 | "sass": "*",
491 | "stylus": "*",
492 | "sugarss": "*",
493 | "terser": "^5.4.0"
494 | },
495 | "peerDependenciesMeta": {
496 | "@types/node": {
497 | "optional": true
498 | },
499 | "less": {
500 | "optional": true
501 | },
502 | "lightningcss": {
503 | "optional": true
504 | },
505 | "sass": {
506 | "optional": true
507 | },
508 | "stylus": {
509 | "optional": true
510 | },
511 | "sugarss": {
512 | "optional": true
513 | },
514 | "terser": {
515 | "optional": true
516 | }
517 | }
518 | }
519 | },
520 | "dependencies": {
521 | "@esbuild/android-arm": {
522 | "version": "0.18.20",
523 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
524 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
525 | "optional": true
526 | },
527 | "@esbuild/android-arm64": {
528 | "version": "0.18.20",
529 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
530 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
531 | "optional": true
532 | },
533 | "@esbuild/android-x64": {
534 | "version": "0.18.20",
535 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
536 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
537 | "optional": true
538 | },
539 | "@esbuild/darwin-arm64": {
540 | "version": "0.18.20",
541 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
542 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
543 | "optional": true
544 | },
545 | "@esbuild/darwin-x64": {
546 | "version": "0.18.20",
547 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
548 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
549 | "optional": true
550 | },
551 | "@esbuild/freebsd-arm64": {
552 | "version": "0.18.20",
553 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
554 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
555 | "optional": true
556 | },
557 | "@esbuild/freebsd-x64": {
558 | "version": "0.18.20",
559 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
560 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
561 | "optional": true
562 | },
563 | "@esbuild/linux-arm": {
564 | "version": "0.18.20",
565 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
566 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
567 | "optional": true
568 | },
569 | "@esbuild/linux-arm64": {
570 | "version": "0.18.20",
571 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
572 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
573 | "optional": true
574 | },
575 | "@esbuild/linux-ia32": {
576 | "version": "0.18.20",
577 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
578 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
579 | "optional": true
580 | },
581 | "@esbuild/linux-loong64": {
582 | "version": "0.18.20",
583 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
584 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
585 | "optional": true
586 | },
587 | "@esbuild/linux-mips64el": {
588 | "version": "0.18.20",
589 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
590 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
591 | "optional": true
592 | },
593 | "@esbuild/linux-ppc64": {
594 | "version": "0.18.20",
595 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
596 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
597 | "optional": true
598 | },
599 | "@esbuild/linux-riscv64": {
600 | "version": "0.18.20",
601 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
602 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
603 | "optional": true
604 | },
605 | "@esbuild/linux-s390x": {
606 | "version": "0.18.20",
607 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
608 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
609 | "optional": true
610 | },
611 | "@esbuild/linux-x64": {
612 | "version": "0.18.20",
613 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
614 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
615 | "optional": true
616 | },
617 | "@esbuild/netbsd-x64": {
618 | "version": "0.18.20",
619 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
620 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
621 | "optional": true
622 | },
623 | "@esbuild/openbsd-x64": {
624 | "version": "0.18.20",
625 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
626 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
627 | "optional": true
628 | },
629 | "@esbuild/sunos-x64": {
630 | "version": "0.18.20",
631 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
632 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
633 | "optional": true
634 | },
635 | "@esbuild/win32-arm64": {
636 | "version": "0.18.20",
637 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
638 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
639 | "optional": true
640 | },
641 | "@esbuild/win32-ia32": {
642 | "version": "0.18.20",
643 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
644 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
645 | "optional": true
646 | },
647 | "@esbuild/win32-x64": {
648 | "version": "0.18.20",
649 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
650 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
651 | "optional": true
652 | },
653 | "esbuild": {
654 | "version": "0.18.20",
655 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
656 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
657 | "requires": {
658 | "@esbuild/android-arm": "0.18.20",
659 | "@esbuild/android-arm64": "0.18.20",
660 | "@esbuild/android-x64": "0.18.20",
661 | "@esbuild/darwin-arm64": "0.18.20",
662 | "@esbuild/darwin-x64": "0.18.20",
663 | "@esbuild/freebsd-arm64": "0.18.20",
664 | "@esbuild/freebsd-x64": "0.18.20",
665 | "@esbuild/linux-arm": "0.18.20",
666 | "@esbuild/linux-arm64": "0.18.20",
667 | "@esbuild/linux-ia32": "0.18.20",
668 | "@esbuild/linux-loong64": "0.18.20",
669 | "@esbuild/linux-mips64el": "0.18.20",
670 | "@esbuild/linux-ppc64": "0.18.20",
671 | "@esbuild/linux-riscv64": "0.18.20",
672 | "@esbuild/linux-s390x": "0.18.20",
673 | "@esbuild/linux-x64": "0.18.20",
674 | "@esbuild/netbsd-x64": "0.18.20",
675 | "@esbuild/openbsd-x64": "0.18.20",
676 | "@esbuild/sunos-x64": "0.18.20",
677 | "@esbuild/win32-arm64": "0.18.20",
678 | "@esbuild/win32-ia32": "0.18.20",
679 | "@esbuild/win32-x64": "0.18.20"
680 | }
681 | },
682 | "fsevents": {
683 | "version": "2.3.3",
684 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
685 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
686 | "optional": true
687 | },
688 | "nanoid": {
689 | "version": "3.3.7",
690 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
691 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
692 | },
693 | "picocolors": {
694 | "version": "1.0.0",
695 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
696 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
697 | },
698 | "postcss": {
699 | "version": "8.4.32",
700 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
701 | "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
702 | "requires": {
703 | "nanoid": "^3.3.7",
704 | "picocolors": "^1.0.0",
705 | "source-map-js": "^1.0.2"
706 | }
707 | },
708 | "rollup": {
709 | "version": "3.29.4",
710 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
711 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
712 | "requires": {
713 | "fsevents": "~2.3.2"
714 | }
715 | },
716 | "source-map-js": {
717 | "version": "1.0.2",
718 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
719 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
720 | },
721 | "vite": {
722 | "version": "4.5.1",
723 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
724 | "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
725 | "requires": {
726 | "esbuild": "^0.18.10",
727 | "fsevents": "~2.3.2",
728 | "postcss": "^8.4.27",
729 | "rollup": "^3.27.1"
730 | }
731 | }
732 | }
733 | }
734 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "serve": "vite preview"
8 | },
9 | "dependencies": {
10 | "vite": "^4.5.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/public/bee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/bee.png
--------------------------------------------------------------------------------
/public/bumblebee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/bumblebee.png
--------------------------------------------------------------------------------
/public/caterpillar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/caterpillar.png
--------------------------------------------------------------------------------
/public/daisy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/daisy.png
--------------------------------------------------------------------------------
/public/flower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/flower.png
--------------------------------------------------------------------------------
/public/leaf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/leaf.png
--------------------------------------------------------------------------------
/public/saffron.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/saffron.png
--------------------------------------------------------------------------------
/public/sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/sun.png
--------------------------------------------------------------------------------
/public/sunflower.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MaxBittker/draggablejs/66125af07c8b3ca4e40f78210b4ec6e044154fc9/public/sunflower.png
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: sans-serif;
3 | height: 100vh;
4 | overflow: hidden;
5 | margin: 0;
6 | touch-action: manipulation;
7 | }
8 |
9 | body,
10 | html {
11 | height: -webkit-fill-available;
12 | }
13 | .draggable {
14 | /* filter: drop-shadow(1px 1px 3px white); */
15 | }
16 |
17 | .caterpillar {
18 | width: 200px;
19 | }
20 |
21 | #header {
22 | background-color: rgba(45, 161, 103, 0.808);
23 | box-shadow: 0px 2px 5px black;
24 | position: absolute;
25 | left: 0;
26 | top: 0;
27 | right: 0;
28 | height: 100px;
29 | z-index: 10000;
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | gap: 10px;
34 | }
35 | button {
36 | margin: 10px;
37 | width: 80px;
38 | height: 80px;
39 | display: block;
40 | }
41 | button img {
42 | /* width: 100%; */
43 | }
44 | #header img {
45 | height: 80px;
46 | }
47 |
48 | .trash-zone {
49 | position: fixed;
50 | bottom: 15%;
51 | right: 15%;
52 | width: 80px;
53 | height: 80px;
54 | border: 3px dashed #ff4444;
55 | border-radius: 10px;
56 | display: flex;
57 | justify-content: center;
58 | align-items: center;
59 | font-size: 40px;
60 | color: #ff4444;
61 | opacity: 0.5;
62 | transition: all 0.3s ease;
63 | }
64 |
65 | .trash-zone:hover, .trash-zone.drag-over {
66 | opacity: 1;
67 | background-color: rgba(255, 68, 68, 0.1);
68 | transform: scale(1.1);
69 | }
70 |
71 | .trash-zone::before {
72 | content: "×";
73 | font-weight: bold;
74 | }
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | server: {
3 | host: '0.0.0.0',
4 | }
5 | }
--------------------------------------------------------------------------------