238 specs (CH/CZ/DE/NORDICS), dark/light theme, cascading filters, side-by-side comparison, checklist export, admin panel, bulk import. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
954 lines
47 KiB
HTML
954 lines
47 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>L'Oréal Spec Tool</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
|
|
<style>
|
|
/* ── CSS Variables — Dark (default) ─── */
|
|
:root {
|
|
--bg: #111111;
|
|
--bg-card: #1c1c1c;
|
|
--bg-modal: #1a1a1a;
|
|
--bg-input: #1c1c1c;
|
|
--bg-inset: #111111;
|
|
--border: #262626;
|
|
--border-sub: #1e1e1e;
|
|
--text: #f3f4f6;
|
|
--text-sub: #9ca3af;
|
|
--text-muted: #6b7280;
|
|
--text-faint: #4b5563;
|
|
--scrolltrack: #1a1a1a;
|
|
--scrollthumb: #3a3a3a;
|
|
--header-bg: #111111;
|
|
--header-bdr: #1e1e1e;
|
|
--shadow: none;
|
|
}
|
|
|
|
/* ── CSS Variables — Light ─── */
|
|
body.light {
|
|
--bg: #f4f4f5;
|
|
--bg-card: #ffffff;
|
|
--bg-modal: #ffffff;
|
|
--bg-input: #f9fafb;
|
|
--bg-inset: #f4f4f5;
|
|
--border: #e4e4e7;
|
|
--border-sub: #f0f0f0;
|
|
--text: #111111;
|
|
--text-sub: #52525b;
|
|
--text-muted: #71717a;
|
|
--text-faint: #a1a1aa;
|
|
--scrolltrack: #e4e4e7;
|
|
--scrollthumb: #d4d4d8;
|
|
--header-bg: #ffffff;
|
|
--header-bdr: #e4e4e7;
|
|
--shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
transition: background 0.2s, color 0.2s;
|
|
}
|
|
|
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
::-webkit-scrollbar-track { background: var(--scrolltrack); }
|
|
::-webkit-scrollbar-thumb { background: var(--scrollthumb); border-radius: 3px; }
|
|
|
|
/* ── Inputs ─── */
|
|
.filter-select, .filter-input, .form-input {
|
|
background: var(--bg-input);
|
|
border: 1px solid var(--border);
|
|
color: var(--text);
|
|
border-radius: 6px;
|
|
padding: 8px 12px;
|
|
font-size: 14px;
|
|
outline: none;
|
|
transition: border-color 0.2s, background 0.2s;
|
|
width: 100%;
|
|
}
|
|
.filter-select:focus, .filter-input:focus, .form-input:focus { border-color: #f59e0b; }
|
|
.filter-select option { background: var(--bg-input); color: var(--text); }
|
|
textarea.form-input { resize: vertical; }
|
|
|
|
/* ── Cards ─── */
|
|
.spec-card {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 18px;
|
|
cursor: pointer;
|
|
transition: border-color 0.2s, transform 0.15s, box-shadow 0.2s;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.spec-card:hover { border-color: #f59e0b; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(245,158,11,0.1); }
|
|
|
|
/* ── Badges ─── */
|
|
.badge {
|
|
display: inline-block;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
}
|
|
.badge-country { background: #292524; color: #f59e0b; border: 1px solid #44403c; }
|
|
body.light .badge-country { background: #fef3c7; color: #d97706; border-color: #fde68a; }
|
|
.badge-banners { background: #1e1b4b; color: #818cf8; }
|
|
body.light .badge-banners { background: #eef2ff; color: #4f46e5; }
|
|
.badge-brand { background: #14532d; color: #86efac; }
|
|
body.light .badge-brand { background: #f0fdf4; color: #16a34a; }
|
|
.badge-pdp { background: #1c1917; color: #a8a29e; }
|
|
body.light .badge-pdp { background: #f5f5f4; color: #78716c; }
|
|
.badge-crm { background: #451a03; color: #fdba74; }
|
|
body.light .badge-crm { background: #fff7ed; color: #ea580c; }
|
|
.badge-retail { background: #0c4a6e; color: #7dd3fc; }
|
|
body.light .badge-retail { background: #f0f9ff; color: #0284c7; }
|
|
|
|
.file-type-pill {
|
|
display: inline-block;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
padding: 1px 7px;
|
|
border-radius: 4px;
|
|
background: var(--bg-inset);
|
|
color: var(--text-sub);
|
|
border: 1px solid var(--border);
|
|
margin-right: 3px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* ── Tabs ─── */
|
|
.tab-btn {
|
|
padding: 10px 20px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
border-bottom: 2px solid transparent;
|
|
cursor: pointer;
|
|
transition: color 0.2s, border-color 0.2s;
|
|
white-space: nowrap;
|
|
background: none;
|
|
border-top: none;
|
|
border-left: none;
|
|
border-right: none;
|
|
}
|
|
.tab-btn:hover { color: var(--text); }
|
|
.tab-btn.active { color: #f59e0b; border-bottom-color: #f59e0b; }
|
|
|
|
/* ── Section containers ─── */
|
|
.panel {
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
/* ── Section header ─── */
|
|
.section-header {
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: #f59e0b;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
/* ── Modals ─── */
|
|
.modal-overlay {
|
|
position: fixed; inset: 0; z-index: 50;
|
|
background: rgba(0,0,0,0.6);
|
|
display: flex; align-items: center; justify-content: center;
|
|
padding: 16px;
|
|
overflow-y: auto;
|
|
}
|
|
.modal-overlay.hidden { display: none; }
|
|
.modal-box {
|
|
background: var(--bg-modal);
|
|
border: 1px solid var(--border);
|
|
border-radius: 12px;
|
|
width: 100%; max-width: 780px;
|
|
max-height: 90vh; overflow-y: auto;
|
|
padding: 28px; position: relative;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
|
|
}
|
|
|
|
.spec-detail-row {
|
|
display: grid;
|
|
grid-template-columns: 160px 1fr;
|
|
gap: 8px 16px;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid var(--border-sub);
|
|
}
|
|
.spec-detail-row:last-child { border-bottom: none; }
|
|
.spec-detail-label { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-muted); padding-top: 2px; }
|
|
.spec-detail-value { font-size: 14px; color: var(--text); line-height: 1.6; }
|
|
.spec-detail-value.empty { color: var(--text-faint); font-style: italic; }
|
|
|
|
/* ── Buttons ─── */
|
|
.btn-primary {
|
|
background: #f59e0b; color: #111; font-weight: 700;
|
|
padding: 10px 20px; border-radius: 6px; font-size: 14px;
|
|
border: none; cursor: pointer; transition: background 0.2s;
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
}
|
|
.btn-primary:hover { background: #d97706; }
|
|
.btn-ghost {
|
|
background: transparent; color: var(--text-sub); font-weight: 500;
|
|
padding: 8px 16px; border-radius: 6px; font-size: 14px;
|
|
border: 1px solid var(--border); cursor: pointer; transition: all 0.2s;
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
}
|
|
.btn-ghost:hover { border-color: var(--text-muted); color: var(--text); }
|
|
.btn-danger {
|
|
background: transparent; color: #f87171; font-weight: 500;
|
|
padding: 6px 12px; border-radius: 6px; font-size: 13px;
|
|
border: 1px solid #3f1212; cursor: pointer; transition: all 0.2s;
|
|
}
|
|
.btn-danger:hover { background: #3f1212; }
|
|
body.light .btn-danger { border-color: #fecaca; }
|
|
body.light .btn-danger:hover { background: #fef2f2; }
|
|
.btn-edit {
|
|
background: transparent; color: var(--text-sub); font-weight: 500;
|
|
padding: 6px 12px; border-radius: 6px; font-size: 13px;
|
|
border: 1px solid var(--border); cursor: pointer; transition: all 0.2s;
|
|
}
|
|
.btn-edit:hover { border-color: var(--text-muted); color: var(--text); }
|
|
|
|
/* ── Recently viewed ─── */
|
|
.recent-strip {
|
|
display: flex; gap: 10px; overflow-x: auto;
|
|
padding-bottom: 4px; scrollbar-width: thin;
|
|
}
|
|
.recent-strip::-webkit-scrollbar { height: 4px; }
|
|
.recent-chip {
|
|
flex-shrink: 0;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 9px 14px;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s;
|
|
max-width: 220px;
|
|
}
|
|
.recent-chip:hover { border-color: #f59e0b; }
|
|
.recent-chip-title { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.recent-chip-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
|
/* ── Copy link button ─── */
|
|
.btn-copy-link {
|
|
display: inline-flex; align-items: center; gap: 5px;
|
|
font-size: 13px; font-weight: 500;
|
|
color: var(--text-muted);
|
|
padding: 7px 14px;
|
|
border-radius: 6px;
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.btn-copy-link:hover { border-color: #f59e0b; color: #f59e0b; }
|
|
|
|
/* ── Naming preview ─── */
|
|
.naming-preview {
|
|
margin-top: 6px;
|
|
background: var(--bg-inset);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 8px 12px;
|
|
font-size: 12px;
|
|
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
color: #f59e0b;
|
|
word-break: break-all;
|
|
}
|
|
.naming-preview-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-faint); margin-bottom: 4px; font-family: inherit; }
|
|
|
|
/* ── Comparison ─── */
|
|
.compare-bar {
|
|
position: fixed; bottom: 0; left: 0; right: 0; z-index: 45;
|
|
background: #1a1a1a;
|
|
border-top: 1px solid #f59e0b;
|
|
padding: 12px 24px;
|
|
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
transform: translateY(100%);
|
|
transition: transform 0.25s ease;
|
|
box-shadow: 0 -4px 24px rgba(0,0,0,0.4);
|
|
}
|
|
body.light .compare-bar { background: #fff; border-top-color: #f59e0b; box-shadow: 0 -4px 24px rgba(0,0,0,0.1); }
|
|
.compare-bar.visible { transform: translateY(0); }
|
|
|
|
.compare-chips { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
.compare-chip {
|
|
background: #2a2a2a; border: 1px solid #3a3a3a;
|
|
border-radius: 6px; padding: 5px 10px;
|
|
font-size: 12px; color: #f3f4f6;
|
|
display: flex; align-items: center; gap: 6px;
|
|
max-width: 200px;
|
|
}
|
|
body.light .compare-chip { background: #f4f4f5; border-color: #e4e4e7; color: #111; }
|
|
.compare-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
.compare-chip-remove { cursor: pointer; color: #6b7280; flex-shrink: 0; line-height: 1; }
|
|
.compare-chip-remove:hover { color: #f87171; }
|
|
|
|
/* Card checkbox */
|
|
.card-checkbox {
|
|
width: 17px; height: 17px;
|
|
border: 2px solid var(--border);
|
|
border-radius: 4px;
|
|
background: var(--bg-inset);
|
|
cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
transition: all 0.15s;
|
|
}
|
|
.card-checkbox.checked { background: #f59e0b; border-color: #f59e0b; }
|
|
.spec-card.selected { border-color: #f59e0b; }
|
|
|
|
/* Compare table */
|
|
.compare-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
.compare-table th { padding: 12px 14px; text-align: left; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #f59e0b; border-bottom: 2px solid #f59e0b; background: var(--bg-inset); }
|
|
.compare-table td { padding: 10px 14px; border-bottom: 1px solid var(--border-sub); vertical-align: top; line-height: 1.5; color: var(--text-sub); }
|
|
.compare-table tr:hover td { background: var(--bg-inset); }
|
|
.compare-table td.row-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); width: 140px; }
|
|
.compare-table td.diff { color: var(--text); font-weight: 600; background: rgba(245,158,11,0.06); }
|
|
.compare-table td.empty-val { color: var(--text-faint); font-style: italic; }
|
|
|
|
/* ── Checklist overlay ─── */
|
|
#checklistOverlay {
|
|
position: fixed; inset: 0; z-index: 60;
|
|
background: var(--bg);
|
|
overflow-y: auto;
|
|
display: none;
|
|
}
|
|
#checklistOverlay.open { display: block; }
|
|
.checklist-toolbar {
|
|
position: sticky; top: 0; z-index: 10;
|
|
background: var(--header-bg);
|
|
border-bottom: 1px solid var(--header-bdr);
|
|
padding: 12px 24px;
|
|
display: flex; align-items: center; gap: 12px;
|
|
}
|
|
@media print {
|
|
.checklist-toolbar { display: none !important; }
|
|
#checklistOverlay { position: static; overflow: visible; }
|
|
body > *:not(#checklistOverlay) { display: none !important; }
|
|
#checklistOverlay { display: block !important; }
|
|
}
|
|
|
|
/* ── Theme toggle ─── */
|
|
.theme-toggle {
|
|
width: 36px; height: 36px;
|
|
border-radius: 8px;
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
transition: all 0.2s;
|
|
}
|
|
.theme-toggle:hover { border-color: #f59e0b; color: #f59e0b; }
|
|
|
|
/* ── Header ─── */
|
|
.app-header {
|
|
background: var(--header-bg);
|
|
border-bottom: 1px solid var(--header-bdr);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.app-tabbar {
|
|
background: var(--header-bg);
|
|
border-bottom: 1px solid var(--header-bdr);
|
|
}
|
|
|
|
/* ── Admin table ─── */
|
|
.admin-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
.admin-table th { text-align: left; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
|
.admin-table td { padding: 10px 12px; border-bottom: 1px solid var(--border-sub); vertical-align: middle; color: var(--text-sub); }
|
|
.admin-table tr:hover td { background: var(--bg-inset); }
|
|
|
|
/* ── Toast ─── */
|
|
#toast {
|
|
position: fixed; bottom: 24px; right: 24px; z-index: 100;
|
|
background: var(--bg-card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px; padding: 12px 18px;
|
|
font-size: 14px; color: var(--text);
|
|
transform: translateY(80px); opacity: 0;
|
|
transition: all 0.3s ease;
|
|
pointer-events: none;
|
|
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
|
|
}
|
|
#toast.show { transform: translateY(0); opacity: 1; }
|
|
#toast.success { border-left: 3px solid #34d399; }
|
|
#toast.error { border-left: 3px solid #f87171; }
|
|
|
|
/* ── Grid ─── */
|
|
.cards-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: 14px;
|
|
}
|
|
|
|
/* ── Quick stat card ─── */
|
|
.quick-stat {
|
|
background: var(--bg-inset);
|
|
border: 1px solid var(--border);
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
}
|
|
|
|
/* ── Form label ─── */
|
|
.form-label { font-size: 12px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; display: block; }
|
|
|
|
/* ── Empty state ─── */
|
|
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-faint); }
|
|
|
|
/* ── Results count ─── */
|
|
.results-count { font-size: 13px; color: var(--text-muted); }
|
|
|
|
/* ── Review status badges ─── */
|
|
.badge-overdue { background: #3f1212; color: #f87171; border: 1px solid #7f1d1d; }
|
|
.badge-due-soon { background: #3f2800; color: #fbbf24; border: 1px solid #78350f; }
|
|
.badge-verified { background: #052e16; color: #4ade80; border: 1px solid #14532d; }
|
|
body.light .badge-overdue { background: #fef2f2; color: #dc2626; border-color: #fecaca; }
|
|
body.light .badge-due-soon { background: #fffbeb; color: #d97706; border-color: #fde68a; }
|
|
body.light .badge-verified { background: #f0fdf4; color: #16a34a; border-color: #bbf7d0; }
|
|
|
|
/* ── Card download button ─── */
|
|
.card-download-btn {
|
|
display: inline-flex; align-items: center; gap: 4px;
|
|
font-size: 11px; font-weight: 600;
|
|
color: var(--text-muted);
|
|
padding: 4px 8px;
|
|
border-radius: 5px;
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
.card-download-btn:hover { border-color: #f59e0b; color: #f59e0b; }
|
|
|
|
.card-compare-btn {
|
|
display: inline-flex; align-items: center; gap: 5px;
|
|
font-size: 11px; font-weight: 600;
|
|
color: var(--text-muted);
|
|
padding: 4px 8px;
|
|
border-radius: 5px;
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
.card-compare-btn:hover { border-color: #f59e0b; color: #f59e0b; }
|
|
.card-compare-btn.active { border-color: #f59e0b; color: #f59e0b; background: rgba(245,158,11,0.08); }
|
|
body.light .card-compare-btn.active { background: rgba(245,158,11,0.12); }
|
|
|
|
/* ── Bulk import ─── */
|
|
.drop-zone {
|
|
border: 2px dashed var(--border);
|
|
border-radius: 10px;
|
|
padding: 36px 24px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: border-color 0.2s, background 0.2s;
|
|
}
|
|
.drop-zone:hover, .drop-zone.drag-over { border-color: #f59e0b; background: rgba(245,158,11,0.04); }
|
|
.drop-zone input[type=file] { display: none; }
|
|
|
|
.import-preview { margin-top: 16px; }
|
|
.import-stat {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 10px 14px;
|
|
border-radius: 7px;
|
|
margin-bottom: 8px;
|
|
font-size: 13px;
|
|
}
|
|
.import-stat.new { background: rgba(74,222,128,0.06); border: 1px solid #14532d; }
|
|
.import-stat.updated { background: rgba(251,191,36,0.06); border: 1px solid #78350f; }
|
|
.import-stat.unchanged{ background: var(--bg-inset); border: 1px solid var(--border); }
|
|
.import-stat-count { font-size: 20px; font-weight: 700; min-width: 36px; }
|
|
.import-stat.new .import-stat-count { color: #4ade80; }
|
|
.import-stat.updated .import-stat-count { color: #fbbf24; }
|
|
.import-stat.unchanged .import-stat-count { color: var(--text-muted); }
|
|
|
|
.btn-disabled {
|
|
background: transparent; color: var(--text-faint); font-weight: 500;
|
|
padding: 10px 20px; border-radius: 6px; font-size: 14px;
|
|
border: 1px dashed var(--border); cursor: not-allowed;
|
|
display: inline-flex; align-items: center; gap: 6px; opacity: 0.6;
|
|
}
|
|
|
|
/* ── Verify button ─── */
|
|
.btn-verify {
|
|
background: transparent; color: #4ade80; font-weight: 500;
|
|
padding: 6px 12px; border-radius: 6px; font-size: 13px;
|
|
border: 1px solid #14532d; cursor: pointer; transition: all 0.2s;
|
|
}
|
|
.btn-verify:hover { background: #052e16; }
|
|
body.light .btn-verify { border-color: #bbf7d0; color: #16a34a; }
|
|
body.light .btn-verify:hover { background: #f0fdf4; }
|
|
|
|
/* ── Print / PDF ─── */
|
|
#printArea { display: none; }
|
|
@media print {
|
|
body > *:not(#printArea) { display: none !important; }
|
|
#printArea { display: block !important; }
|
|
}
|
|
|
|
/* ── Animations ─── */
|
|
@keyframes fadeUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
.fade-up { animation: fadeUp 0.2s ease both; }
|
|
|
|
/* ── Close btn ─── */
|
|
.close-btn {
|
|
color: var(--text-muted);
|
|
width: 32px; height: 32px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: background 0.2s, color 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
.close-btn:hover { background: var(--bg-inset); color: var(--text); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
HEADER
|
|
═══════════════════════════════════════════════════ -->
|
|
<header class="app-header sticky top-0 z-40 px-6 py-4">
|
|
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
|
<div>
|
|
<h1 style="font-size:17px;font-weight:700;letter-spacing:-0.01em;">L'Oréal Spec Tool</h1>
|
|
<p style="font-size:12px;color:var(--text-muted);margin-top:1px;">Retailer asset specifications library</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<!-- Theme toggle -->
|
|
<button class="theme-toggle" id="themeToggle" onclick="toggleTheme()" title="Toggle light/dark mode">
|
|
<svg id="iconDark" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
|
|
</svg>
|
|
<svg id="iconLight" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="display:none;">
|
|
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
</svg>
|
|
</button>
|
|
<button onclick="showTab('admin')"
|
|
style="font-size:13px;font-weight:500;color:var(--text-muted);border:1px solid var(--border);border-radius:6px;padding:7px 16px;background:transparent;cursor:pointer;transition:all 0.2s;"
|
|
onmouseover="this.style.borderColor='#f59e0b';this.style.color='#f59e0b'"
|
|
onmouseout="this.style.borderColor='';this.style.color=''">
|
|
Admin
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
TAB BAR
|
|
═══════════════════════════════════════════════════ -->
|
|
<div class="app-tabbar px-6 sticky top-[65px] z-30">
|
|
<div class="max-w-7xl mx-auto flex">
|
|
<button class="tab-btn active" id="tab-browse" onclick="showTab('browse')">Browse Specs</button>
|
|
<button class="tab-btn" id="tab-admin" onclick="showTab('admin')">Admin</button>
|
|
<button class="tab-btn" id="tab-help" onclick="showTab('help')">Help</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
BROWSE TAB
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="tab-browse-content" class="max-w-7xl mx-auto px-6 py-6">
|
|
|
|
<!-- Recently viewed -->
|
|
<div id="recentSection" class="hidden mb-5">
|
|
<p style="font-size:11px;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-muted);margin-bottom:8px;">Recently Viewed</p>
|
|
<div id="recentStrip" class="recent-strip"></div>
|
|
</div>
|
|
|
|
<div class="panel mb-6">
|
|
<p class="section-header">Filter Specs</p>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
|
<div>
|
|
<label class="form-label">Country</label>
|
|
<select id="filterCountry" class="filter-select" onchange="onFilterChange()">
|
|
<option value="">All Countries</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Retailer</label>
|
|
<select id="filterRetailer" class="filter-select" onchange="onFilterChange()">
|
|
<option value="">All Retailers</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Content Type</label>
|
|
<select id="filterContent" class="filter-select" onchange="onFilterChange()">
|
|
<option value="">All Types</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label">Search</label>
|
|
<input id="filterSearch" type="text" class="filter-input" placeholder="Format, dimensions, guidelines..." oninput="onFilterChange()">
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center justify-between flex-wrap gap-2">
|
|
<span id="resultsCount" class="results-count"></span>
|
|
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
|
<button id="checklistBtn" onclick="openChecklist()" class="btn-ghost hidden" style="padding:6px 14px;font-size:13px;">
|
|
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
|
|
Export Checklist
|
|
</button>
|
|
<button onclick="clearFilters()" class="btn-ghost" style="padding:6px 14px;font-size:13px;">Clear filters</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Prompt shown before any filter is selected -->
|
|
<div id="filterPrompt" style="text-align:center;padding:64px 20px;">
|
|
<svg width="48" height="48" fill="none" stroke="var(--text-faint)" stroke-width="1.2" viewBox="0 0 24 24" style="margin:0 auto 14px;">
|
|
<path d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2a1 1 0 01-.293.707L13 13.414V19a1 1 0 01-.553.894l-4 2A1 1 0 017 21v-7.586L3.293 6.707A1 1 0 013 6V4z"/>
|
|
</svg>
|
|
<p style="font-size:16px;font-weight:600;color:var(--text-muted);margin-bottom:6px;">Select a filter to get started</p>
|
|
<p style="font-size:13px;color:var(--text-faint);">Choose a country, retailer, or content type above — or type in the search box</p>
|
|
</div>
|
|
|
|
<div id="cardsGrid" class="cards-grid"></div>
|
|
|
|
<div id="emptyState" class="empty-state hidden">
|
|
<svg width="40" height="40" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" style="margin:0 auto 12px;">
|
|
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
|
</svg>
|
|
<p style="font-weight:600;">No specs match your filters</p>
|
|
<p style="font-size:13px;margin-top:4px;">Try adjusting the filters or clearing them</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
ADMIN TAB
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="tab-admin-content" class="max-w-7xl mx-auto px-6 py-6 hidden">
|
|
|
|
<div id="adminPasswordOverlay" class="panel" style="text-align:center;padding:48px 24px;">
|
|
<p class="section-header" style="display:flex;justify-content:center;">Admin Access</p>
|
|
<p style="font-size:14px;color:var(--text-muted);margin-bottom:24px;">Enter the admin password to manage specs</p>
|
|
<div style="max-width:320px;margin:0 auto;">
|
|
<input id="adminPasswordInput" type="password" class="form-input" placeholder="Password"
|
|
style="margin-bottom:12px;"
|
|
onkeydown="if(event.key==='Enter')checkAdminPassword()">
|
|
<button class="btn-primary" style="width:100%;justify-content:center;" onclick="checkAdminPassword()">Sign In</button>
|
|
<p id="adminPasswordError" class="hidden" style="color:#f87171;font-size:13px;margin-top:12px;">Incorrect password</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="adminPanel" class="hidden">
|
|
<div class="panel mb-6">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<p class="section-header" style="margin-bottom:0;">Add New Spec</p>
|
|
<button onclick="toggleAddForm()" id="toggleAddBtn" class="btn-ghost" style="padding:6px 14px;font-size:13px;">Show form</button>
|
|
</div>
|
|
<div id="addForm" class="hidden">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
|
|
<div><label class="form-label">Country *</label><input id="newCountry" class="form-input" placeholder="e.g. DE"></div>
|
|
<div><label class="form-label">Retailer *</label><input id="newRetailer" class="form-input" placeholder="e.g. Apodiscounter"></div>
|
|
<div><label class="form-label">Content Grouping *</label><input id="newContentGrouping" class="form-input" placeholder="e.g. BANNERS / PROMO BANNERS"></div>
|
|
<div><label class="form-label">Format Name *</label><input id="newFormat" class="form-input" placeholder="e.g. Homepage Banner"></div>
|
|
<div><label class="form-label">Dimensions</label><input id="newDimensions" class="form-input" placeholder="e.g. 1200x628px"></div>
|
|
<div><label class="form-label">Max File Weight</label><input id="newMaxWeight" class="form-input" placeholder="e.g. max 300KB"></div>
|
|
<div><label class="form-label">File Types</label><input id="newFileTypes" class="form-input" placeholder="e.g. JPG, PNG, WEBP"></div>
|
|
<div><label class="form-label">Max Number of Assets</label><input id="newMaxAssets" class="form-input" placeholder="e.g. 1-3 product packshots"></div>
|
|
<div><label class="form-label">Naming Convention</label><input id="newNamingConvention" class="form-input" placeholder="e.g. Brand-Product-Type"></div>
|
|
</div>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
|
<div><label class="form-label">Asset Guidelines</label><textarea id="newGuidelines" class="form-input" rows="3" placeholder="Resolution, colour mode, safe zones..."></textarea></div>
|
|
<div><label class="form-label">Delivery Detail</label><textarea id="newDeliveryDetail" class="form-input" rows="3" placeholder="Email address, upload method, deadline..."></textarea></div>
|
|
</div>
|
|
<div class="mb-4"><label class="form-label">Notes</label><input id="newNotes" class="form-input" placeholder="Any additional notes"></div>
|
|
<button onclick="addSpec()" class="btn-primary">Add Spec</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Import -->
|
|
<div class="panel mb-6">
|
|
<div style="margin-bottom:16px;">
|
|
<p class="section-header" style="margin-bottom:4px;">Bulk Import</p>
|
|
<p style="font-size:13px;color:var(--text-muted);">Upload an Excel or CSV file to add or update specs in bulk.</p>
|
|
</div>
|
|
<div id="importPanel">
|
|
|
|
<!-- Format buttons -->
|
|
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px;">
|
|
<button onclick="document.getElementById('importFileInput').click()" class="btn-primary" style="font-size:13px;padding:8px 16px;">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
|
|
Upload Excel or CSV
|
|
</button>
|
|
<div style="position:relative;">
|
|
<button class="btn-disabled" title="Requires Anthropic API key — coming soon">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
Extract from PDF
|
|
</button>
|
|
<span style="position:absolute;top:-8px;right:-8px;background:#2a2a2a;border:1px solid #3a3a3a;color:var(--text-muted);font-size:10px;padding:1px 6px;border-radius:10px;white-space:nowrap;">API key needed</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Drop zone -->
|
|
<div class="drop-zone" id="dropZone" onclick="document.getElementById('importFileInput').click()">
|
|
<input type="file" id="importFileInput" accept=".xlsx,.xls,.csv" onchange="handleImportFile(this.files[0])">
|
|
<svg width="32" height="32" fill="none" stroke="var(--text-faint)" stroke-width="1.5" viewBox="0 0 24 24" style="margin:0 auto 10px;">
|
|
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/>
|
|
</svg>
|
|
<p style="font-size:14px;font-weight:500;color:var(--text-muted);">Drop an Excel or CSV file here</p>
|
|
<p style="font-size:12px;color:var(--text-faint);margin-top:4px;">.xlsx, .xls, .csv — max 20MB</p>
|
|
</div>
|
|
|
|
<!-- Import preview (hidden until file parsed) -->
|
|
<div id="importPreview" class="import-preview hidden">
|
|
<div style="display:flex;flex-direction:column;gap:0;margin-bottom:16px;">
|
|
<div class="import-stat new">
|
|
<span class="import-stat-count" id="importCountNew">0</span>
|
|
<div>
|
|
<p style="font-weight:600;color:#4ade80;">New specs</p>
|
|
<p style="font-size:12px;color:var(--text-muted);">Will be added</p>
|
|
</div>
|
|
</div>
|
|
<div class="import-stat updated">
|
|
<span class="import-stat-count" id="importCountUpdated">0</span>
|
|
<div>
|
|
<p style="font-weight:600;color:#fbbf24;">Updated specs</p>
|
|
<p style="font-size:12px;color:var(--text-muted);">Matched by retailer + format name</p>
|
|
</div>
|
|
</div>
|
|
<div class="import-stat unchanged">
|
|
<span class="import-stat-count" id="importCountUnchanged">0</span>
|
|
<div>
|
|
<p style="font-weight:600;color:var(--text-sub);">Unchanged</p>
|
|
<p style="font-size:12px;color:var(--text-muted);">No changes detected</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;gap:10px;">
|
|
<button onclick="confirmImport()" class="btn-primary">Confirm Import</button>
|
|
<button onclick="cancelImport()" class="btn-ghost">Cancel</button>
|
|
</div>
|
|
</div>
|
|
|
|
<p id="importStatus" style="font-size:13px;color:var(--text-muted);margin-top:12px;"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<p class="section-header" style="margin-bottom:0;">All Specs (<span id="adminSpecCount">0</span>)</p>
|
|
<input id="adminSearch" type="text" class="filter-input" style="width:220px;" placeholder="Search specs..." oninput="renderAdminTable()">
|
|
</div>
|
|
<div style="overflow-x:auto;">
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Country</th><th>Retailer</th><th>Content Type</th>
|
|
<th>Format</th><th>Dimensions</th><th>File Types</th>
|
|
<th>Review Status</th>
|
|
<th style="width:150px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="adminTableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
HELP TAB
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="tab-help-content" class="max-w-4xl mx-auto px-6 py-6 hidden">
|
|
<div class="panel">
|
|
<p class="section-header">How to use the Spec Tool</p>
|
|
<div style="display:flex;flex-direction:column;gap:20px;font-size:14px;color:var(--text-sub);line-height:1.7;">
|
|
<div>
|
|
<p style="font-weight:600;color:var(--text);margin-bottom:4px;">Finding a spec</p>
|
|
<p>Use the filters at the top of the Browse page. Start by selecting a Country, then narrow down by Retailer and Content Type. You can also type in the Search box to find specs by format name, dimensions, or guidelines keywords.</p>
|
|
</div>
|
|
<div>
|
|
<p style="font-weight:600;color:var(--text);margin-bottom:4px;">Viewing full details</p>
|
|
<p>Click any spec card to open the full specification. This includes all fields: dimensions, file types, weight limit, asset guidelines, delivery instructions, naming conventions, and more.</p>
|
|
</div>
|
|
<div>
|
|
<p style="font-weight:600;color:var(--text);margin-bottom:4px;">Exporting a spec as PNG/PDF</p>
|
|
<p>Open any spec card and click <strong>Export PDF</strong>. A formatted image will be downloaded ready to share with your team or attach to a brief.</p>
|
|
</div>
|
|
<div>
|
|
<p style="font-weight:600;color:var(--text);margin-bottom:4px;">Copying spec details</p>
|
|
<p>In the spec modal, click <strong>Copy as Text</strong> to copy all spec details to your clipboard as plain text — useful for pasting into briefs or Slack.</p>
|
|
</div>
|
|
<div>
|
|
<p style="font-weight:600;color:var(--text);margin-bottom:4px;">Managing specs (Admin)</p>
|
|
<p>Click the <strong>Admin</strong> tab and enter the admin password. From there you can add new specs, edit existing ones, or delete outdated specs.</p>
|
|
</div>
|
|
<div class="panel" style="padding:16px;">
|
|
<p style="font-weight:600;color:#f59e0b;margin-bottom:8px;font-size:12px;text-transform:uppercase;letter-spacing:0.05em;">Content Groupings</p>
|
|
<div style="display:flex;flex-direction:column;gap:6px;">
|
|
<div><span class="badge badge-banners" style="margin-right:8px;">Banners</span>Homepage banners, promo banners, category banners</div>
|
|
<div><span class="badge badge-brand" style="margin-right:8px;">Brand Store</span>Landing pages and brand store assets</div>
|
|
<div><span class="badge badge-pdp" style="margin-right:8px;">PDP</span>Product Detail Page images and packshots</div>
|
|
<div><span class="badge badge-crm" style="margin-right:8px;">CRM</span>Newsletter and email marketing assets</div>
|
|
<div><span class="badge badge-retail" style="margin-right:8px;">Retail Media</span>Sponsored product and retail media placements</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
SPEC DETAIL MODAL
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="specModal" class="modal-overlay hidden" onclick="closeModal(event)">
|
|
<div class="modal-box" onclick="event.stopPropagation()">
|
|
<div style="display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:20px;">
|
|
<div style="padding-right:32px;">
|
|
<div id="modalBadges" style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px;"></div>
|
|
<h2 id="modalTitle" style="font-size:20px;font-weight:700;color:var(--text);line-height:1.3;"></h2>
|
|
<p id="modalSubtitle" style="font-size:13px;color:var(--text-muted);margin-top:4px;"></p>
|
|
</div>
|
|
<button class="close-btn" onclick="document.getElementById('specModal').classList.add('hidden');clearURL()">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M18 6L6 18M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="modalQuickStats" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px;"></div>
|
|
<div id="modalDetails" style="margin-bottom:24px;"></div>
|
|
|
|
<div style="display:flex;gap:10px;flex-wrap:wrap;padding-top:16px;border-top:1px solid var(--border);">
|
|
<button onclick="exportPDF()" class="btn-primary">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
|
|
</svg>
|
|
Export PDF
|
|
</button>
|
|
<button onclick="copySpecText()" class="btn-ghost">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>
|
|
</svg>
|
|
Copy as Text
|
|
</button>
|
|
<button onclick="copySpecLink()" class="btn-copy-link">
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/>
|
|
</svg>
|
|
Copy Link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
EDIT SPEC MODAL
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="editModal" class="modal-overlay hidden" onclick="closeEditModal(event)">
|
|
<div class="modal-box" onclick="event.stopPropagation()">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
|
|
<h2 style="font-size:18px;font-weight:700;color:var(--text);">Edit Spec</h2>
|
|
<button class="close-btn" onclick="document.getElementById('editModal').classList.add('hidden')">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path d="M18 6L6 18M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<input type="hidden" id="editSpecId">
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-4">
|
|
<div><label class="form-label">Country</label><input id="editCountry" class="form-input"></div>
|
|
<div><label class="form-label">Retailer</label><input id="editRetailer" class="form-input"></div>
|
|
<div><label class="form-label">Content Grouping</label><input id="editContentGrouping" class="form-input"></div>
|
|
<div><label class="form-label">Format Name</label><input id="editFormat" class="form-input"></div>
|
|
<div><label class="form-label">Dimensions</label><input id="editDimensions" class="form-input"></div>
|
|
<div><label class="form-label">Max File Weight</label><input id="editMaxWeight" class="form-input"></div>
|
|
<div><label class="form-label">File Types (comma-separated)</label><input id="editFileTypes" class="form-input"></div>
|
|
<div><label class="form-label">Max Number of Assets</label><input id="editMaxAssets" class="form-input"></div>
|
|
<div><label class="form-label">Naming Convention</label><input id="editNamingConvention" class="form-input"></div>
|
|
</div>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
|
<div><label class="form-label">Asset Guidelines</label><textarea id="editGuidelines" class="form-input" rows="3"></textarea></div>
|
|
<div><label class="form-label">Delivery Detail</label><textarea id="editDeliveryDetail" class="form-input" rows="3"></textarea></div>
|
|
</div>
|
|
<div style="margin-bottom:20px;"><label class="form-label">Notes</label><input id="editNotes" class="form-input"></div>
|
|
<div style="display:flex;gap:12px;padding-top:16px;border-top:1px solid var(--border);">
|
|
<button onclick="saveEditSpec()" class="btn-primary">Save Changes</button>
|
|
<button onclick="document.getElementById('editModal').classList.add('hidden')" class="btn-ghost">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
COMPARE BAR (sticky bottom)
|
|
═══════════════════════════════════════════════════ -->
|
|
<div class="compare-bar" id="compareBar">
|
|
<div style="display:flex;align-items:center;gap:12px;flex:1;min-width:0;">
|
|
<span style="font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:0.06em;color:#f59e0b;white-space:nowrap;">
|
|
Comparing <span id="compareCount">0</span>/3
|
|
</span>
|
|
<div class="compare-chips" id="compareChips"></div>
|
|
</div>
|
|
<div style="display:flex;gap:8px;flex-shrink:0;">
|
|
<button onclick="openComparison()" id="compareBtn" class="btn-primary" style="font-size:13px;padding:7px 16px;">Compare</button>
|
|
<button onclick="clearComparison()" class="btn-ghost" style="font-size:13px;padding:7px 12px;">Clear</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
COMPARE MODAL
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="compareModal" class="modal-overlay hidden" onclick="closeCompareModal(event)">
|
|
<div class="modal-box" style="max-width:960px;" onclick="event.stopPropagation()">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
|
|
<h2 style="font-size:18px;font-weight:700;color:var(--text);">Spec Comparison</h2>
|
|
<button class="close-btn" onclick="document.getElementById('compareModal').classList.add('hidden')">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div style="overflow-x:auto;">
|
|
<table class="compare-table" id="compareTable"></table>
|
|
</div>
|
|
<div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border);display:flex;gap:8px;align-items:center;">
|
|
<span style="font-size:12px;color:var(--text-muted);">
|
|
<span style="display:inline-block;width:10px;height:10px;background:rgba(245,158,11,0.15);border:1px solid #f59e0b;border-radius:2px;margin-right:4px;vertical-align:middle;"></span>
|
|
Highlighted cells differ between specs
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast"></div>
|
|
<div id="printArea"></div>
|
|
|
|
<!-- ═══════════════════════════════════════════════════
|
|
CHECKLIST OVERLAY
|
|
═══════════════════════════════════════════════════ -->
|
|
<div id="checklistOverlay">
|
|
<div class="checklist-toolbar">
|
|
<button onclick="closeChecklist()" class="btn-ghost" style="padding:6px 12px;font-size:13px;">
|
|
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
|
Close
|
|
</button>
|
|
<button onclick="window.print()" class="btn-primary" style="font-size:13px;padding:7px 16px;">
|
|
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
|
Print / Save PDF
|
|
</button>
|
|
<span id="checklistTitle" style="font-size:14px;font-weight:600;color:var(--text-muted);margin-left:4px;"></span>
|
|
</div>
|
|
<div id="checklistContent" style="max-width:900px;margin:0 auto;padding:32px 24px;"></div>
|
|
</div>
|
|
|
|
<script src="script.js"></script>
|
|
</body>
|
|
</html>
|