Skip to content

Commit cd98770

Browse files
GCU Emblem Maker Update
1 parent 3c2d4a3 commit cd98770

1 file changed

Lines changed: 168 additions & 12 deletions

File tree

gcuemblem/index.html

Lines changed: 168 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@
6363
margin-bottom: 10px;
6464
}
6565

66+
.upload-zone.dragover {
67+
border-color: var(--accent-color);
68+
background: rgba(79, 70, 229, 0.12);
69+
}
70+
6671
/* The Stage */
6772
.stage {
6873
position: relative;
@@ -109,16 +114,49 @@
109114
padding: 15px; border: 1px solid #ff00ff; border-radius: 8px;
110115
display: none; z-index: 1000; width: 250px;
111116
}
117+
118+
#mobileWarning {
119+
display: none;
120+
width: 100%;
121+
max-width: 1100px;
122+
box-sizing: border-box;
123+
margin: 20px 0;
124+
padding: 16px;
125+
border-radius: 10px;
126+
border: 1px solid #b45309;
127+
background: #2b1d0e;
128+
color: #fde68a;
129+
font-weight: 600;
130+
line-height: 1.5;
131+
}
132+
133+
#dpiWarning {
134+
grid-column: 1 / -1;
135+
display: none;
136+
box-sizing: border-box;
137+
padding: 12px;
138+
border-radius: 8px;
139+
border: 1px solid #b45309;
140+
background: #2b1d0e;
141+
color: #fde68a;
142+
line-height: 1.4;
143+
}
112144
</style>
113145
</head>
114146
<body>
115147

148+
<div id="mobileWarning">
149+
Mobile is not fully supported for this tool. Please use a desktop browser for accurate scaling and print output.
150+
</div>
151+
116152
<div class="controls">
117-
<div class="upload-zone" onclick="document.getElementById('artUpload').click()">
118-
<span style="color:var(--accent-color)">FILE:</span> CLICK TO UPLOAD DESIGN (.PNG)
119-
<input type="file" id="artUpload" accept="image/png" style="display:none">
153+
<div class="upload-zone" id="uploadZone" onclick="document.getElementById('artUpload').click()">
154+
<span style="color:var(--accent-color)">FILE:</span> CLICK OR DRAG TO UPLOAD DESIGN (.PNG, .SVG)
155+
<input type="file" id="artUpload" accept="image/png,image/svg+xml,.svg" style="display:none">
120156
</div>
121157

158+
<div id="dpiWarning"></div>
159+
122160
<div class="control-group" style="grid-column: 1/-1; flex-direction: row; align-items: center;">
123161
<input type="checkbox" id="showGuides" style="width:18px; height:18px;">
124162
<label for="showGuides" style="margin-left:8px; text-transform: none; color:#eee;">Show Center Guides</label>
@@ -162,19 +200,82 @@ <h4 style="color:#ff00ff; margin:0 0 10px 0;">DEV ALIGNMENT</h4>
162200
// --- USER DEFAULTS ---
163201
const ALIGN_DEF = { x: -1, y: 390, s: 154 };
164202

165-
const MM_W = 90, MM_H = 42, DPI_WS = 360, DPI_EXP = 600, IN_MM = 25.4;
203+
const MM_W = 90, MM_H = 42, DPI_WS = 360, DPI_EXP = 600, MIN_SOURCE_DPI = 600, IN_MM = 25.4;
166204
const canvas = document.getElementById('mainCanvas');
167205
const ctx = canvas.getContext('2d');
168206
const bg = document.getElementById('gamepadBg');
207+
const dpiWarning = document.getElementById('dpiWarning');
208+
const uploadZone = document.getElementById('uploadZone');
209+
const artUpload = document.getElementById('artUpload');
210+
const scaleRange = document.getElementById('rScale');
211+
const scaleNum = document.getElementById('nScale');
212+
const MIN_SCALE = parseFloat(scaleRange.min);
213+
const MAX_SCALE_DEFAULT = parseFloat(scaleRange.max);
169214

170215
const wsW = Math.round((MM_W / IN_MM) * DPI_WS);
171216
const wsH = Math.round((MM_H / IN_MM) * DPI_WS);
172217
canvas.width = wsW; canvas.height = wsH;
173218

174219
let thickImg = new Image(), userArt = null;
175220
let state = { x: wsW/2, y: wsH/2, s: 1 };
221+
let maxAllowedScale = MAX_SCALE_DEFAULT;
222+
223+
function isMobileDevice() {
224+
const ua = navigator.userAgent || '';
225+
const hasMobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
226+
const likelyTouchLayout = window.matchMedia('(max-width: 900px)').matches && window.matchMedia('(pointer: coarse)').matches;
227+
return hasMobileUA || likelyTouchLayout;
228+
}
229+
230+
function setMobileUnsupportedUI() {
231+
document.getElementById('mobileWarning').style.display = 'block';
232+
document.querySelector('.controls').style.display = 'none';
233+
document.querySelector('.stage').style.display = 'none';
234+
}
235+
236+
function clampScale(value) {
237+
return Math.max(MIN_SCALE, Math.min(maxAllowedScale, value));
238+
}
239+
240+
function setScale(value) {
241+
const clamped = clampScale(value);
242+
state.s = clamped;
243+
scaleRange.value = clamped.toFixed(2);
244+
scaleNum.value = clamped.toFixed(2);
245+
draw();
246+
}
247+
248+
function setScaleLimit(maxScale) {
249+
maxAllowedScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE_DEFAULT, maxScale));
250+
scaleRange.max = maxAllowedScale.toFixed(2);
251+
setScale(state.s);
252+
}
253+
254+
function showDpiWarning(message) {
255+
dpiWarning.style.display = 'block';
256+
dpiWarning.textContent = message;
257+
}
258+
259+
function hideDpiWarning() {
260+
dpiWarning.style.display = 'none';
261+
dpiWarning.textContent = '';
262+
}
263+
264+
function getResolutionBasedDpi(img) {
265+
// Estimate source DPI from pixel resolution against print size.
266+
const widthInches = MM_W / IN_MM;
267+
const heightInches = MM_H / IN_MM;
268+
const dpiX = img.width / widthInches;
269+
const dpiY = img.height / heightInches;
270+
return Math.min(dpiX, dpiY);
271+
}
176272

177273
window.onload = () => {
274+
if (isMobileDevice()) {
275+
setMobileUnsupportedUI();
276+
return;
277+
}
278+
178279
// Init Dev Sliders
179280
document.getElementById('rBgX').value = ALIGN_DEF.x;
180281
document.getElementById('rBgY').value = ALIGN_DEF.y;
@@ -200,10 +301,10 @@ <h4 style="color:#ff00ff; margin:0 0 10px 0;">DEV ALIGNMENT</h4>
200301
const r = document.getElementById(rangeId);
201302
const n = document.getElementById(numId);
202303
const update = (val) => {
203-
if (prop === 's') state.s = parseFloat(val);
304+
if (prop === 's') setScale(parseFloat(val));
204305
if (prop === 'x') state.x = (wsW/2) + parseInt(val);
205306
if (prop === 'y') state.y = (wsH/2) + parseInt(val);
206-
draw();
307+
if (prop !== 's') draw();
207308
};
208309
r.oninput = () => { n.value = r.value; update(r.value); };
209310
n.oninput = () => { r.value = n.value; update(n.value); };
@@ -220,16 +321,71 @@ <h4 style="color:#ff00ff; margin:0 0 10px 0;">DEV ALIGNMENT</h4>
220321

221322
window.onkeydown = (e) => { if(e.key.toLowerCase()==='d'){ const d=document.getElementById('devConsole'); d.style.display=d.style.display==='block'?'none':'block';}};
222323

223-
document.getElementById('artUpload').onchange = (e) => {
324+
function handleImportedFile(file) {
325+
if (!file) return;
326+
327+
const fileName = file.name || '';
328+
const fileType = file.type || '';
329+
const isSvg = fileType === 'image/svg+xml' || fileName.toLowerCase().endsWith('.svg');
330+
const isPng = fileType === 'image/png' || fileName.toLowerCase().endsWith('.png');
331+
if (!isSvg && !isPng) {
332+
showDpiWarning('Unsupported file type. Please upload PNG or SVG.');
333+
return;
334+
}
335+
224336
const reader = new FileReader();
225-
reader.onload = (ev) => {
337+
new Promise((resolve) => {
338+
reader.onload = (ev) => resolve(ev.target.result);
339+
reader.readAsDataURL(file);
340+
}).then((dataUrl) => {
226341
const img = new Image();
227-
img.onload = () => { userArt = img; draw(); };
228-
img.src = ev.target.result;
229-
};
230-
reader.readAsDataURL(e.target.files[0]);
342+
img.onload = () => {
343+
userArt = img;
344+
345+
if (isSvg) {
346+
setScaleLimit(MAX_SCALE_DEFAULT);
347+
setScale(1);
348+
hideDpiWarning();
349+
} else {
350+
const detectedDpi = getResolutionBasedDpi(img);
351+
const safeScale = detectedDpi / MIN_SOURCE_DPI;
352+
setScaleLimit(safeScale);
353+
if (detectedDpi < MIN_SOURCE_DPI) {
354+
setScale(safeScale);
355+
showDpiWarning(`This image resolution provides about ${detectedDpi.toFixed(0)} DPI for the print area, below the recommended ${MIN_SOURCE_DPI} DPI. It has been auto-scaled to ${safeScale.toFixed(2)}x to maintain ${MIN_SOURCE_DPI} DPI effective quality, and scaling above this limit is disabled.`);
356+
} else {
357+
setScale(1);
358+
hideDpiWarning();
359+
}
360+
}
361+
362+
draw();
363+
};
364+
img.src = dataUrl;
365+
});
366+
}
367+
368+
artUpload.onchange = (e) => {
369+
handleImportedFile(e.target.files[0]);
370+
artUpload.value = '';
231371
};
232372

373+
uploadZone.addEventListener('dragover', (e) => {
374+
e.preventDefault();
375+
uploadZone.classList.add('dragover');
376+
});
377+
378+
uploadZone.addEventListener('dragleave', () => {
379+
uploadZone.classList.remove('dragover');
380+
});
381+
382+
uploadZone.addEventListener('drop', (e) => {
383+
e.preventDefault();
384+
uploadZone.classList.remove('dragover');
385+
const file = e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files[0] : null;
386+
handleImportedFile(file);
387+
});
388+
233389
function draw() {
234390
ctx.clearRect(0, 0, wsW, wsH);
235391
if (userArt) {

0 commit comments

Comments
 (0)