From 666ec6ce6ee81c3d9711eaeac5fb71e0340518bd Mon Sep 17 00:00:00 2001 From: "vitalii.litvinchuk" Date: Sat, 1 Aug 2026 07:42:04 +0300 Subject: [PATCH] first commit --- index.html | 22 +++++ script.js | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 62 ++++++++++++++ 3 files changed, 316 insertions(+) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..da736a0 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + Letter Scatter + + + + +
+ + + +
+
+ + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..8011c9b --- /dev/null +++ b/script.js @@ -0,0 +1,232 @@ +const input = document.getElementById('textInput'); +const btn = document.getElementById('applyBtn'); +const resetBtn = document.getElementById('resetBtn'); +const container = document.getElementById('container'); +const selectionBox = document.getElementById('selectionBox'); + +let letters = []; +let isDragging = false; +let isSelecting = false; +let hasMoved = false; +let startX = 0; +let startY = 0; +let dragOffsets = []; +let clickedLetter = null; + +function init() { + if (window.localStorage) { + const saved = localStorage.getItem('savedWord'); + + if (saved) { + input.value = saved; + createLetters(saved); + } + } +} + +function createLetters(text) { + container.innerHTML = ''; + letters = []; + + let totalWidth = 0; + const tempElements = []; + + for (let i = 0; i < text.length; i++) { + const el = document.createElement('span'); + el.textContent = text[i]; + el.className = 'letter'; + container.appendChild(el); + const rect = el.getBoundingClientRect(); + totalWidth += rect.width + 2; + tempElements.push({ el, rect }); + } + + let startLeft = (window.innerWidth - totalWidth) / 2; + let top = window.innerHeight / 2; + + for (let i = 0; i < text.length; i++) { + const item = tempElements[i]; + let x = startLeft; + let y = top; + item.el.style.left = x + 'px'; + item.el.style.top = y + 'px'; + + letters.push({ + char: text[i], + el: item.el, + x: x, + y: y, + initialX: x, + initialY: y, + selected: false, + width: item.rect.width, + height: item.rect.height + }); + + startLeft += item.rect.width + 2; + } +} + +btn.addEventListener('click', () => { + const val = input.value; + + if (val) { + localStorage.setItem('savedWord', val); + createLetters(val); + } +}); + +resetBtn.addEventListener('click', () => { + letters.forEach(l => { + l.x = l.initialX; + l.y = l.initialY; + l.el.style.left = l.x + 'px'; + l.el.style.top = l.y + 'px'; + l.selected = false; + }); + updateSelection(); +}); + +input.addEventListener('input', () => { + localStorage.setItem('savedWord', input.value); +}); + +document.addEventListener('mousedown', (e) => { + if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return; + + hasMoved = false; + + if (e.target.classList.contains('letter')) { + const index = letters.findIndex(l => l.el === e.target); + if (index === -1) return; + + clickedLetter = letters[index]; + + if (e.ctrlKey) { + clickedLetter.selected = !clickedLetter.selected; + updateSelection(); + } + else if (!clickedLetter.selected) { + letters.forEach(l => l.selected = false); + clickedLetter.selected = true; + updateSelection(); + } + + isDragging = true; + startX = e.clientX; + startY = e.clientY; + + dragOffsets = letters.filter(l => l.selected).map(l => ({ + l, + originalX: l.x, + originalY: l.y, + offsetX: l.x - e.clientX, + offsetY: l.y - e.clientY + })); + + e.preventDefault(); + } + else { + isSelecting = true; + startX = e.clientX; + startY = e.clientY; + selectionBox.hidden = false; + selectionBox.style.left = startX + 'px'; + selectionBox.style.top = startY + 'px'; + selectionBox.style.width = '0px'; + selectionBox.style.height = '0px'; + + if (!e.ctrlKey) { + letters.forEach(l => l.selected = false); + updateSelection(); + } + e.preventDefault(); + } +}); + +document.addEventListener('mousemove', (e) => { + if (isDragging || isSelecting) { + hasMoved = true; + } + + if (isDragging) { + dragOffsets.forEach(item => { + item.l.x = e.clientX + item.offsetX; + item.l.y = e.clientY + item.offsetY; + item.l.el.style.left = item.l.x + 'px'; + item.l.el.style.top = item.l.y + 'px'; + }); + } + else if (isSelecting) { + const currentX = e.clientX; + const currentY = e.clientY; + const left = Math.min(startX, currentX); + const top = Math.min(startY, currentY); + const width = Math.abs(currentX - startX); + const height = Math.abs(currentY - startY); + + selectionBox.style.left = left + 'px'; + selectionBox.style.top = top + 'px'; + selectionBox.style.width = width + 'px'; + selectionBox.style.height = height + 'px'; + + letters.forEach(l => { + const lRight = l.x + l.width; + const lBottom = l.y + l.height; + if (l.x < left + width && lRight > left && l.y < top + height && lBottom > top) { + l.selected = true; + } + }); + updateSelection(); + } +}); + +document.addEventListener('mouseup', (e) => { + if (isDragging) { + if (!hasMoved && clickedLetter && !e.ctrlKey) { + clickedLetter.selected = !clickedLetter.selected; + updateSelection(); + } + else if (dragOffsets.length === 1 && hasMoved) { + const draggedItem = dragOffsets[0]; + const targetX = e.clientX; + const targetY = e.clientY; + + const dropTarget = letters.find(l => { + if (l === draggedItem.l) return false; + + return targetX >= l.x && targetX <= l.x + l.width && + targetY >= l.y && targetY <= l.y + l.height; + }); + + if (dropTarget) { + dropTarget.x = draggedItem.originalX; + dropTarget.y = draggedItem.originalY; + dropTarget.el.style.left = dropTarget.x + 'px'; + dropTarget.el.style.top = dropTarget.y + 'px'; + } + } + + isDragging = false; + dragOffsets = []; + clickedLetter = null; + } + + if (isSelecting) { + isSelecting = false; + selectionBox.hidden = true; + } +}); + +function updateSelection() { + letters.forEach(l => { + if (l.selected) { + l.el.classList.add('selected'); + } + else { + l.el.classList.remove('selected'); + } + }); +} + +init(); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..ce14aae --- /dev/null +++ b/style.css @@ -0,0 +1,62 @@ +body, +html { + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + font-family: Arial, sans-serif; + background-color: #f4f4f9; +} + +#controls { + margin-top: 30px; + z-index: 10; + display: flex; + gap: 10px; +} + +#controls input, +#controls button { + padding: 8px 16px; + font-size: 16px; + cursor: pointer; +} + +#container { + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; +} + +.letter { + position: absolute; + font-size: 2.5rem; + font-weight: bold; + cursor: grab; + user-select: none; + pointer-events: auto; + padding: 5px; + color: #333; +} + +.letter:active { + cursor: grabbing; +} + +.letter.selected { + color: #ff4757; +} + +#selectionBox { + position: absolute; + border: 2px dashed #007bff; + background-color: rgba(0, 123, 255, 0.1); + pointer-events: none; + z-index: 5; +} \ No newline at end of file