MarkTheArtist commited on
Commit
e3cc6e9
·
verified ·
1 Parent(s): 7c0d216

Add 2 files

Browse files
Files changed (1) hide show
  1. index.html +655 -381
index.html CHANGED
@@ -3,501 +3,775 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Box Generator</title>
7
  <style>
8
  * {
9
  margin: 0;
10
  padding: 0;
11
  box-sizing: border-box;
12
  }
13
-
14
  body {
15
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
  min-height: 100vh;
18
  padding: 20px;
19
  }
20
-
21
  .container {
22
  max-width: 1400px;
23
  margin: 0 auto;
24
- background: white;
25
  border-radius: 20px;
26
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
 
27
  overflow: hidden;
28
  }
29
-
30
  .header {
31
- background: linear-gradient(135deg, #2d3748 0%, #4a5568 100%);
32
  color: white;
33
  padding: 30px;
34
  text-align: center;
35
  }
36
-
37
  .header h1 {
38
- font-size: 2.5rem;
39
  margin-bottom: 10px;
40
  font-weight: 300;
41
  }
42
-
43
- .content {
 
 
 
 
 
44
  display: grid;
45
- grid-template-columns: 350px 1fr;
46
- min-height: 600px;
 
47
  }
48
-
49
  .controls {
50
- background: #f8fafc;
51
- padding: 30px;
52
- border-right: 1px solid #e2e8f0;
 
53
  }
54
-
55
- .control-group {
56
- margin-bottom: 25px;
 
 
 
 
 
57
  }
58
-
59
- .control-group label {
60
- display: block;
 
 
 
 
 
 
 
 
 
 
 
61
  font-weight: 600;
62
- color: #2d3748;
63
- margin-bottom: 8px;
64
- font-size: 0.9rem;
65
  }
66
-
67
- .control-group input {
68
- width: 100%;
69
- padding: 12px;
70
- border: 2px solid #e2e8f0;
71
- border-radius: 8px;
72
- font-size: 1rem;
73
- transition: border-color 0.3s ease;
74
  }
75
-
76
- .control-group input:focus {
77
- outline: none;
78
- border-color: #667eea;
79
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
 
80
  }
81
-
82
- .control-group select {
 
 
 
 
 
 
 
 
83
  width: 100%;
84
- padding: 12px;
85
- border: 2px solid #e2e8f0;
86
  border-radius: 8px;
87
- font-size: 1rem;
 
88
  background: white;
89
- cursor: pointer;
90
  }
91
-
92
- .generate-btn {
93
- width: 100%;
94
- padding: 15px;
95
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
 
 
 
96
  color: white;
97
  border: none;
98
- border-radius: 10px;
99
- font-size: 1.1rem;
 
100
  font-weight: 600;
101
  cursor: pointer;
102
- transition: transform 0.2s ease;
 
 
103
  }
104
-
105
- .generate-btn:hover {
106
  transform: translateY(-2px);
107
- box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
108
  }
109
-
110
- .preview {
111
- padding: 30px;
112
- background: white;
113
- overflow: auto;
114
  }
115
-
116
- .preview-header {
117
- display: flex;
118
- justify-content: space-between;
119
- align-items: center;
 
 
 
120
  margin-bottom: 20px;
121
  }
122
-
123
- .preview-title {
124
- font-size: 1.5rem;
125
- color: #2d3748;
126
- font-weight: 600;
 
 
127
  }
128
-
129
- .download-btn {
130
- padding: 10px 20px;
131
- background: #48bb78;
132
- color: white;
133
- border: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  border-radius: 8px;
135
  cursor: pointer;
136
- font-weight: 600;
137
- transition: background 0.3s ease;
138
  }
139
-
140
- .download-btn:hover {
141
- background: #38a169;
 
142
  }
143
-
144
- .svg-container {
145
- border: 2px solid #e2e8f0;
146
- border-radius: 10px;
147
- background: #f8fafc;
148
- padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  text-align: center;
150
- min-height: 400px;
151
- display: flex;
152
- align-items: center;
153
- justify-content: center;
154
  }
155
-
156
- svg {
157
- max-width: 100%;
158
- height: auto;
159
- background: white;
160
- border-radius: 5px;
 
 
 
161
  }
162
-
163
- .instructions {
164
- background: #ebf8ff;
165
- border: 1px solid #bee3f8;
166
- border-radius: 8px;
167
- padding: 15px;
168
- margin-top: 20px;
169
- font-size: 0.9rem;
170
- color: #2b6cb0;
171
  }
172
-
173
  @media (max-width: 768px) {
174
- .content {
 
 
 
 
175
  grid-template-columns: 1fr;
176
  }
177
 
178
- .controls {
179
- border-right: none;
180
- border-bottom: 1px solid #e2e8f0;
181
  }
182
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  </style>
184
  </head>
185
  <body>
186
  <div class="container">
187
  <div class="header">
188
- <h1>Box Generator</h1>
189
- <p>Create custom laser-cut box patterns with finger joints</p>
190
  </div>
191
 
192
- <div class="content">
193
  <div class="controls">
194
- <div class="control-group">
195
- <label for="width">Width (inches)</label>
196
- <input type="number" id="width" value="3" min="0.1" step="0.1">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  </div>
198
-
199
- <div class="control-group">
200
- <label for="height">Height (inches)</label>
201
- <input type="number" id="height" value="2" min="0.1" step="0.1">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  </div>
203
-
204
- <div class="control-group">
205
- <label for="depth">Depth (inches)</label>
206
- <input type="number" id="depth" value="2" min="0.1" step="0.1">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  </div>
208
-
209
- <div class="control-group">
210
- <label for="materialThickness">Material Thickness (inches)</label>
211
- <input type="number" id="materialThickness" value="0.11" min="0.01" step="0.01">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  </div>
213
-
214
- <div class="control-group">
215
- <label for="fingerWidth">Finger Width (inches)</label>
216
- <input type="number" id="fingerWidth" value="0.1" min="0.05" step="0.01">
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  </div>
 
 
 
 
 
 
218
 
219
- <div class="control-group">
220
- <label for="boxType">Box Type</label>
221
- <select id="boxType">
222
- <option value="closed">Closed Box (6 pieces)</option>
223
- <option value="open">Open Box (5 pieces)</option>
224
- </select>
225
  </div>
226
-
227
- <button class="generate-btn" onclick="generateBox()">Generate Box</button>
228
-
229
- <div class="instructions">
230
- <strong>Assembly Logic:</strong><br>
231
- <strong>Green sides:</strong> Alternating corner types with solid diagonal corners<br>
232
- <strong>Purple/Yellow:</strong> All edges have slots for side fingers<br>
233
- Assemble by interlocking the alternating corner patterns
 
 
 
 
 
 
 
 
 
 
234
  </div>
235
- </div>
236
-
237
- <div class="preview">
238
- <div class="preview-header">
239
- <div class="preview-title">Box Pattern Preview</div>
240
- <button class="download-btn" onclick="downloadSVG()" id="downloadBtn" style="display:none;">Download SVG</button>
 
241
  </div>
242
-
243
- <div class="svg-container" id="svgContainer">
244
- <p style="color: #718096;">Click "Generate Box" to create your pattern</p>
 
 
 
 
 
 
 
245
  </div>
246
  </div>
247
  </div>
248
  </div>
249
 
250
  <script>
251
- function generateFingers(length, fingerWidth, startWithFinger = true) {
252
- const fingers = [];
253
- let currentPos = 0;
254
- let isFinger = startWithFinger;
255
-
256
- while (currentPos < length) {
257
- const remainingLength = length - currentPos;
258
- const segmentLength = Math.min(fingerWidth, remainingLength);
259
-
260
- fingers.push({
261
- start: currentPos,
262
- length: segmentLength,
263
- isFinger: isFinger
264
- });
265
-
266
- currentPos += segmentLength;
267
- isFinger = !isFinger;
268
- }
269
-
270
- return fingers;
 
 
 
 
 
 
 
271
  }
272
-
273
- function createSidePath(width, height, fingerWidth, materialThickness, sideNumber) {
274
- // Start at upper left corner (0,0)
275
- let path = `M 0 0`;
276
-
277
- // Calculate finger pattern for top edge
278
- const topFingers = generateFingers(width, fingerWidth, sideNumber % 2 === 0);
279
-
280
- // Top edge - move right along the top
281
- let currentX = 0;
282
- topFingers.forEach(finger => {
283
- if (finger.isFinger) {
284
- // Move to start of finger, down material thickness, right finger width, up material thickness
285
- path += ` L ${currentX + finger.start} 0`;
286
- path += ` L ${currentX + finger.start} ${-materialThickness}`;
287
- path += ` L ${currentX + finger.start + finger.length} ${-materialThickness}`;
288
- path += ` L ${currentX + finger.start + finger.length} 0`;
289
- } else {
290
- // Just move right for gaps
291
- path += ` L ${currentX + finger.start + finger.length} 0`;
292
- }
293
  });
294
-
295
- // Now at top-right corner, move down the right edge
296
- const rightFingers = generateFingers(height, fingerWidth, sideNumber % 2 === 1);
297
- let currentY = 0;
298
- rightFingers.forEach(finger => {
299
- if (finger.isFinger) {
300
- // Move to start of finger, right material thickness, down finger width, left material thickness
301
- path += ` L ${width} ${currentY + finger.start}`;
302
- path += ` L ${width + materialThickness} ${currentY + finger.start}`;
303
- path += ` L ${width + materialThickness} ${currentY + finger.start + finger.length}`;
304
- path += ` L ${width} ${currentY + finger.start + finger.length}`;
305
- } else {
306
- // Just move down for gaps
307
- path += ` L ${width} ${currentY + finger.start + finger.length}`;
 
308
  }
309
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
- // Now at bottom-right corner, move left along the bottom
312
- const bottomFingers = generateFingers(width, fingerWidth, sideNumber % 2 === 1);
313
- for (let i = bottomFingers.length - 1; i >= 0; i--) {
314
- const finger = bottomFingers[i];
315
- if (finger.isFinger) {
316
- // Move to end of finger, up material thickness, left finger width, down material thickness
317
- path += ` L ${finger.start + finger.length} ${height}`;
318
- path += ` L ${finger.start + finger.length} ${height + materialThickness}`;
319
- path += ` L ${finger.start} ${height + materialThickness}`;
320
- path += ` L ${finger.start} ${height}`;
321
- } else {
322
- // Just move left for gaps
323
- path += ` L ${finger.start} ${height}`;
324
- }
325
- }
326
 
327
- // Now at bottom-left corner, move up the left edge
328
- const leftFingers = generateFingers(height, fingerWidth, sideNumber % 2 === 0);
329
- for (let i = leftFingers.length - 1; i >= 0; i--) {
330
- const finger = leftFingers[i];
331
- if (finger.isFinger) {
332
- // Move to end of finger, left material thickness, up finger width, right material thickness
333
- path += ` L 0 ${finger.start + finger.length}`;
334
- path += ` L ${-materialThickness} ${finger.start + finger.length}`;
335
- path += ` L ${-materialThickness} ${finger.start}`;
336
- path += ` L 0 ${finger.start}`;
337
- } else {
338
- // Just move up for gaps
339
- path += ` L 0 ${finger.start}`;
340
- }
341
- }
342
 
343
- // Close the path back to start
344
- path += ` Z`;
345
- return path;
346
- }
347
-
348
- function createTopBottomPath(width, depth, fingerWidth, materialThickness) {
349
- const topFingers = generateFingers(width, fingerWidth, false);
350
- const rightFingers = generateFingers(depth, fingerWidth, false);
351
- const bottomFingers = generateFingers(width, fingerWidth, false);
352
- const leftFingers = generateFingers(depth, fingerWidth, false);
353
 
354
- let path = `M 0 0`;
 
355
 
356
- // Top edge (slots for side fingers)
357
- topFingers.forEach(finger => {
358
- if (finger.isFinger) {
359
- path += ` L ${finger.start} 0 L ${finger.start} ${materialThickness} L ${finger.start + finger.length} ${materialThickness} L ${finger.start + finger.length} 0`;
360
- } else {
361
- path += ` L ${finger.start + finger.length} 0`;
362
- }
363
- });
364
 
365
- // Right edge (slots for side fingers)
366
- rightFingers.forEach(finger => {
367
- if (finger.isFinger) {
368
- path += ` L ${width} ${finger.start} L ${width - materialThickness} ${finger.start} L ${width - materialThickness} ${finger.start + finger.length} L ${width} ${finger.start + finger.length}`;
369
- } else {
370
- path += ` L ${width} ${finger.start + finger.length}`;
371
- }
372
- });
373
 
374
- // Bottom edge (slots for side fingers)
375
- for (let i = bottomFingers.length - 1; i >= 0; i--) {
376
- const finger = bottomFingers[i];
377
- if (finger.isFinger) {
378
- path += ` L ${finger.start + finger.length} ${depth} L ${finger.start + finger.length} ${depth - materialThickness} L ${finger.start} ${depth - materialThickness} L ${finger.start} ${depth}`;
379
- } else {
380
- path += ` L ${finger.start} ${depth}`;
381
- }
382
- }
383
 
384
- // Left edge (slots for side fingers)
385
- for (let i = leftFingers.length - 1; i >= 0; i--) {
386
- const finger = leftFingers[i];
387
- if (finger.isFinger) {
388
- path += ` L 0 ${finger.start + finger.length} L ${materialThickness} ${finger.start + finger.length} L ${materialThickness} ${finger.start} L 0 ${finger.start}`;
389
- } else {
390
- path += ` L 0 ${finger.start}`;
391
- }
392
- }
393
 
394
- path += ` Z`;
395
- return path;
396
  }
397
-
398
- function generateBox() {
399
- const width = parseFloat(document.getElementById('width').value);
400
- const height = parseFloat(document.getElementById('height').value);
401
- const depth = parseFloat(document.getElementById('depth').value);
402
- const materialThickness = parseFloat(document.getElementById('materialThickness').value);
403
- const fingerWidth = parseFloat(document.getElementById('fingerWidth').value);
404
- const boxType = document.getElementById('boxType').value;
405
 
406
- const scale = 50; // Scale factor for display
407
- const margin = 30;
 
408
 
409
- // Calculate piece dimensions
410
- const sideWidth = width + 2 * materialThickness;
411
- const sideHeight = height + 2 * materialThickness;
412
- const topBottomWidth = width + 2 * materialThickness;
413
- const topBottomDepth = depth + 2 * materialThickness;
414
 
415
- // Layout pieces
416
- const pieces = [];
417
- let currentX = margin;
418
- let currentY = margin;
419
- let maxRowHeight = 0;
420
 
421
- // Add 4 side pieces (green) with alternating patterns
422
- for (let i = 0; i < 4; i++) {
423
- if (currentX + sideWidth * scale > 800) {
424
- currentX = margin;
425
- currentY += maxRowHeight + margin;
426
- maxRowHeight = 0;
427
- }
428
-
429
- pieces.push({
430
- type: 'side',
431
- x: currentX,
432
- y: currentY,
433
- path: createSidePath(width, height, fingerWidth, materialThickness, i),
434
- color: '#22c55e'
435
- });
436
-
437
- currentX += sideWidth * scale + margin;
438
- maxRowHeight = Math.max(maxRowHeight, sideHeight * scale);
439
  }
440
 
441
- // Add top and bottom pieces (purple) for closed box, or just bottom (yellow) for open box
442
- const topBottomCount = boxType === 'closed' ? 2 : 1;
443
- const topBottomColor = boxType === 'closed' ? '#a855f7' : '#eab308';
 
 
 
 
 
 
 
444
 
445
- for (let i = 0; i < topBottomCount; i++) {
446
- if (currentX + topBottomWidth * scale > 800) {
447
- currentX = margin;
448
- currentY += maxRowHeight + margin;
449
- maxRowHeight = 0;
450
- }
451
-
452
- pieces.push({
453
- type: 'topbottom',
454
- x: currentX,
455
- y: currentY,
456
- path: createTopBottomPath(width, depth, fingerWidth, materialThickness),
457
- color: topBottomColor
458
- });
459
-
460
- currentX += topBottomWidth * scale + margin;
461
- maxRowHeight = Math.max(maxRowHeight, topBottomDepth * scale);
462
  }
 
 
 
 
 
 
463
 
464
- // Create SVG
465
- const totalWidth = 800;
466
- const totalHeight = currentY + maxRowHeight + margin;
467
-
468
- let svg = `<svg width="${totalWidth}" height="${totalHeight}" viewBox="0 0 ${totalWidth} ${totalHeight}" xmlns="http://www.w3.org/2000/svg">`;
469
 
470
- pieces.forEach(piece => {
471
- svg += `<g transform="translate(${piece.x}, ${piece.y}) scale(${scale})">`;
472
- svg += `<path d="${piece.path}" fill="${piece.color}" stroke="#000" stroke-width="${1/scale}" opacity="0.8"/>`;
473
- svg += `</g>`;
474
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- svg += `</svg>`;
 
477
 
478
- document.getElementById('svgContainer').innerHTML = svg;
479
- document.getElementById('downloadBtn').style.display = 'block';
 
 
 
 
480
  }
481
-
482
- function downloadSVG() {
483
- const svgElement = document.querySelector('#svgContainer svg');
484
- if (!svgElement) return;
485
-
486
- const svgData = new XMLSerializer().serializeToString(svgElement);
487
- const blob = new Blob([svgData], { type: 'image/svg+xml' });
488
- const url = URL.createObjectURL(blob);
489
 
490
- const a = document.createElement('a');
491
- a.href = url;
492
- a.download = 'box-pattern.svg';
493
- document.body.appendChild(a);
494
- a.click();
495
- document.body.removeChild(a);
496
- URL.revokeObjectURL(url);
497
  }
498
-
499
- // Generate initial box
500
- generateBox();
501
- </script>
502
- <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MarkTheArtist/cube-8" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
503
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced Laser-Cut Box Generator</title>
7
  <style>
8
  * {
9
  margin: 0;
10
  padding: 0;
11
  box-sizing: border-box;
12
  }
13
+
14
  body {
15
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
  min-height: 100vh;
18
  padding: 20px;
19
  }
20
+
21
  .container {
22
  max-width: 1400px;
23
  margin: 0 auto;
24
+ background: rgba(255, 255, 255, 0.95);
25
  border-radius: 20px;
26
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
27
+ backdrop-filter: blur(10px);
28
  overflow: hidden;
29
  }
30
+
31
  .header {
32
+ background: linear-gradient(135deg, #2c3e50, #34495e);
33
  color: white;
34
  padding: 30px;
35
  text-align: center;
36
  }
37
+
38
  .header h1 {
39
+ font-size: 2.5em;
40
  margin-bottom: 10px;
41
  font-weight: 300;
42
  }
43
+
44
+ .header p {
45
+ opacity: 0.9;
46
+ font-size: 1.1em;
47
+ }
48
+
49
+ .main-content {
50
  display: grid;
51
+ grid-template-columns: 1fr 1fr;
52
+ gap: 0;
53
+ min-height: 800px;
54
  }
55
+
56
  .controls {
57
+ padding: 40px;
58
+ background: #f8f9fa;
59
+ border-right: 1px solid #e9ecef;
60
+ overflow-y: auto;
61
  }
62
+
63
+ .preview {
64
+ padding: 40px;
65
+ background: white;
66
+ display: flex;
67
+ flex-direction: column;
68
+ align-items: center;
69
+ justify-content: center;
70
  }
71
+
72
+ .section {
73
+ margin-bottom: 30px;
74
+ background: white;
75
+ border-radius: 12px;
76
+ padding: 25px;
77
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
78
+ border: 1px solid #e9ecef;
79
+ }
80
+
81
+ .section h3 {
82
+ color: #2c3e50;
83
+ margin-bottom: 20px;
84
+ font-size: 1.2em;
85
  font-weight: 600;
86
+ border-bottom: 2px solid #3498db;
87
+ padding-bottom: 10px;
 
88
  }
89
+
90
+ .form-group {
91
+ margin-bottom: 20px;
 
 
 
 
 
92
  }
93
+
94
+ .form-row {
95
+ display: grid;
96
+ grid-template-columns: 1fr 1fr 1fr;
97
+ gap: 15px;
98
+ margin-bottom: 20px;
99
  }
100
+
101
+ label {
102
+ display: block;
103
+ margin-bottom: 8px;
104
+ font-weight: 500;
105
+ color: #34495e;
106
+ font-size: 0.9em;
107
+ }
108
+
109
+ input, select {
110
  width: 100%;
111
+ padding: 12px 15px;
112
+ border: 2px solid #e9ecef;
113
  border-radius: 8px;
114
+ font-size: 0.95em;
115
+ transition: all 0.3s ease;
116
  background: white;
 
117
  }
118
+
119
+ input:focus, select:focus {
120
+ outline: none;
121
+ border-color: #3498db;
122
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
123
+ }
124
+
125
+ .btn {
126
+ background: linear-gradient(135deg, #3498db, #2980b9);
127
  color: white;
128
  border: none;
129
+ padding: 15px 30px;
130
+ border-radius: 8px;
131
+ font-size: 1em;
132
  font-weight: 600;
133
  cursor: pointer;
134
+ transition: all 0.3s ease;
135
+ width: 100%;
136
+ margin-top: 10px;
137
  }
138
+
139
+ .btn:hover {
140
  transform: translateY(-2px);
141
+ box-shadow: 0 8px 25px rgba(52, 152, 219, 0.3);
142
  }
143
+
144
+ .btn:active {
145
+ transform: translateY(0);
 
 
146
  }
147
+
148
+ .preview-canvas {
149
+ width: 100%;
150
+ max-width: 500px;
151
+ height: 400px;
152
+ border: 2px solid #e9ecef;
153
+ border-radius: 12px;
154
+ background: #fdfdfd;
155
  margin-bottom: 20px;
156
  }
157
+
158
+ .dimensions-display {
159
+ background: #f8f9fa;
160
+ padding: 20px;
161
+ border-radius: 8px;
162
+ margin-bottom: 20px;
163
+ font-family: 'Courier New', monospace;
164
  }
165
+
166
+ .help-text {
167
+ font-size: 0.8em;
168
+ color: #7f8c8d;
169
+ margin-top: 5px;
170
+ line-height: 1.4;
171
+ }
172
+
173
+ .warning {
174
+ background: #fff3cd;
175
+ border: 1px solid #ffeaa7;
176
+ color: #856404;
177
+ padding: 12px;
178
+ border-radius: 6px;
179
+ margin-top: 10px;
180
+ font-size: 0.9em;
181
+ }
182
+
183
+ .success {
184
+ background: #d4edda;
185
+ border: 1px solid #c3e6cb;
186
+ color: #155724;
187
+ padding: 12px;
188
+ border-radius: 6px;
189
+ margin-top: 10px;
190
+ font-size: 0.9em;
191
+ }
192
+
193
+ .kerf-test {
194
+ background: #e8f4f8;
195
+ border: 1px solid #bee5eb;
196
+ padding: 15px;
197
+ border-radius: 8px;
198
+ margin-top: 15px;
199
+ }
200
+
201
+ .joint-preview {
202
+ display: grid;
203
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
204
+ gap: 15px;
205
+ margin-top: 15px;
206
+ }
207
+
208
+ .joint-option {
209
+ text-align: center;
210
+ padding: 15px;
211
+ border: 2px solid #e9ecef;
212
  border-radius: 8px;
213
  cursor: pointer;
214
+ transition: all 0.3s ease;
215
+ background: white;
216
  }
217
+
218
+ .joint-option:hover {
219
+ border-color: #3498db;
220
+ background: #f8f9fa;
221
  }
222
+
223
+ .joint-option.selected {
224
+ border-color: #3498db;
225
+ background: #e8f4f8;
226
+ }
227
+
228
+ .joint-diagram {
229
+ width: 80px;
230
+ height: 40px;
231
+ margin: 0 auto 10px;
232
+ background: #34495e;
233
+ position: relative;
234
+ border-radius: 2px;
235
+ }
236
+
237
+ .finger-joint::after {
238
+ content: '';
239
+ position: absolute;
240
+ top: -5px;
241
+ left: 10px;
242
+ right: 10px;
243
+ height: 50px;
244
+ background: repeating-linear-gradient(to right, #3498db 0px, #3498db 8px, transparent 8px, transparent 16px);
245
+ }
246
+
247
+ .download-section {
248
  text-align: center;
249
+ margin-top: 30px;
 
 
 
250
  }
251
+
252
+ .file-format {
253
+ display: inline-block;
254
+ margin: 0 10px;
255
+ padding: 10px 20px;
256
+ background: #ecf0f1;
257
+ border-radius: 6px;
258
+ cursor: pointer;
259
+ transition: all 0.3s ease;
260
  }
261
+
262
+ .file-format:hover {
263
+ background: #3498db;
264
+ color: white;
 
 
 
 
 
265
  }
266
+
267
  @media (max-width: 768px) {
268
+ .main-content {
269
+ grid-template-columns: 1fr;
270
+ }
271
+
272
+ .form-row {
273
  grid-template-columns: 1fr;
274
  }
275
 
276
+ .container {
277
+ margin: 10px;
 
278
  }
279
  }
280
+
281
+ .material-guide {
282
+ background: #f8f9fa;
283
+ border-left: 4px solid #3498db;
284
+ padding: 15px;
285
+ margin: 15px 0;
286
+ }
287
+
288
+ .material-guide h4 {
289
+ color: #2c3e50;
290
+ margin-bottom: 10px;
291
+ }
292
+
293
+ .stats-grid {
294
+ display: grid;
295
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
296
+ gap: 15px;
297
+ margin-top: 20px;
298
+ }
299
+
300
+ .stat-card {
301
+ background: white;
302
+ padding: 15px;
303
+ border-radius: 8px;
304
+ border: 1px solid #e9ecef;
305
+ text-align: center;
306
+ }
307
+
308
+ .stat-value {
309
+ font-size: 1.5em;
310
+ font-weight: bold;
311
+ color: #3498db;
312
+ }
313
+
314
+ .stat-label {
315
+ font-size: 0.9em;
316
+ color: #7f8c8d;
317
+ margin-top: 5px;
318
+ }
319
  </style>
320
  </head>
321
  <body>
322
  <div class="container">
323
  <div class="header">
324
+ <h1>🔥 Advanced Laser-Cut Box Generator</h1>
325
+ <p>Professional-grade parametric box design with precision kerf compensation and multiple joint types</p>
326
  </div>
327
 
328
+ <div class="main-content">
329
  <div class="controls">
330
+ <div class="section">
331
+ <h3>📐 Box Dimensions</h3>
332
+ <div class="form-row">
333
+ <div class="form-group">
334
+ <label for="width">Width (mm)</label>
335
+ <input type="number" id="width" value="100" min="10" max="1000" step="0.1">
336
+ <div class="help-text">Internal width of the box</div>
337
+ </div>
338
+ <div class="form-group">
339
+ <label for="depth">Depth (mm)</label>
340
+ <input type="number" id="depth" value="80" min="10" max="1000" step="0.1">
341
+ <div class="help-text">Internal depth of the box</div>
342
+ </div>
343
+ <div class="form-group">
344
+ <label for="height">Height (mm)</label>
345
+ <input type="number" id="height" value="60" min="10" max="1000" step="0.1">
346
+ <div class="help-text">Internal height of the box</div>
347
+ </div>
348
+ </div>
349
  </div>
350
+
351
+ <div class="section">
352
+ <h3>🔧 Material & Cutting Settings</h3>
353
+ <div class="form-row">
354
+ <div class="form-group">
355
+ <label for="thickness">Material Thickness (mm)</label>
356
+ <input type="number" id="thickness" value="3.0" min="0.1" max="20" step="0.1">
357
+ <div class="help-text">Actual measured thickness</div>
358
+ </div>
359
+ <div class="form-group">
360
+ <label for="kerf">Laser Kerf (mm)</label>
361
+ <input type="number" id="kerf" value="0.1" min="0" max="1" step="0.01">
362
+ <div class="help-text">Material removed by laser</div>
363
+ </div>
364
+ <div class="form-group">
365
+ <label for="materialType">Material Type</label>
366
+ <select id="materialType">
367
+ <option value="plywood">Plywood</option>
368
+ <option value="mdf">MDF</option>
369
+ <option value="acrylic">Acrylic</option>
370
+ <option value="cardboard">Cardboard</option>
371
+ <option value="custom">Custom</option>
372
+ </select>
373
+ </div>
374
+ </div>
375
+
376
+ <div class="kerf-test">
377
+ <strong>🎯 Kerf Testing Tip:</strong> Cut a small test piece with interlocking tabs to verify your kerf setting before cutting the final box.
378
+ </div>
379
  </div>
380
+
381
+ <div class="section">
382
+ <h3>⚙️ Joint Configuration</h3>
383
+ <div class="form-group">
384
+ <label for="jointType">Joint Type</label>
385
+ <div class="joint-preview">
386
+ <div class="joint-option selected" data-joint="finger">
387
+ <div class="joint-diagram finger-joint"></div>
388
+ <div>Finger Joints</div>
389
+ </div>
390
+ <div class="joint-option" data-joint="dovetail">
391
+ <div class="joint-diagram"></div>
392
+ <div>Dovetail</div>
393
+ </div>
394
+ <div class="joint-option" data-joint="simple">
395
+ <div class="joint-diagram"></div>
396
+ <div>Simple Tab</div>
397
+ </div>
398
+ </div>
399
+ </div>
400
+
401
+ <div class="form-row">
402
+ <div class="form-group">
403
+ <label for="fingerWidth">Tab Width (mm)</label>
404
+ <input type="number" id="fingerWidth" value="10" min="2" max="50" step="0.5">
405
+ <div class="help-text">Width of individual tabs</div>
406
+ </div>
407
+ <div class="form-group">
408
+ <label for="fingerCount">Fingers per Edge</label>
409
+ <select id="fingerCount">
410
+ <option value="auto">Auto Calculate</option>
411
+ <option value="3">3</option>
412
+ <option value="5">5</option>
413
+ <option value="7">7</option>
414
+ <option value="9">9</option>
415
+ <option value="11">11</option>
416
+ </select>
417
+ </div>
418
+ <div class="form-group">
419
+ <label for="fingerOffset">Edge Offset (mm)</label>
420
+ <input type="number" id="fingerOffset" value="0" min="0" max="20" step="0.5">
421
+ <div class="help-text">Distance from edge to first tab</div>
422
+ </div>
423
+ </div>
424
  </div>
425
+
426
+ <div class="section">
427
+ <h3>📦 Box Configuration</h3>
428
+ <div class="form-row">
429
+ <div class="form-group">
430
+ <label for="boxType">Box Type</label>
431
+ <select id="boxType">
432
+ <option value="open">Open Top</option>
433
+ <option value="closed">Closed Box</option>
434
+ <option value="lid">Box with Lid</option>
435
+ <option value="sliding">Sliding Lid</option>
436
+ </select>
437
+ </div>
438
+ <div class="form-group">
439
+ <label for="bottomType">Bottom Type</label>
440
+ <select id="bottomType">
441
+ <option value="attached">Attached</option>
442
+ <option value="inset">Inset</option>
443
+ <option value="none">No Bottom</option>
444
+ </select>
445
+ </div>
446
+ <div class="form-group">
447
+ <label for="cornerType">Corner Style</label>
448
+ <select id="cornerType">
449
+ <option value="square">Square</option>
450
+ <option value="rounded">Rounded</option>
451
+ <option value="chamfered">Chamfered</option>
452
+ </select>
453
+ </div>
454
+ </div>
455
  </div>
456
+
457
+ <div class="section">
458
+ <h3>🎛️ Advanced Options</h3>
459
+ <div class="form-group">
460
+ <label>
461
+ <input type="checkbox" id="addHoles"> Add mounting holes
462
+ </label>
463
+ <label>
464
+ <input type="checkbox" id="addDividers"> Include dividers
465
+ </label>
466
+ <label>
467
+ <input type="checkbox" id="addVents"> Add ventilation holes
468
+ </label>
469
+ <label>
470
+ <input type="checkbox" id="addLabels"> Add part labels
471
+ </label>
472
+ </div>
473
  </div>
474
+
475
+ <button class="btn" onclick="generateBox()">🚀 Generate Box Design</button>
476
+ </div>
477
+
478
+ <div class="preview">
479
+ <canvas class="preview-canvas" id="previewCanvas"></canvas>
480
 
481
+ <div class="dimensions-display" id="dimensionsDisplay">
482
+ <strong>📏 Calculated Dimensions:</strong><br>
483
+ External: 106.0 × 86.0 × 66.0 mm<br>
484
+ Material Usage: 520.0 cm²<br>
485
+ Cutting Time: ~8 minutes
 
486
  </div>
487
+
488
+ <div class="stats-grid">
489
+ <div class="stat-card">
490
+ <div class="stat-value" id="partCount">6</div>
491
+ <div class="stat-label">Parts</div>
492
+ </div>
493
+ <div class="stat-card">
494
+ <div class="stat-value" id="cutLength">2.4m</div>
495
+ <div class="stat-label">Cut Length</div>
496
+ </div>
497
+ <div class="stat-card">
498
+ <div class="stat-value" id="materialUsage">520</div>
499
+ <div class="stat-label">cm² Material</div>
500
+ </div>
501
+ <div class="stat-card">
502
+ <div class="stat-value" id="efficiency">85%</div>
503
+ <div class="stat-label">Efficiency</div>
504
+ </div>
505
  </div>
506
+
507
+ <div class="download-section">
508
+ <h3>📥 Download Files</h3>
509
+ <div class="file-format" onclick="downloadFile('svg')">SVG</div>
510
+ <div class="file-format" onclick="downloadFile('dxf')">DXF</div>
511
+ <div class="file-format" onclick="downloadFile('pdf')">PDF</div>
512
+ <div class="file-format" onclick="downloadFile('eps')">EPS</div>
513
  </div>
514
+
515
+ <div class="material-guide">
516
+ <h4>📋 Cutting Guidelines</h4>
517
+ <p><strong>Recommended Settings:</strong></p>
518
+ <ul>
519
+ <li>3mm Plywood: 1000mm/min, 80% power</li>
520
+ <li>Cut in this order: 1. Inner holes 2. Finger joints 3. Outer edges</li>
521
+ <li>Use air assist for clean cuts</li>
522
+ <li>Test fit corner pieces before full cut</li>
523
+ </ul>
524
  </div>
525
  </div>
526
  </div>
527
  </div>
528
 
529
  <script>
530
+ // Global variables for box configuration
531
+ let boxConfig = {
532
+ width: 100,
533
+ depth: 80,
534
+ height: 60,
535
+ thickness: 3.0,
536
+ kerf: 0.1,
537
+ jointType: 'finger',
538
+ fingerWidth: 10,
539
+ fingerCount: 'auto',
540
+ boxType: 'open',
541
+ bottomType: 'attached'
542
+ };
543
+
544
+ // Material presets for common laser cutting materials
545
+ const materialPresets = {
546
+ plywood: { kerf: 0.1, density: 0.6 },
547
+ mdf: { kerf: 0.12, density: 0.75 },
548
+ acrylic: { kerf: 0.05, density: 1.18 },
549
+ cardboard: { kerf: 0.02, density: 0.7 }
550
+ };
551
+
552
+ // Initialize the application
553
+ function init() {
554
+ setupEventListeners();
555
+ drawPreview();
556
+ updateDimensions();
557
  }
558
+
559
+ function setupEventListeners() {
560
+ // Dimension inputs
561
+ ['width', 'depth', 'height', 'thickness', 'kerf', 'fingerWidth'].forEach(id => {
562
+ document.getElementById(id).addEventListener('input', function() {
563
+ boxConfig[id] = parseFloat(this.value);
564
+ updateDisplay();
565
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  });
567
+
568
+ // Select inputs
569
+ ['fingerCount', 'boxType', 'bottomType', 'cornerType'].forEach(id => {
570
+ document.getElementById(id).addEventListener('change', function() {
571
+ boxConfig[id] = this.value;
572
+ updateDisplay();
573
+ });
574
+ });
575
+
576
+ // Material type change
577
+ document.getElementById('materialType').addEventListener('change', function() {
578
+ if (materialPresets[this.value]) {
579
+ document.getElementById('kerf').value = materialPresets[this.value].kerf;
580
+ boxConfig.kerf = materialPresets[this.value].kerf;
581
+ updateDisplay();
582
  }
583
  });
584
+
585
+ // Joint type selection
586
+ document.querySelectorAll('.joint-option').forEach(option => {
587
+ option.addEventListener('click', function() {
588
+ document.querySelectorAll('.joint-option').forEach(o => o.classList.remove('selected'));
589
+ this.classList.add('selected');
590
+ boxConfig.jointType = this.dataset.joint;
591
+ updateDisplay();
592
+ });
593
+ });
594
+ }
595
+
596
+ function updateDisplay() {
597
+ drawPreview();
598
+ updateDimensions();
599
+ updateStats();
600
+ }
601
+
602
+ function drawPreview() {
603
+ const canvas = document.getElementById('previewCanvas');
604
+ const ctx = canvas.getContext('2d');
605
 
606
+ // Set canvas size
607
+ canvas.width = canvas.offsetWidth;
608
+ canvas.height = canvas.offsetHeight;
 
 
 
 
 
 
 
 
 
 
 
 
609
 
610
+ // Clear canvas
611
+ ctx.fillStyle = '#fdfdfd';
612
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
 
 
 
 
 
 
 
 
 
 
 
 
613
 
614
+ // Calculate scaling to fit the box in the canvas
615
+ const margin = 40;
616
+ const availableWidth = canvas.width - 2 * margin;
617
+ const availableHeight = canvas.height - 2 * margin;
 
 
 
 
 
 
618
 
619
+ const boxWidth = boxConfig.width + 2 * boxConfig.thickness;
620
+ const boxHeight = boxConfig.height + boxConfig.thickness;
621
 
622
+ const scale = Math.min(availableWidth / boxWidth, availableHeight / boxHeight);
 
 
 
 
 
 
 
623
 
624
+ // Center the drawing
625
+ const centerX = canvas.width / 2;
626
+ const centerY = canvas.height / 2;
 
 
 
 
 
627
 
628
+ ctx.save();
629
+ ctx.translate(centerX, centerY);
630
+ ctx.scale(scale, scale);
 
 
 
 
 
 
631
 
632
+ // Draw 3D isometric view of the box
633
+ drawIsometricBox(ctx);
 
 
 
 
 
 
 
634
 
635
+ ctx.restore();
 
636
  }
637
+
638
+ function drawIsometricBox(ctx) {
639
+ const w = boxConfig.width;
640
+ const h = boxConfig.height;
641
+ const d = boxConfig.depth;
642
+ const t = boxConfig.thickness;
 
 
643
 
644
+ // Isometric transformation
645
+ const isoX = (x, y) => x * 0.866 - y * 0.866;
646
+ const isoY = (x, y, z) => x * 0.5 + y * 0.5 - z;
647
 
648
+ // Colors for different faces
649
+ const frontColor = '#3498db';
650
+ const topColor = '#2980b9';
651
+ const sideColor = '#34495e';
 
652
 
653
+ ctx.lineWidth = 2;
654
+ ctx.strokeStyle = '#2c3e50';
 
 
 
655
 
656
+ // Draw front face
657
+ ctx.fillStyle = frontColor;
658
+ ctx.beginPath();
659
+ ctx.moveTo(isoX(-w/2, 0), isoY(-w/2, 0, h));
660
+ ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, h));
661
+ ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, 0));
662
+ ctx.lineTo(isoX(-w/2, 0), isoY(-w/2, 0, 0));
663
+ ctx.closePath();
664
+ ctx.fill();
665
+ ctx.stroke();
666
+
667
+ // Draw finger joints on front face
668
+ if (boxConfig.jointType === 'finger') {
669
+ drawFingerJoints(ctx, isoX(-w/2, 0), isoY(-w/2, 0, h), isoX(w/2, 0), isoY(w/2, 0, h), 'horizontal');
 
 
 
 
670
  }
671
 
672
+ // Draw right side face
673
+ ctx.fillStyle = sideColor;
674
+ ctx.beginPath();
675
+ ctx.moveTo(isoX(w/2, 0), isoY(w/2, 0, h));
676
+ ctx.lineTo(isoX(w/2, -d), isoY(w/2, -d, h));
677
+ ctx.lineTo(isoX(w/2, -d), isoY(w/2, -d, 0));
678
+ ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, 0));
679
+ ctx.closePath();
680
+ ctx.fill();
681
+ ctx.stroke();
682
 
683
+ // Draw top face (if closed box)
684
+ if (boxConfig.boxType !== 'open') {
685
+ ctx.fillStyle = topColor;
686
+ ctx.beginPath();
687
+ ctx.moveTo(isoX(-w/2, 0), isoY(-w/2, 0, h));
688
+ ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, h));
689
+ ctx.lineTo(isoX(w/2, -d), isoY(w/2, -d, h));
690
+ ctx.lineTo(isoX(-w/2, -d), isoY(-w/2, -d, h));
691
+ ctx.closePath();
692
+ ctx.fill();
693
+ ctx.stroke();
 
 
 
 
 
 
694
  }
695
+ }
696
+
697
+ function drawFingerJoints(ctx, x1, y1, x2, y2, orientation) {
698
+ const fingerCount = calculateFingerCount();
699
+ const edgeLength = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
700
+ const fingerWidth = edgeLength / (fingerCount * 2 - 1);
701
 
702
+ ctx.strokeStyle = '#e74c3c';
703
+ ctx.lineWidth = 1;
 
 
 
704
 
705
+ for (let i = 0; i < fingerCount; i++) {
706
+ const startRatio = (i * 2) / (fingerCount * 2 - 1);
707
+ const endRatio = (i * 2 + 1) / (fingerCount * 2 - 1);
708
+
709
+ const startX = x1 + (x2 - x1) * startRatio;
710
+ const startY = y1 + (y2 - y1) * startRatio;
711
+ const endX = x1 + (x2 - x1) * endRatio;
712
+ const endY = y1 + (y2 - y1) * endRatio;
713
+
714
+ ctx.beginPath();
715
+ ctx.moveTo(startX, startY);
716
+ ctx.lineTo(endX, endY);
717
+ ctx.stroke();
718
+ }
719
+ }
720
+
721
+ function calculateFingerCount() {
722
+ if (boxConfig.fingerCount === 'auto') {
723
+ // Auto-calculate based on edge length and finger width
724
+ const edgeLength = Math.max(boxConfig.width, boxConfig.depth);
725
+ const count = Math.floor(edgeLength / (boxConfig.fingerWidth * 2)) * 2 + 1;
726
+ return Math.max(3, Math.min(11, count));
727
+ }
728
+ return parseInt(boxConfig.fingerCount);
729
+ }
730
+
731
+ function updateDimensions() {
732
+ const external = {
733
+ width: boxConfig.width + 2 * boxConfig.thickness,
734
+ depth: boxConfig.depth + 2 * boxConfig.thickness,
735
+ height: boxConfig.height + boxConfig.thickness
736
+ };
737
 
738
+ const materialUsage = calculateMaterialUsage();
739
+ const cuttingTime = estimateCuttingTime();
740
 
741
+ document.getElementById('dimensionsDisplay').innerHTML = `
742
+ <strong>📏 Calculated Dimensions:</strong><br>
743
+ External: ${external.width.toFixed(1)} × ${external.depth.toFixed(1)} × ${external.height.toFixed(1)} mm<br>
744
+ Material Usage: ${materialUsage.toFixed(0)} cm²<br>
745
+ Cutting Time: ~${cuttingTime} minutes
746
+ `;
747
  }
748
+
749
+ function updateStats() {
750
+ const partCount = calculatePartCount();
751
+ const cutLength = calculateCutLength();
752
+ const materialUsage = calculateMaterialUsage();
753
+ const efficiency = calculateEfficiency();
 
 
754
 
755
+ document.getElementById('partCount').textContent = partCount;
756
+ document.getElementById('cutLength').textContent = (cutLength / 1000).toFixed(1) + 'm';
757
+ document.getElementById('materialUsage').textContent = Math.round(materialUsage);
758
+ document.getElementById('efficiency').textContent = Math.round(efficiency) + '%';
 
 
 
759
  }
760
+
761
+ function calculatePartCount() {
762
+ let count = 4; // Four sides
763
+ if (boxConfig.bottomType !== 'none') count += 1; // Bottom
764
+ if (boxConfig.boxType === 'closed' || boxConfig.boxType === 'lid') count += 1; // Top
765
+ return count;
766
+ }
767
+
768
+ function calculateCutLength() {
769
+ const perimeter = 2 * (boxConfig.width + boxConfig.depth + boxConfig.height + boxConfig.thickness);
770
+ const fingerCount = calculateFingerCount();
771
+ const fingerLength = fingerCount * 8 * boxConfig.fingerWidth; // Approximate finger joint length
772
+ return perimeter + fingerLength;
773
+ }
774
+
775
+ function calculateMaterialUsage() {
776
+ const area1 = 2 * (boxConfig.width + 2 * boxConfig.thickness) * (boxConfig.height + boxConfig.thickness);
777
+ const area2 = 2 * (boxConfig.depth + 2 * boxConfig.thickness) * (boxConfig.height + boxConfig.thickness);