Skip to content

Commit b0fcaaa

Browse files
authored
Create connections_grouper.html
Add a connections grouper app
1 parent c405ddf commit b0fcaaa

File tree

1 file changed

+346
-0
lines changed

1 file changed

+346
-0
lines changed

connections_grouper.html

+346
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Word Grouping (Mobile-Friendly)</title>
7+
<!-- Minimal, polished styling -->
8+
<link rel="preconnect" href="https://fonts.googleapis.com">
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10+
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600&display=swap" rel="stylesheet">
11+
<style>
12+
body {
13+
font-family: 'Open Sans', sans-serif;
14+
margin: 1rem auto;
15+
max-width: 800px;
16+
background-color: #fafafa;
17+
display: flex;
18+
flex-direction: column;
19+
align-items: center;
20+
color: #333;
21+
}
22+
23+
h1 {
24+
font-weight: 600;
25+
margin-bottom: 1rem;
26+
text-align: center;
27+
}
28+
29+
#wordInput {
30+
width: 100%;
31+
max-width: 600px;
32+
margin-bottom: 0.5rem;
33+
padding: 0.5rem;
34+
box-sizing: border-box;
35+
font-size: 1rem;
36+
border: 1px solid #ccc;
37+
border-radius: 4px;
38+
}
39+
40+
.button-bar {
41+
display: flex;
42+
flex-wrap: wrap;
43+
gap: 0.5rem;
44+
justify-content: center;
45+
margin-bottom: 1rem;
46+
}
47+
48+
button {
49+
background-color: #fff;
50+
border: 1px solid #ccc;
51+
border-radius: 6px;
52+
padding: 0.5rem 1rem;
53+
cursor: pointer;
54+
font-size: 1rem;
55+
transition: background-color 0.2s, box-shadow 0.2s;
56+
}
57+
58+
button:hover {
59+
background-color: #f0f0f0;
60+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
61+
}
62+
63+
button:active {
64+
background-color: #e8e8e8;
65+
}
66+
67+
.container {
68+
display: flex;
69+
justify-content: space-around;
70+
gap: 1rem;
71+
width: 100%;
72+
margin-top: 1rem;
73+
box-sizing: border-box;
74+
flex-wrap: wrap; /* Allow wrapping for smaller screens */
75+
}
76+
77+
/* Holding area styling */
78+
.holding-area {
79+
border: 2px dashed #999;
80+
border-radius: 8px;
81+
padding: 0.5rem;
82+
min-width: 160px;
83+
min-height: 160px;
84+
background-color: #fdfdfd;
85+
display: grid;
86+
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
87+
gap: 0.5rem;
88+
box-sizing: border-box;
89+
}
90+
91+
.holding-area p {
92+
margin: 0 0 0.5rem 0;
93+
font-weight: 600;
94+
grid-column: 1 / -1;
95+
}
96+
97+
/* Each group container */
98+
.group {
99+
border: 2px dashed #999;
100+
border-radius: 8px;
101+
padding: 0.5rem;
102+
min-width: 160px;
103+
min-height: 160px;
104+
background-color: #fdfdfd;
105+
box-sizing: border-box;
106+
position: relative;
107+
display: flex;
108+
flex-direction: column;
109+
}
110+
111+
.group-header {
112+
display: flex;
113+
justify-content: space-between;
114+
align-items: center;
115+
margin-bottom: 0.5rem;
116+
}
117+
118+
.group-header p {
119+
margin: 0;
120+
font-weight: 600;
121+
}
122+
123+
.group-squares {
124+
display: grid;
125+
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
126+
gap: 0.5rem;
127+
flex: 1;
128+
}
129+
130+
.square {
131+
border: 1px solid #ddd;
132+
background-color: #fff;
133+
width: 60px;
134+
height: 60px;
135+
display: flex;
136+
justify-content: center;
137+
align-items: center;
138+
border-radius: 8px;
139+
cursor: grab;
140+
text-align: center;
141+
box-sizing: border-box;
142+
overflow: hidden;
143+
word-break: break-word;
144+
white-space: normal;
145+
font-size: calc(12px + 0.2vw);
146+
text-transform: capitalize;
147+
transition: box-shadow 0.2s, transform 0.2s;
148+
}
149+
150+
.square:hover {
151+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
152+
transform: scale(1.03);
153+
}
154+
155+
.square:active {
156+
transform: scale(0.97);
157+
}
158+
159+
.square.dragging {
160+
opacity: 0.5;
161+
}
162+
163+
.frozen {
164+
background-color: #eee;
165+
}
166+
167+
/* Responsive layout for smaller screens */
168+
@media (max-width: 600px) {
169+
#wordInput {
170+
width: 100%;
171+
}
172+
}
173+
</style>
174+
</head>
175+
<body>
176+
<h1>Word Grouping Tool</h1>
177+
178+
<textarea id="wordInput" rows="3" placeholder="Enter 16 words, separated by spaces..."></textarea>
179+
<div class="button-bar">
180+
<button id="generateWords">Generate Squares</button>
181+
<button id="resetSquares">Send All Back</button>
182+
<button id="clearBoard">Clear Board</button>
183+
</div>
184+
185+
<div class="container">
186+
<div id="holdingArea" class="holding-area">
187+
<p>Holding Area</p>
188+
</div>
189+
<div id="groups">
190+
<div class="group" data-group="1">
191+
<div class="group-header">
192+
<p>Group 1</p>
193+
<button class="freezeButton">Freeze</button>
194+
</div>
195+
<div class="group-squares"></div>
196+
</div>
197+
<div class="group" data-group="2">
198+
<div class="group-header">
199+
<p>Group 2</p>
200+
<button class="freezeButton">Freeze</button>
201+
</div>
202+
<div class="group-squares"></div>
203+
</div>
204+
<div class="group" data-group="3">
205+
<div class="group-header">
206+
<p>Group 3</p>
207+
<button class="freezeButton">Freeze</button>
208+
</div>
209+
<div class="group-squares"></div>
210+
</div>
211+
<div class="group" data-group="4">
212+
<div class="group-header">
213+
<p>Group 4</p>
214+
<button class="freezeButton">Freeze</button>
215+
</div>
216+
<div class="group-squares"></div>
217+
</div>
218+
</div>
219+
</div>
220+
221+
<script>
222+
const wordInput = document.getElementById('wordInput');
223+
const generateWords = document.getElementById('generateWords');
224+
const resetSquares = document.getElementById('resetSquares');
225+
const clearBoard = document.getElementById('clearBoard');
226+
const holdingArea = document.getElementById('holdingArea');
227+
const groups = document.querySelectorAll('.group');
228+
229+
// Helper to get the .group-squares container for a given group
230+
function getGroupSquares(groupElement) {
231+
return groupElement.querySelector('.group-squares');
232+
}
233+
234+
// Clear all squares from the holding area and the groups
235+
function clearSquares() {
236+
holdingArea.innerHTML = '<p>Holding Area</p>';
237+
groups.forEach(group => {
238+
const groupNum = group.dataset.group;
239+
group.innerHTML = `
240+
<div class="group-header">
241+
<p>Group ${groupNum}</p>
242+
<button class="freezeButton">Freeze</button>
243+
</div>
244+
<div class="group-squares"></div>
245+
`;
246+
});
247+
}
248+
249+
generateWords.addEventListener('click', () => {
250+
const words = wordInput.value.trim().split(/\s+/);
251+
if (words.length !== 16) {
252+
alert('Please enter exactly 16 words.');
253+
return;
254+
}
255+
256+
// Clear existing squares
257+
clearSquares();
258+
259+
// Create new squares in holding area
260+
const placeholder = document.createElement('p');
261+
placeholder.textContent = 'Holding Area';
262+
holdingArea.appendChild(placeholder);
263+
264+
words.forEach(word => {
265+
const square = document.createElement('div');
266+
square.className = 'square';
267+
square.textContent = word;
268+
square.draggable = true;
269+
270+
square.addEventListener('dragstart', () => {
271+
square.classList.add('dragging');
272+
});
273+
square.addEventListener('dragend', () => {
274+
square.classList.remove('dragging');
275+
});
276+
277+
holdingArea.appendChild(square);
278+
});
279+
280+
attachFreezeListeners();
281+
});
282+
283+
// Send all squares back to holding area except in frozen groups
284+
resetSquares.addEventListener('click', () => {
285+
groups.forEach(group => {
286+
if (!group.classList.contains('frozen')) {
287+
const squares = Array.from(getGroupSquares(group).querySelectorAll('.square'));
288+
squares.forEach(square => holdingArea.appendChild(square));
289+
}
290+
});
291+
});
292+
293+
// Clear board entirely, removing all squares from every container
294+
clearBoard.addEventListener('click', () => {
295+
clearSquares();
296+
wordInput.value = '';
297+
});
298+
299+
// Handle dragover to place squares into whichever container is hovered
300+
document.addEventListener('dragover', (e) => {
301+
e.preventDefault();
302+
const draggingSquare = document.querySelector('.dragging');
303+
if (!draggingSquare) return;
304+
305+
// Gather all droppable containers
306+
const allContainers = [holdingArea];
307+
groups.forEach(g => {
308+
if (!g.classList.contains('frozen')) {
309+
allContainers.push(getGroupSquares(g));
310+
} else {
311+
// If group is frozen, you can't drop into it
312+
}
313+
});
314+
315+
// Find the container under the cursor
316+
const closestContainer = allContainers.find(container => {
317+
const rect = container.getBoundingClientRect();
318+
return (
319+
e.clientX >= rect.left &&
320+
e.clientX <= rect.right &&
321+
e.clientY >= rect.top &&
322+
e.clientY <= rect.bottom
323+
);
324+
});
325+
326+
if (closestContainer) {
327+
closestContainer.appendChild(draggingSquare);
328+
}
329+
});
330+
331+
// Attach freeze/unfreeze listeners to all freeze buttons
332+
function attachFreezeListeners() {
333+
document.querySelectorAll('.freezeButton').forEach(button => {
334+
button.addEventListener('click', (e) => {
335+
e.stopPropagation();
336+
const group = button.closest('.group');
337+
group.classList.toggle('frozen');
338+
button.textContent = group.classList.contains('frozen') ? 'Unfreeze' : 'Freeze';
339+
});
340+
});
341+
}
342+
343+
attachFreezeListeners();
344+
</script>
345+
</body>
346+
</html>

0 commit comments

Comments
 (0)