Add collapsible cards with persistent state (localStorage)

- All .card > h2 cards get a rotating chevron toggle; clicking the h2
  collapses/expands all content below it
- Existing <details> elements inside cards are synced to the same
  localStorage store so their state also persists across reloads
- Default state: expanded (open); first collapse saves false, reload restores it
- Storage key: 'card-collapse-v1' → per-card slug of heading text
- CSS: chevron box matches opt-group style; <details> summary styled consistently

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-05-26 13:25:57 +02:00
parent 9c47b1998d
commit 04b82e49d8
2 changed files with 69 additions and 0 deletions

View File

@ -6163,3 +6163,60 @@ loadSettings().then(() => {
elLoadKey(); elLoadKey();
})(); })();
loadVoiceLibrary().then(renderIntegrationSnippets).catch(e => status('Voice library load failed: ' + e.message)); loadVoiceLibrary().then(renderIntegrationSnippets).catch(e => status('Voice library load failed: ' + e.message));
// ── Collapsible cards ──────────────────────────────────────────────────────
(function initCollapsibleCards() {
const LS_KEY = 'card-collapse-v1';
const load = () => { try { return JSON.parse(localStorage.getItem(LS_KEY) || '{}'); } catch { return {}; } };
const save = (k, v) => { const s = load(); s[k] = v; try { localStorage.setItem(LS_KEY, JSON.stringify(s)); } catch {} };
const slug = t => t.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 48);
// Sync existing <details> elements inside .card to localStorage
document.querySelectorAll('.card > details').forEach(det => {
const sum = det.querySelector('summary');
if (!sum) return;
const key = 'det-' + slug(sum.textContent);
const st = load();
if (st[key] !== undefined) det.open = st[key];
else st[key] = det.open; // persist current default
det.addEventListener('toggle', () => save(key, det.open));
});
// Add collapse toggle to every .card that has a direct h2 child
document.querySelectorAll('.card > h2').forEach(h2 => {
const card = h2.parentElement;
if (!card || card.dataset.colInit) return;
card.dataset.colInit = '1';
const key = 'card-' + slug(h2.textContent);
const open = load()[key] !== false; // default: open
// Prepend a rotating chevron inside the h2
const chev = document.createElement('span');
chev.className = 'card-chev';
chev.setAttribute('aria-hidden', 'true');
h2.prepend(chev);
h2.classList.add('card-collapse-h2');
// Wrap every element after h2 in a single body div
const body = document.createElement('div');
body.className = 'card-col-body';
let sib = h2.nextElementSibling;
while (sib) { const nx = sib.nextElementSibling; body.appendChild(sib); sib = nx; }
card.appendChild(body);
const apply = (isOpen) => {
body.hidden = !isOpen;
card.classList.toggle('card-col-closed', !isOpen);
};
apply(open);
h2.addEventListener('click', () => {
const nowOpen = body.hidden; // hidden → about to open
apply(nowOpen);
save(key, nowOpen);
});
});
})();

View File

@ -161,6 +161,18 @@ body {
text-transform: uppercase; letter-spacing: .09em; text-transform: uppercase; letter-spacing: .09em;
} }
/* ── Collapsible cards ───────────────────────────────────────────────────── */
.card-collapse-h2 { cursor: pointer; user-select: none; display: flex; align-items: center; gap: 7px; }
.card-collapse-h2:hover { opacity: .8; }
.card-chev { display: inline-block; flex-shrink: 0; width: 16px; height: 16px; border-radius: 4px; background: var(--panel); border: 1px solid var(--border); color: var(--accent); font-size: 10px; line-height: 16px; text-align: center; transition: transform .2s; }
.card-chev::before { content: '▾'; }
.card-col-closed .card-chev { transform: rotate(-90deg); }
/* Sync <details> inside cards to the same chevron look */
.card > details > summary { list-style: none; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 7px; }
.card > details > summary::marker, .card > details > summary::-webkit-details-marker { display: none; }
.card > details > summary::before { content: '▾'; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 16px; height: 16px; border-radius: 4px; background: var(--panel); border: 1px solid var(--border); color: var(--accent); font-size: 10px; transition: transform .2s; }
.card > details:not([open]) > summary::before { transform: rotate(-90deg); }
/* ── Drop zone ──────────────────────────────────────────────────────────── */ /* ── Drop zone ──────────────────────────────────────────────────────────── */
#drop-zone { #drop-zone {
border: 2px dashed var(--border); border-radius: var(--radius); border: 2px dashed var(--border); border-radius: var(--radius);