├── .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 | } --------------------------------------------------------------------------------