loreal-spec-tool/index.html
Phil Dore 21fcf63431 Initial commit — L'Oréal Spec Tool
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>
2026-04-27 18:39:10 +01:00

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>