phenixrhyder commited on
Commit
7d51cd1
·
unverified ·
1 Parent(s): f86798f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +375 -0
index.html CHANGED
@@ -1 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Frame Studio</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
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=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Poppins', sans-serif;
14
+ background-color: #f9fafb; /* gray-50 */
15
+ }
16
+ /* Custom scrollbar for the new theme */
17
+ ::-webkit-scrollbar { width: 8px; }
18
+ ::-webkit-scrollbar-track { background: #e5e7eb; } /* gray-200 */
19
+ ::-webkit-scrollbar-thumb { background: #f472b6; border-radius: 4px; } /* pink-400 */
20
+ ::-webkit-scrollbar-thumb:hover { background: #ec4899; } /* pink-500 */
21
 
22
+ /* Custom styles for range inputs - Light Mode */
23
+ input[type="range"] {
24
+ -webkit-appearance: none; appearance: none; width: 100%; height: 6px;
25
+ background: #e5e7eb; /* gray-200 */ border-radius: 3px;
26
+ outline: none; transition: background 0.2s;
27
+ }
28
+ input[type="range"]::-webkit-slider-thumb {
29
+ -webkit-appearance: none; appearance: none; width: 20px; height: 20px;
30
+ border-radius: 50%; background: #f9fafb; /* gray-50 */ cursor: pointer;
31
+ border: 4px solid #ec4899; /* pink-500 */
32
+ transition: transform 0.2s ease-in-out;
33
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
34
+ }
35
+ input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.1); }
36
+
37
+ /* Custom styles for color inputs - Light Mode */
38
+ input[type="color"] {
39
+ -webkit-appearance: none; -moz-appearance: none; appearance: none;
40
+ width: 48px; height: 48px; background-color: transparent;
41
+ border: none; cursor: pointer; border-radius: 0.5rem;
42
+ }
43
+ input[type="color"]::-webkit-color-swatch {
44
+ border-radius: 0.5rem;
45
+ border: 2px solid #d1d5db; /* gray-300 */
46
+ }
47
+
48
+ /* Modal styles */
49
+ #saveModal { display: none; }
50
+ </style>
51
+ </head>
52
+ <body class="text-gray-800">
53
+
54
+ <div class="container mx-auto p-4 lg:p-8">
55
+ <!-- Header -->
56
+ <header class="text-center mb-8 lg:mb-12">
57
+ <h1 class="text-4xl lg:text-5xl font-bold text-gray-900 tracking-tight">Frame Studio</h1>
58
+ <p class="text-lg text-gray-500 mt-2">Craft your perfect checkerboard frame.</p>
59
+ </header>
60
+
61
+ <main class="grid grid-cols-1 lg:grid-cols-5 gap-8">
62
+ <!-- Controls Column -->
63
+ <div class="lg:col-span-2 space-y-8">
64
+ <!-- Appearance Controls -->
65
+ <div class="bg-white p-6 rounded-2xl border border-gray-200 shadow-sm">
66
+ <h2 class="text-2xl font-semibold text-gray-900 mb-5">Appearance</h2>
67
+ <div class="flex items-center justify-around space-x-4">
68
+ <!-- Color 1 -->
69
+ <div class="text-center space-y-2">
70
+ <input id="color1" type="color" value="#6b7280">
71
+ <label for="color1" class="block text-sm font-medium text-gray-600">Color 1</label>
72
+ <span id="color1Value" class="font-mono text-xs text-pink-600">#6b7280</span>
73
+ <label class="flex items-center justify-center gap-2 text-gray-500 text-sm pt-1"><input id="color1Transparent" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-pink-500 focus:ring-pink-500"> Transp.</label>
74
+ </div>
75
+ <!-- Swap & Randomize Buttons -->
76
+ <div class="space-y-4">
77
+ <button id="swapBtn" title="Swap Colors" class="p-3 bg-gray-100 hover:bg-pink-100 rounded-full transition-all duration-200 group">
78
+ <svg class="w-6 h-6 text-gray-500 group-hover:text-pink-600 transition-transform group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path></svg>
79
+ </button>
80
+ <button id="randomizeBtn" title="Randomize" class="p-3 bg-gray-100 hover:bg-pink-100 rounded-full transition-all duration-200 group">
81
+ <svg class="w-6 h-6 text-gray-500 group-hover:text-pink-600 transition-transform group-hover:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 16v-2m8-6h-2m-16 0H4m14.485 8.485l-1.414-1.414M5.929 5.929L4.515 4.515m14.97 14.97l-1.414-1.414M5.929 18.071l-1.414 1.414M12 18a6 6 0 100-12 6 6 0 000 12z"></path></svg>
82
+ </button>
83
+ </div>
84
+ <!-- Color 2 -->
85
+ <div class="text-center space-y-2">
86
+ <input id="color2" type="color" value="#d1d5db">
87
+ <label for="color2" class="block text-sm font-medium text-gray-600">Color 2</label>
88
+ <span id="color2Value" class="font-mono text-xs text-pink-600">#d1d5db</span>
89
+ <label class="flex items-center justify-center gap-2 text-gray-500 text-sm pt-1"><input id="color2Transparent" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-pink-500 focus:ring-pink-500"> Transp.</label>
90
+ </div>
91
+ </div>
92
+ </div>
93
+
94
+ <!-- Pattern Controls -->
95
+ <div class="bg-white p-6 rounded-2xl border border-gray-200 shadow-sm space-y-6">
96
+ <h2 class="text-2xl font-semibold text-gray-900 mb-2">Pattern</h2>
97
+ <div>
98
+ <label for="frameThickness" class="flex justify-between items-center font-medium text-gray-600 mb-2">
99
+ <span>Frame Thickness</span>
100
+ <span id="frameThicknessValue" class="font-mono text-pink-600 bg-pink-50 px-2 py-1 text-sm rounded-md">3</span>
101
+ </label>
102
+ <input id="frameThickness" type="range" min="1" max="20" value="3" class="w-full">
103
+ </div>
104
+ <div>
105
+ <label for="checkerSize" class="flex justify-between items-center font-medium text-gray-600 mb-2">
106
+ <span>Checker Size</span>
107
+ <span id="checkerSizeValue" class="font-mono text-pink-600 bg-pink-50 px-2 py-1 text-sm rounded-md">25</span>
108
+ </label>
109
+ <input id="checkerSize" type="range" min="5" max="100" value="25" class="w-full">
110
+ </div>
111
+ </div>
112
+
113
+ <!-- Export Button -->
114
+ <button id="showSaveModalBtn" class="w-full text-lg font-bold py-4 px-4 rounded-xl transition-all duration-300 bg-gradient-to-r from-pink-500 to-orange-400 hover:from-pink-600 hover:to-orange-500 text-white shadow-lg hover:shadow-pink-500/30 transform hover:scale-105">
115
+ Export Images...
116
+ </button>
117
+ </div>
118
+
119
+ <!-- Canvas Column -->
120
+ <div class="lg:col-span-3">
121
+ <div class="aspect-square bg-white rounded-2xl overflow-hidden border-2 border-gray-200 shadow-sm sticky top-8">
122
+ <canvas id="checkerboardCanvas"></canvas>
123
+ </div>
124
+ </div>
125
+ </main>
126
+ </div>
127
+
128
+ <!-- Save Modal -->
129
+ <div id="saveModal" class="fixed inset-0 bg-gray-900/60 backdrop-blur-sm flex items-center justify-center p-4">
130
+ <div id="modalContent" class="bg-white rounded-2xl shadow-2xl p-8 w-full max-w-lg border border-gray-200">
131
+ <!-- View 1: Options -->
132
+ <div id="modalOptionsView">
133
+ <h2 class="text-2xl font-bold text-gray-900 mb-2">Export Options</h2>
134
+ <p class="text-gray-500 mb-6">Select sizes to generate. You can pick multiple.</p>
135
+ <div id="sizeOptions" class="grid grid-cols-2 sm:grid-cols-3 gap-3 mb-6"></div>
136
+ <div class="mb-6 space-y-4">
137
+ <label class="block text-gray-600 font-medium">Custom Size</label>
138
+ <div class="flex items-center gap-3">
139
+ <input type="number" id="customWidth" placeholder="Width" class="w-full bg-gray-100 border border-gray-300 rounded-md px-3 py-2 text-gray-800 placeholder-gray-400 focus:ring-pink-500 focus:border-pink-500">
140
+ <span class="text-gray-400">x</span>
141
+ <input type="number" id="customHeight" placeholder="Height" class="w-full bg-gray-100 border border-gray-300 rounded-md px-3 py-2 text-gray-800 placeholder-gray-400 focus:ring-pink-500 focus:border-pink-500">
142
+ </div>
143
+ </div>
144
+ <div class="flex gap-4">
145
+ <button id="cancelSaveBtn" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-3 px-4 rounded-lg transition-colors">Cancel</button>
146
+ <button id="generateBtn" class="w-full bg-gradient-to-r from-pink-500 to-orange-400 hover:from-pink-600 hover:to-orange-500 text-white font-bold py-3 px-4 rounded-lg transition-all">Generate</button>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- View 2: Results -->
151
+ <div id="modalResultsView" class="hidden">
152
+ <h2 class="text-2xl font-bold text-gray-900 mb-2">Your Images</h2>
153
+ <p class="text-gray-500 mb-6">Long-press or right-click an image to save.</p>
154
+ <div id="generatedImagesContainer" class="space-y-4 max-h-[60vh] overflow-y-auto p-1 -mr-4 pr-4"></div>
155
+ <div class="flex mt-6">
156
+ <button id="backToOptionsBtn" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-3 px-4 rounded-lg transition-colors">Back</button>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+
162
+ <script>
163
+ document.addEventListener('DOMContentLoaded', () => {
164
+ // --- STATE MANAGEMENT ---
165
+ const state = {
166
+ frameThickness: 3,
167
+ checkerSize: 25,
168
+ color1: '#6b7280', // gray-500
169
+ color2: '#d1d5db', // gray-300
170
+ color1Transparent: false,
171
+ color2Transparent: false,
172
+ };
173
+
174
+ // --- DOM ELEMENT SELECTION ---
175
+ const dom = {
176
+ canvas: document.getElementById('checkerboardCanvas'),
177
+ controls: {
178
+ frameThickness: document.getElementById('frameThickness'),
179
+ checkerSize: document.getElementById('checkerSize'),
180
+ color1: document.getElementById('color1'),
181
+ color2: document.getElementById('color2'),
182
+ color1Transparent: document.getElementById('color1Transparent'),
183
+ color2Transparent: document.getElementById('color2Transparent'),
184
+ swapBtn: document.getElementById('swapBtn'),
185
+ randomizeBtn: document.getElementById('randomizeBtn'),
186
+ },
187
+ values: {
188
+ frameThickness: document.getElementById('frameThicknessValue'),
189
+ checkerSize: document.getElementById('checkerSizeValue'),
190
+ color1: document.getElementById('color1Value'),
191
+ color2: document.getElementById('color2Value'),
192
+ },
193
+ modal: {
194
+ element: document.getElementById('saveModal'),
195
+ content: document.getElementById('modalContent'),
196
+ showBtn: document.getElementById('showSaveModalBtn'),
197
+ optionsView: document.getElementById('modalOptionsView'),
198
+ cancelBtn: document.getElementById('cancelSaveBtn'),
199
+ generateBtn: document.getElementById('generateBtn'),
200
+ sizeOptions: document.getElementById('sizeOptions'),
201
+ customWidth: document.getElementById('customWidth'),
202
+ customHeight: document.getElementById('customHeight'),
203
+ resultsView: document.getElementById('modalResultsView'),
204
+ imagesContainer: document.getElementById('generatedImagesContainer'),
205
+ backBtn: document.getElementById('backToOptionsBtn'),
206
+ }
207
+ };
208
+ const ctx = dom.canvas.getContext('2d');
209
+
210
+ // --- CONFIGURATION ---
211
+ const PREDEFINED_SIZES = [
212
+ { w: 256, h: 256 }, { w: 512, h: 512 }, { w: 1024, h: 1024 },
213
+ { w: 1920, h: 1080 }, { w: 1080, h: 1920 }, { w: 1600, h: 900 }
214
+ ];
215
+
216
+ // --- CORE LOGIC ---
217
+ const drawPattern = (targetCtx, width, height, config) => {
218
+ const { frameThickness, checkerSize, color1, color2, color1Transparent, color2Transparent } = config;
219
+ const c1 = color1Transparent ? 'transparent' : color1;
220
+ const c2 = color2Transparent ? 'transparent' : color2;
221
+
222
+ const cols = Math.ceil(width / checkerSize);
223
+ const rows = Math.ceil(height / checkerSize);
224
+ targetCtx.clearRect(0, 0, width, height);
225
+
226
+ for (let i = 0; i < rows; i++) {
227
+ for (let j = 0; j < cols; j++) {
228
+ const isFrame = i < frameThickness || i >= rows - frameThickness || j < frameThickness || j >= cols - frameThickness;
229
+ if (isFrame) {
230
+ targetCtx.fillStyle = (i + j) % 2 === 0 ? c1 : c2;
231
+ if (targetCtx.fillStyle !== 'transparent') {
232
+ targetCtx.fillRect(j * checkerSize, i * checkerSize, checkerSize, checkerSize);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ };
238
+
239
+ const drawLiveCanvas = () => {
240
+ const boardSize = dom.canvas.parentElement.clientWidth;
241
+ dom.canvas.width = boardSize;
242
+ dom.canvas.height = boardSize;
243
+ const liveConfig = { ...state };
244
+ drawPattern(ctx, boardSize, boardSize, liveConfig);
245
+ };
246
+
247
+ const updateUI = () => {
248
+ for (const key in dom.controls) {
249
+ if (state.hasOwnProperty(key)) {
250
+ const el = dom.controls[key];
251
+ const value = state[key];
252
+ if (el.type === 'checkbox') el.checked = value;
253
+ else el.value = value;
254
+ }
255
+ }
256
+ for (const key in dom.values) {
257
+ if (state.hasOwnProperty(key)) {
258
+ dom.values[key].textContent = state[key];
259
+ }
260
+ }
261
+ drawLiveCanvas();
262
+ };
263
+
264
+ // --- EVENT HANDLERS ---
265
+ const handleControlInput = (e) => {
266
+ const { id, value, type, checked } = e.target;
267
+ state[id] = type === 'checkbox' ? checked : value;
268
+ if(type === 'range') state[id] = parseInt(value, 10);
269
+ // BUG FIX: Directly update the UI and redraw on every single input change.
270
+ updateUI();
271
+ };
272
+
273
+ const handleSwap = () => {
274
+ [state.color1, state.color2] = [state.color2, state.color1];
275
+ [state.color1Transparent, state.color2Transparent] = [state.color2Transparent, state.color1Transparent];
276
+ updateUI();
277
+ };
278
+
279
+ const handleRandomize = () => {
280
+ const randomHex = () => '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
281
+ state.color1 = randomHex();
282
+ state.color2 = randomHex();
283
+ state.frameThickness = Math.floor(Math.random() * 19) + 1;
284
+ state.checkerSize = Math.floor(Math.random() * 95) + 5;
285
+ state.color1Transparent = false;
286
+ state.color2Transparent = Math.random() < 0.2;
287
+ updateUI();
288
+ };
289
+
290
+ const handleGenerate = () => {
291
+ const sizesToGenerate = [];
292
+ document.querySelectorAll('#sizeOptions input:checked').forEach(cb => {
293
+ const [w, h] = cb.value.split('x').map(Number);
294
+ sizesToGenerate.push({ w, h });
295
+ });
296
+
297
+ // BUG FIX: Added robust parsing for custom sizes to prevent crashes.
298
+ const customW = parseInt(dom.modal.customWidth.value, 10) || 0;
299
+ const customH = parseInt(dom.modal.customHeight.value, 10) || 0;
300
+ if (customW > 0 && customH > 0) {
301
+ sizesToGenerate.push({ w: customW, h: customH });
302
+ }
303
+
304
+ if (sizesToGenerate.length === 0) {
305
+ // Add a visual cue that nothing was selected
306
+ dom.modal.generateBtn.textContent = "Select a size!";
307
+ dom.modal.generateBtn.classList.add('bg-red-500');
308
+ setTimeout(() => {
309
+ dom.modal.generateBtn.textContent = "Generate";
310
+ dom.modal.generateBtn.classList.remove('bg-red-500');
311
+ }, 2000);
312
+ return;
313
+ }
314
+
315
+ dom.modal.imagesContainer.innerHTML = '';
316
+ sizesToGenerate.forEach(size => {
317
+ const offscreenCanvas = document.createElement('canvas');
318
+ offscreenCanvas.width = size.w;
319
+ offscreenCanvas.height = size.h;
320
+ drawPattern(offscreenCanvas.getContext('2d'), size.w, size.h, state);
321
+
322
+ const imageCard = document.createElement('div');
323
+ imageCard.className = 'bg-gray-100 p-3 rounded-lg border border-gray-200';
324
+ imageCard.innerHTML = `
325
+ <p class="text-gray-700 font-semibold mb-2">${size.w} x ${size.h} px</p>
326
+ <img src="${offscreenCanvas.toDataURL('image/png')}" alt="Generated frame" class="rounded-md border border-gray-300 w-full">`;
327
+ dom.modal.imagesContainer.appendChild(imageCard);
328
+ });
329
+
330
+ dom.modal.customWidth.value = dom.modal.customHeight.value = '';
331
+ document.querySelectorAll('#sizeOptions input:checked').forEach(cb => cb.checked = false);
332
+ dom.modal.optionsView.classList.add('hidden');
333
+ dom.modal.resultsView.classList.remove('hidden');
334
+ };
335
+
336
+ // --- MODAL ---
337
+ const openModal = () => {
338
+ dom.modal.element.style.display = 'flex';
339
+ };
340
+ const closeModal = () => {
341
+ dom.modal.element.style.display = 'none';
342
+ dom.modal.resultsView.classList.add('hidden');
343
+ dom.modal.optionsView.classList.remove('hidden');
344
+ };
345
+
346
+ // --- INITIALIZATION ---
347
+ const initialize = () => {
348
+ PREDEFINED_SIZES.forEach(size => {
349
+ const val = `${size.w}x${size.h}`;
350
+ dom.modal.sizeOptions.innerHTML += `
351
+ <label class="flex items-center gap-2 p-3 bg-gray-100 rounded-lg cursor-pointer hover:bg-gray-200 border border-gray-200 transition-colors">
352
+ <input type="checkbox" value="${val}" class="h-4 w-4 rounded border-gray-300 text-pink-500 focus:ring-pink-500">
353
+ <span class="text-gray-700 text-sm font-medium">${val}</span>
354
+ </label>`;
355
+ });
356
+
357
+ Object.values(dom.controls).forEach(el => el.addEventListener('input', handleControlInput));
358
+ dom.controls.swapBtn.addEventListener('click', handleSwap);
359
+ dom.controls.randomizeBtn.addEventListener('click', handleRandomize);
360
+ dom.modal.showBtn.addEventListener('click', openModal);
361
+ dom.modal.cancelBtn.addEventListener('click', closeModal);
362
+ dom.modal.generateBtn.addEventListener('click', handleGenerate);
363
+ dom.modal.backBtn.addEventListener('click', () => {
364
+ dom.modal.resultsView.classList.add('hidden');
365
+ dom.modal.optionsView.classList.remove('hidden');
366
+ });
367
+
368
+ new ResizeObserver(drawLiveCanvas).observe(dom.canvas.parentElement);
369
+ updateUI();
370
+ };
371
+
372
+ initialize();
373
+ });
374
+ </script>
375
+ </body>
376
+ </html>