first commit

This commit is contained in:
vitalii.litvinchuk
2026-08-01 07:42:04 +03:00
commit 666ec6ce6e
3 changed files with 316 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Letter Scatter</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="controls">
<input type="text" id="textInput" placeholder="Введіть текст...">
<button id="applyBtn">Apply</button>
<button id="resetBtn">Reset Position</button>
</div>
<div id="container"></div>
<div id="selectionBox" hidden></div>
<script src="script.js"></script>
</body>
</html>
+232
View File
@@ -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();
+62
View File
@@ -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;
}