feat: subcategories as 4-column card grid with expandable detail panel
This commit is contained in:
parent
2b0f76aa1f
commit
8624d1887a
2 changed files with 148 additions and 79 deletions
|
|
@ -268,66 +268,125 @@
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Subcategory Sections ───────────────────────────── */
|
/* ─── Subcategory Grid (4-col cards) ─────────────────── */
|
||||||
.crt-subcategory {
|
.crt-sub-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 0.75rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crt-sub-header {
|
.crt-sub-card {
|
||||||
|
background: rgba(20, 20, 20, 0.9);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-left: 3px solid var(--status-amber);
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-sub-card:hover {
|
||||||
|
background: rgba(30, 30, 30, 0.95);
|
||||||
|
border-left-color: var(--mil-red);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-sub-card.active {
|
||||||
|
background: rgba(35, 35, 35, 0.95);
|
||||||
|
border-color: var(--status-amber);
|
||||||
|
border-left-color: var(--status-amber);
|
||||||
|
box-shadow: 0 0 12px rgba(201, 162, 39, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-sub-card-name {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-sub-card-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-sub-card-stats .amber {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Subcategory Detail Panel ───────────────────────── */
|
||||||
|
.crt-sub-detail {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-top: 2px solid var(--status-amber);
|
||||||
|
background: rgba(16, 16, 16, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.crt-sub-detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.8rem 1.2rem;
|
||||||
background: rgba(20, 20, 20, 0.9);
|
background: rgba(20, 20, 20, 0.9);
|
||||||
border: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
border-left: 2px solid var(--status-amber);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.crt-sub-header:hover {
|
.crt-sub-detail-name {
|
||||||
background: rgba(25, 25, 25, 0.9);
|
|
||||||
border-left-color: var(--mil-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
.crt-sub-toggle {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 1rem;
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crt-sub-header.collapsed .crt-sub-toggle {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.crt-sub-name {
|
|
||||||
font-family: var(--font-display);
|
font-family: var(--font-display);
|
||||||
font-size: 0.95rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crt-sub-toggle {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.crt-sub-count {
|
.crt-sub-count {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 0.95rem;
|
font-size: 0.85rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crt-sub-notes {
|
.crt-sub-detail-notes {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.6rem 1.2rem;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 1rem;
|
font-size: 0.9rem;
|
||||||
color: var(--status-amber);
|
color: var(--status-amber);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
border-left: 2px solid var(--status-amber);
|
border-bottom: 1px solid var(--border);
|
||||||
background: rgba(201, 162, 39, 0.05);
|
background: rgba(201, 162, 39, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crt-sub-detail-entries {
|
||||||
|
/* entries rendered inside here */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Responsive subcategory grid ────────────────────── */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.crt-sub-grid { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.crt-sub-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.crt-sub-grid { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ─── Entry List ─────────────────────────────────────── */
|
/* ─── Entry List ─────────────────────────────────────── */
|
||||||
.crt-entries {
|
.crt-entries {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
|
||||||
102
js/contraband.js
102
js/contraband.js
|
|
@ -108,36 +108,28 @@
|
||||||
<button class="crt-filter-btn" data-filter="starred">⭐ STARRED (${cat.starred_count})</button>
|
<button class="crt-filter-btn" data-filter="starred">⭐ STARRED (${cat.starred_count})</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// Subcategories
|
// Subcategory grid (4-col cards)
|
||||||
html += `<div id="crtSubcategories">`;
|
html += `<div class="crt-sub-grid" id="crtSubGrid">`;
|
||||||
for (let i = 0; i < cat.subcategories.length; i++) {
|
for (let i = 0; i < cat.subcategories.length; i++) {
|
||||||
const sub = cat.subcategories[i];
|
const sub = cat.subcategories[i];
|
||||||
if (sub.entries.length === 0 && sub.notes.length === 0) continue;
|
if (sub.entries.length === 0 && sub.notes.length === 0) continue;
|
||||||
|
const starCount = sub.entries.filter(e => e.starred).length;
|
||||||
html += `<div class="crt-subcategory" data-sub-idx="${i}">`;
|
html += `<div class="crt-sub-card" data-sub-idx="${i}">
|
||||||
html += `<div class="crt-sub-header collapsed" data-toggle="${i}">
|
<div class="crt-sub-card-name">${esc(sub.name)}</div>
|
||||||
<span class="crt-sub-toggle">▸</span>
|
<div class="crt-sub-card-stats">
|
||||||
<span class="crt-sub-name">${esc(sub.name)}</span>
|
<span>${sub.entries.length} items</span>
|
||||||
<span class="crt-sub-count">${sub.entries.length} items</span>
|
${starCount > 0 ? `<span class="amber">⭐ ${starCount}</span>` : ''}
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// Notes
|
|
||||||
if (sub.notes && sub.notes.length > 0) {
|
|
||||||
html += `<div class="crt-sub-notes" style="display:none" id="crtNotes${i}">`;
|
|
||||||
for (const note of sub.notes) {
|
|
||||||
html += `⚠ ${esc(note)}<br>`;
|
|
||||||
}
|
}
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
}
|
|
||||||
|
|
||||||
// Entries
|
// Shared detail panel (populated on card click)
|
||||||
html += `<div class="crt-entries" id="crtEntries${i}" style="display:none">`;
|
html += `<div class="crt-sub-detail" id="crtSubDetail" style="display:none">
|
||||||
for (const entry of sub.entries) {
|
<div class="crt-sub-detail-header" id="crtSubDetailHeader"></div>
|
||||||
html += renderEntry(entry);
|
<div class="crt-sub-detail-notes" id="crtSubDetailNotes"></div>
|
||||||
}
|
<div class="crt-sub-detail-entries" id="crtSubDetailEntries"></div>
|
||||||
html += `</div></div>`;
|
</div>`;
|
||||||
}
|
|
||||||
html += `</div>`;
|
|
||||||
|
|
||||||
// End of grid
|
// End of grid
|
||||||
|
|
||||||
|
|
@ -250,25 +242,48 @@
|
||||||
loadIndex();
|
loadIndex();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Subcategory toggle (collapse/expand)
|
// Subcategory card clicks
|
||||||
document.querySelectorAll('.crt-sub-header').forEach(header => {
|
document.querySelectorAll('.crt-sub-card').forEach(card => {
|
||||||
header.addEventListener('click', () => {
|
card.addEventListener('click', () => {
|
||||||
const idx = header.dataset.toggle;
|
const idx = parseInt(card.dataset.subIdx);
|
||||||
const entries = document.getElementById(`crtEntries${idx}`);
|
const sub = cat.subcategories[idx];
|
||||||
if (!entries) return;
|
const panel = document.getElementById('crtSubDetail');
|
||||||
const isCollapsed = header.classList.contains('collapsed');
|
const headerEl = document.getElementById('crtSubDetailHeader');
|
||||||
const notes = document.getElementById(`crtNotes${idx}`);
|
const notesEl = document.getElementById('crtSubDetailNotes');
|
||||||
if (isCollapsed) {
|
const entriesEl = document.getElementById('crtSubDetailEntries');
|
||||||
header.classList.remove('collapsed');
|
|
||||||
entries.style.display = '';
|
// If clicking the already active card, collapse
|
||||||
if (notes) notes.style.display = '';
|
const wasActive = card.classList.contains('active');
|
||||||
header.querySelector('.crt-sub-toggle').textContent = '▾';
|
document.querySelectorAll('.crt-sub-card').forEach(c => c.classList.remove('active'));
|
||||||
} else {
|
|
||||||
header.classList.add('collapsed');
|
if (wasActive) {
|
||||||
entries.style.display = 'none';
|
panel.style.display = 'none';
|
||||||
if (notes) notes.style.display = 'none';
|
return;
|
||||||
header.querySelector('.crt-sub-toggle').textContent = '▸';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
card.classList.add('active');
|
||||||
|
|
||||||
|
// Populate header
|
||||||
|
headerEl.innerHTML = `<span class="crt-sub-toggle">▾</span> <span class="crt-sub-detail-name">${esc(sub.name)}</span> <span class="crt-sub-count">${sub.entries.length} items</span>`;
|
||||||
|
|
||||||
|
// Populate notes
|
||||||
|
if (sub.notes && sub.notes.length > 0) {
|
||||||
|
let nh = '';
|
||||||
|
for (const note of sub.notes) nh += `⚠ ${esc(note)}<br>`;
|
||||||
|
notesEl.innerHTML = nh;
|
||||||
|
notesEl.style.display = '';
|
||||||
|
} else {
|
||||||
|
notesEl.innerHTML = '';
|
||||||
|
notesEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate entries
|
||||||
|
let eh = '';
|
||||||
|
for (const entry of sub.entries) eh += renderEntry(entry);
|
||||||
|
entriesEl.innerHTML = eh;
|
||||||
|
|
||||||
|
panel.style.display = '';
|
||||||
|
panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -301,11 +316,6 @@
|
||||||
const text = entry.textContent.toLowerCase();
|
const text = entry.textContent.toLowerCase();
|
||||||
entry.style.display = text.includes(q) ? '' : 'none';
|
entry.style.display = text.includes(q) ? '' : 'none';
|
||||||
});
|
});
|
||||||
// Hide empty subcategories
|
|
||||||
document.querySelectorAll('.crt-subcategory').forEach(sub => {
|
|
||||||
const visible = sub.querySelectorAll('.crt-entry:not([style*="display: none"])');
|
|
||||||
sub.style.display = visible.length > 0 || !q ? '' : 'none';
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue