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:
parent
9c47b1998d
commit
04b82e49d8
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user