feat: switch web UI to light theme with larger font sizes

Replace dark blueprint theme with a clean light theme for better
readability. Bump all font sizes (10px labels to 12px, 13px body
text to 14px) and improve text contrast for users with reading
glasses. Icon colors now use CSS variables instead of hardcoded hex.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 09:03:21 -05:00
parent 0d5742124e
commit 7db44640ca
4 changed files with 102 additions and 106 deletions

View File

@@ -1,21 +1,21 @@
:root {
--bg-deep: #0a0e14;
--bg: #0d1117;
--surface: #151b23;
--surface-raised: #1c2128;
--border: #2a313a;
--border-subtle: #21262d;
--text: #e6edf3;
--text-secondary: #7d8590;
--text-dim: #484f58;
--cyan: #00d4ff;
--cyan-dim: rgba(0, 212, 255, 0.15);
--cyan-glow: rgba(0, 212, 255, 0.3);
--amber: #f0883e;
--amber-dim: rgba(240, 136, 62, 0.15);
--green: #3fb950;
--green-dim: rgba(63, 185, 80, 0.12);
--red: #f85149;
--bg-deep: #f0f1f3;
--bg: #f8f9fa;
--surface: #ffffff;
--surface-raised: #ffffff;
--border: #d0d5dd;
--border-subtle: #e4e7ec;
--text: #1a1a1a;
--text-secondary: #475467;
--text-dim: #667085;
--cyan: #0975b0;
--cyan-dim: rgba(9, 117, 176, 0.1);
--cyan-glow: rgba(9, 117, 176, 0.2);
--amber: #b54708;
--amber-dim: rgba(181, 71, 8, 0.08);
--green: #067647;
--green-dim: rgba(6, 118, 71, 0.08);
--red: #d92d20;
--sidebar-w: 64px;
--font-display: 'Outfit', sans-serif;
--font-body: 'IBM Plex Sans', sans-serif;
@@ -33,24 +33,11 @@ body {
overflow-x: hidden;
}
/* Blueprint grid background */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(0, 212, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 212, 255, 0.03) 1px, transparent 1px);
background-size: 48px 48px;
pointer-events: none;
z-index: 0;
}
/* ─── Sidebar ─── */
.sidebar {
width: var(--sidebar-w);
background: var(--bg-deep);
border-right: 1px solid var(--border-subtle);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
align-items: center;
@@ -81,7 +68,6 @@ body::before {
.sidebar-brand svg {
width: 26px; height: 26px;
color: var(--cyan);
filter: drop-shadow(0 0 6px rgba(0, 212, 255, 0.4));
}
.sidebar-nav {
@@ -126,7 +112,6 @@ body::before {
height: 20px;
background: var(--cyan);
border-radius: 0 2px 2px 0;
box-shadow: 0 0 8px var(--cyan-glow);
}
.nav-item svg { width: 20px; height: 20px; }
@@ -136,12 +121,12 @@ body::before {
left: calc(100% + 12px);
top: 50%;
transform: translateY(-50%);
background: var(--surface-raised);
background: var(--text);
border: 1px solid var(--border);
color: var(--text);
color: #fff;
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
font-size: 13px;
font-family: var(--font-body);
white-space: nowrap;
opacity: 0;
@@ -164,9 +149,9 @@ body::before {
}
.topbar {
background: rgba(13, 17, 23, 0.85);
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--border);
padding: 0 32px;
height: 56px;
display: flex;
@@ -185,14 +170,14 @@ body::before {
.topbar h2 {
font-family: var(--font-display);
font-size: 16px;
font-size: 18px;
font-weight: 600;
letter-spacing: -0.01em;
}
.topbar-tag {
font-family: var(--font-mono);
font-size: 10px;
font-size: 12px;
color: var(--cyan);
background: var(--cyan-dim);
padding: 2px 8px;
@@ -212,11 +197,6 @@ body::before {
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulseGlow {
0%, 100% { box-shadow: 0 0 4px var(--cyan-glow); }
50% { box-shadow: 0 0 12px var(--cyan-glow); }
}
.animate-in {
animation: fadeSlideIn 0.3s ease forwards;
opacity: 0;
@@ -233,6 +213,7 @@ body::before {
border: 1px solid var(--border-subtle);
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.card-header {
@@ -240,7 +221,7 @@ body::before {
border-bottom: 1px solid var(--border-subtle);
font-family: var(--font-display);
font-weight: 600;
font-size: 13px;
font-size: 14px;
letter-spacing: 0.02em;
display: flex;
align-items: center;
@@ -266,20 +247,21 @@ body::before {
padding: 18px 20px;
position: relative;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.stat-card::before {
content: '';
position: absolute;
top: 0; left: 0;
width: 100%; height: 2px;
width: 100%; height: 3px;
background: linear-gradient(90deg, var(--cyan), transparent);
opacity: 0.6;
opacity: 0.5;
}
.stat-label {
font-family: var(--font-mono);
font-size: 10px;
font-size: 12px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 1.5px;
@@ -295,7 +277,7 @@ body::before {
}
.stat-value.stat-sm {
font-size: 14px;
font-size: 15px;
font-weight: 500;
font-family: var(--font-mono);
}
@@ -309,22 +291,22 @@ th {
background: var(--bg);
border-bottom: 1px solid var(--border);
font-family: var(--font-mono);
font-size: 10px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1.2px;
letter-spacing: 1px;
color: var(--text-dim);
font-weight: 500;
font-weight: 600;
white-space: nowrap;
}
td {
padding: 11px 16px;
padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
font-size: 13px;
font-size: 14px;
}
tbody tr { transition: background 0.1s; }
tbody tr:hover td { background: rgba(0, 212, 255, 0.03); }
tbody tr:hover td { background: var(--cyan-dim); }
tbody tr:last-child td { border-bottom: none; }
/* ─── Badges ─── */
@@ -332,21 +314,21 @@ tbody tr:last-child td { border-bottom: none; }
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
padding: 3px 10px;
border-radius: 3px;
font-family: var(--font-mono);
font-size: 10px;
font-weight: 500;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.5px;
text-transform: uppercase;
}
.badge svg { width: 12px; height: 12px; flex-shrink: 0; }
.badge svg { width: 14px; height: 14px; flex-shrink: 0; }
.badge-cyan { background: var(--cyan-dim); color: var(--cyan); }
.badge-amber { background: var(--amber-dim); color: var(--amber); }
.badge-green { background: var(--green-dim); color: var(--green); }
.badge-count {
background: var(--surface-raised);
background: var(--bg);
color: var(--text-secondary);
border: 1px solid var(--border);
}
@@ -356,41 +338,53 @@ tbody tr:last-child td { border-bottom: none; }
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
padding: 7px 14px;
border-radius: 4px;
font-family: var(--font-body);
font-size: 12px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
text-decoration: none;
border: 1px solid var(--border);
background: var(--surface-raised);
background: var(--surface);
color: var(--text-secondary);
transition: all 0.15s;
white-space: nowrap;
}
.btn:hover {
background: var(--surface);
background: var(--bg);
color: var(--text);
border-color: var(--text-dim);
}
.btn svg { width: 13px; height: 13px; }
.btn svg { width: 14px; height: 14px; }
.btn-cyan {
background: var(--cyan-dim);
color: var(--cyan);
border-color: rgba(0, 212, 255, 0.2);
border-color: rgba(9, 117, 176, 0.25);
}
.btn-cyan:hover {
background: rgba(0, 212, 255, 0.2);
border-color: rgba(0, 212, 255, 0.4);
background: rgba(9, 117, 176, 0.15);
border-color: rgba(9, 117, 176, 0.4);
color: var(--cyan);
}
.btn-sm { padding: 3px 8px; font-size: 11px; }
.btn-amber {
background: var(--amber-dim);
color: var(--amber);
border-color: rgba(181, 71, 8, 0.25);
}
.btn-amber:hover {
background: rgba(181, 71, 8, 0.15);
border-color: rgba(181, 71, 8, 0.4);
color: var(--amber);
}
.btn-sm { padding: 4px 10px; font-size: 12px; }
/* ─── Search ─── */
.search-box {
@@ -401,18 +395,18 @@ tbody tr:last-child td { border-bottom: none; }
border: 1px solid var(--border);
border-radius: 4px;
padding: 0 12px;
height: 34px;
height: 36px;
width: 300px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.search-box:focus-within {
border-color: var(--cyan);
box-shadow: 0 0 0 1px var(--cyan-dim);
box-shadow: 0 0 0 2px var(--cyan-dim);
}
.search-box svg {
width: 14px; height: 14px;
width: 16px; height: 16px;
color: var(--text-dim);
flex-shrink: 0;
}
@@ -421,7 +415,7 @@ tbody tr:last-child td { border-bottom: none; }
border: none;
outline: none;
font-family: var(--font-body);
font-size: 13px;
font-size: 14px;
width: 100%;
background: transparent;
color: var(--text);
@@ -442,7 +436,7 @@ tbody tr:last-child td { border-bottom: none; }
.detail-field label {
display: block;
font-family: var(--font-mono);
font-size: 10px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1.2px;
color: var(--text-dim);
@@ -450,14 +444,14 @@ tbody tr:last-child td { border-bottom: none; }
}
.detail-field .value {
font-size: 14px;
font-size: 15px;
font-weight: 500;
word-break: break-all;
}
.detail-field .value.mono {
font-family: var(--font-mono);
font-size: 12px;
font-size: 13px;
color: var(--text-secondary);
}
@@ -468,7 +462,7 @@ tbody tr:last-child td { border-bottom: none; }
gap: 6px;
color: var(--text-dim);
text-decoration: none;
font-size: 12px;
font-size: 13px;
cursor: pointer;
margin-bottom: 20px;
font-family: var(--font-mono);
@@ -488,7 +482,7 @@ tbody tr:last-child td { border-bottom: none; }
.bom-expand-content {
padding: 16px 16px 16px 48px;
border-left: 2px solid var(--cyan-dim);
border-left: 3px solid var(--cyan-dim);
margin-left: 16px;
}
@@ -499,14 +493,14 @@ tbody tr:last-child td { border-bottom: none; }
}
.bom-expand-content .info-item {
font-size: 12px;
font-size: 13px;
padding: 2px 0;
}
.bom-expand-content .info-item .lbl {
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 10px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-right: 6px;
@@ -519,7 +513,7 @@ tbody tr:last-child td { border-bottom: none; }
.bom-section-title {
font-family: var(--font-mono);
font-size: 10px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
@@ -546,7 +540,7 @@ tbody tr:last-child td { border-bottom: none; }
align-items: center;
gap: 6px;
font-family: var(--font-mono);
font-size: 12px;
font-size: 13px;
margin-bottom: 16px;
flex-wrap: wrap;
padding: 8px 14px;
@@ -563,7 +557,7 @@ tbody tr:last-child td { border-bottom: none; }
}
.breadcrumb a:hover { opacity: 0.7; }
.breadcrumb .sep { color: var(--text-dim); font-size: 10px; }
.breadcrumb .sep { color: var(--text-dim); font-size: 11px; }
.breadcrumb .current { color: var(--text); font-weight: 500; }
.file-name-cell {
@@ -588,7 +582,7 @@ tbody tr:last-child td { border-bottom: none; }
text-align: center;
padding: 60px 24px;
color: var(--text-dim);
font-size: 13px;
font-size: 14px;
font-family: var(--font-mono);
}
@@ -638,24 +632,25 @@ tbody tr:last-child td { border-bottom: none; }
cursor: pointer;
transition: all 0.2s;
position: relative;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.drawing-card:hover {
border-color: var(--cyan);
background: rgba(0, 212, 255, 0.03);
box-shadow: 0 0 0 1px var(--cyan-dim);
background: var(--cyan-dim);
box-shadow: 0 2px 8px rgba(9, 117, 176, 0.1);
}
.drawing-card-title {
font-family: var(--font-display);
font-size: 15px;
font-size: 16px;
font-weight: 600;
margin-bottom: 2px;
}
.drawing-card-sub {
font-family: var(--font-mono);
font-size: 10px;
font-size: 12px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 1px;
@@ -679,21 +674,22 @@ tbody tr:last-child td { border-bottom: none; }
cursor: pointer;
transition: all 0.15s;
user-select: none;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.equip-header:hover { background: rgba(0, 212, 255, 0.03); }
.equip-header:hover { background: var(--cyan-dim); }
.equip-header .chevron-toggle { flex-shrink: 0; }
.equip-header-title {
font-family: var(--font-display);
font-size: 15px;
font-size: 16px;
font-weight: 600;
}
.equip-header-number {
font-family: var(--font-mono);
font-size: 13px;
font-size: 15px;
color: var(--cyan);
font-weight: 600;
}
@@ -707,7 +703,7 @@ tbody tr:last-child td { border-bottom: none; }
.equip-header-stat {
font-family: var(--font-mono);
font-size: 11px;
font-size: 13px;
color: var(--text-dim);
}

View File

@@ -17,7 +17,7 @@ function renderBomDetails(b) {
if (ct.contentHash) {
html += `<div style="margin-top:10px">
<a class="btn btn-cyan btn-sm" href="/api/files/blob/${encodeURIComponent(ct.contentHash)}?ext=dxf&download=true&name=${encodeURIComponent(displayName)}" onclick="event.stopPropagation()">${icons.download} Download DXF</a>
<span style="font-family:var(--font-mono);font-size:10px;color:var(--text-dim);margin-left:8px">${esc(displayName)}</span>
<span style="font-family:var(--font-mono);font-size:13px;color:var(--text-dim);margin-left:8px">${esc(displayName)}</span>
</div>`;
}
}
@@ -35,7 +35,7 @@ function renderBomDetails(b) {
<div class="info-item"><span class="lbl">Upper Tools</span><span class="val">${esc(fp.upperToolNames) || '\u2014'}</span></div>
<div class="info-item"><span class="lbl">Lower Tools</span><span class="val">${esc(fp.lowerToolNames) || '\u2014'}</span></div>
</div>
${fp.setupNotes ? `<div style="margin-top:8px;padding:8px 12px;background:var(--amber-dim);border-radius:4px;font-size:12px;font-family:var(--font-mono);color:var(--amber)"><span class="lbl">Setup Notes</span>${esc(fp.setupNotes)}</div>` : ''}`;
${fp.setupNotes ? `<div style="margin-top:8px;padding:8px 12px;background:var(--amber-dim);border-radius:4px;font-size:13px;font-family:var(--font-mono);color:var(--amber)"><span class="lbl">Setup Notes</span>${esc(fp.setupNotes)}</div>` : ''}`;
}
html += '</div>';

View File

@@ -1,8 +1,8 @@
const icons = {
search: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
folder: `<svg viewBox="0 0 24 24" fill="rgba(240,136,62,0.2)" stroke="#f0883e" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`,
folder: `<svg viewBox="0 0 24 24" fill="var(--amber-dim)" stroke="var(--amber)" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`,
fileDxf: `<svg viewBox="0 0 24 24" fill="none" stroke="var(--cyan)" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
filePdf: `<svg viewBox="0 0 24 24" fill="none" stroke="#f85149" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
filePdf: `<svg viewBox="0 0 24 24" fill="none" stroke="var(--red)" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
fileGeneric: `<svg viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
download: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
back: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>`,

View File

@@ -69,12 +69,12 @@ const pages = {
return `
<tr class="clickable" onclick="router.go('export-detail', {id: ${e.id}})" style="animation: fadeSlideIn 0.2s ease ${0.02 * i}s forwards; opacity: 0">
<td style="font-family:var(--font-mono);color:var(--text-dim);font-size:11px">${e.id}</td>
<td style="font-family:var(--font-mono);color:var(--text-dim);font-size:13px">${e.id}</td>
<td><strong>${esc(drawingPart) || '<span style="color:var(--text-dim)">\u2014</span>'}</strong></td>
<td style="color:var(--text-secondary);font-size:12px">${esc(e.title) || ''}</td>
<td style="color:var(--text-secondary);font-size:13px">${esc(e.title) || ''}</td>
<td><span class="badge badge-count">${e.bomItemCount}</span></td>
<td style="color:var(--text-secondary)">${esc(e.exportedBy)}</td>
<td style="font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)">${fmtDate(e.exportedAt)}</td>
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary)">${fmtDate(e.exportedAt)}</td>
</tr>`;
}).join('');
@@ -137,7 +137,7 @@ const pages = {
<td style="font-family:var(--font-mono);font-weight:600;color:var(--cyan)">${esc(b.itemNo)}</td>
<td><strong>${esc(b.partName)}</strong></td>
<td style="color:var(--text-secondary)">${esc(b.description)}</td>
<td><span style="font-family:var(--font-mono);font-size:12px">${esc(b.material)}</span></td>
<td><span style="font-family:var(--font-mono);font-size:13px">${esc(b.material)}</span></td>
<td style="font-family:var(--font-mono);text-align:center">${b.qty ?? ''}</td>
<td style="font-family:var(--font-mono);text-align:center">${b.totalQty ?? ''}</td>
<td>
@@ -260,7 +260,7 @@ const pages = {
<td style="font-family:var(--font-mono);font-weight:600;color:var(--cyan)">${esc(b.itemNo)}</td>
<td><strong>${esc(b.partName)}</strong></td>
<td style="color:var(--text-secondary)">${esc(b.description)}</td>
<td><span style="font-family:var(--font-mono);font-size:12px">${esc(b.material)}</span></td>
<td><span style="font-family:var(--font-mono);font-size:13px">${esc(b.material)}</span></td>
<td style="font-family:var(--font-mono);text-align:center">${b.qty ?? ''}</td>
<td style="font-family:var(--font-mono);text-align:center">${b.totalQty ?? ''}</td>
<td>
@@ -317,7 +317,7 @@ const pages = {
${icons.search}
<input type="text" id="file-search" placeholder="Search drawing number, filename..." value="${esc(searchVal)}">
</div>
<select id="file-type-filter" style="background:var(--surface);border:1px solid var(--border);border-radius:4px;padding:6px 10px;color:var(--text);font-family:var(--font-body);font-size:13px;height:34px">
<select id="file-type-filter" style="background:var(--surface);border:1px solid var(--border);border-radius:4px;padding:6px 10px;color:var(--text);font-family:var(--font-body);font-size:14px;height:36px">
<option value="">All types</option>
<option value="dxf">DXF only</option>
<option value="pdf">PDF only</option>
@@ -368,9 +368,9 @@ const pages = {
<td><div class="file-name-cell">${ext === 'pdf' ? icons.filePdf : icons.fileDxf}<a href="/api/filebrowser/download?hash=${encodeURIComponent(f.contentHash)}&ext=${ext}&name=${encodeURIComponent(f.fileName)}">${esc(f.fileName)}</a></div></td>
<td><span class="badge ${ext === 'dxf' ? 'badge-cyan' : 'badge-amber'}">${ext.toUpperCase()}</span></td>
<td style="color:var(--text-secondary)">${esc(f.drawingNumber)}</td>
<td style="font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)">${f.thickness != null ? f.thickness.toFixed(4) + '"' : '\u2014'}</td>
<td style="font-family:var(--font-mono);font-size:12px;color:var(--text-secondary)">${fmtDate(f.createdAt)}</td>
<td style="font-family:var(--font-mono);font-size:10px;color:var(--text-dim)">${esc(hashShort)}</td>
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary)">${f.thickness != null ? f.thickness.toFixed(4) + '"' : '\u2014'}</td>
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary)">${fmtDate(f.createdAt)}</td>
<td style="font-family:var(--font-mono);font-size:12px;color:var(--text-dim)">${esc(hashShort)}</td>
<td style="white-space:nowrap">
<a class="btn btn-cyan btn-sm" href="/api/filebrowser/download?hash=${encodeURIComponent(f.contentHash)}&ext=${ext}&name=${encodeURIComponent(f.fileName)}">${icons.download}</a>
</td>