diff --git a/css/styles.css b/css/styles.css index 382f7a0..8be59fc 100644 --- a/css/styles.css +++ b/css/styles.css @@ -72,19 +72,21 @@ input,textarea,select{font-family:var(--font)} #panel-photos{overflow-y:auto;padding:2px 14px 12px;flex:1} .section-label{font-size:.63rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:8px 0 6px} .year-group{margin-bottom:4px} -.year-hdr{display:flex;align-items:center;padding:10px 8px;cursor:pointer;user-select:none} -.year-hdr:hover{background:var(--surface2);border-radius:8px} -.year-hdr-arrow{font-size:.7rem;color:var(--muted);transition:transform .15s;width:16px;text-align:center;margin-right:8px} -.year-hdr.collapsed .year-hdr-arrow{transform:rotate(-90deg)} -.year-hdr-label{font-size:.95rem;font-weight:600;color:var(--text)} -.year-hdr-count{font-size:.62rem;color:var(--muted);margin-left:8px} -.year-hdr-line{flex:1;height:1px;background:var(--border);margin-left:8px}.year-body{padding-left:2px;overflow:hidden;transition:max-height .5s ease} +.year-hdr{display:flex;align-items:center;padding:10px 8px;cursor:pointer;user-select:none;border-radius:8px;transition:background .15s} +.year-hdr:hover{background:var(--surface2)} +.year-hdr-label{font-size:.95rem;font-weight:600;color:var(--text);transition:color .15s} +.year-hdr.collapsed .year-hdr-label{color:var(--muted)} +.year-hdr-count{font-size:.6rem;color:var(--muted);margin-left:9px;background:var(--surface2);padding:2px 8px;border-radius:10px;transition:background .15s,color .15s} +.year-hdr.collapsed .year-hdr-count{background:transparent;border:1px solid var(--border)} +.year-hdr:hover .year-hdr-count{background:var(--accent-dim);color:var(--text)} +.year-hdr-line{flex:1;height:1px;background:var(--border);margin-left:8px}.year-body{padding-left:2px;overflow:hidden;transition:max-height .3s ease} .year-body>.year-group>.year-hdr{padding-left:26px} .year-body>.year-group>.year-body{padding-left:20px} .year-hdr.collapsed+.year-body{max-height:0} -.collapse-all-btn{background:none;border:none;color:var(--muted);font-size:.7rem;cursor:pointer;padding:2px 6px;border-radius:4px;transition:transform .15s,background .15s;line-height:1} -.collapse-all-btn:hover{background:var(--surface2)} -.collapse-all-btn.all-collapsed{transform:rotate(-90deg)} +.collapse-all-btn{background:none;border:none;color:var(--muted);font-size:.62rem;cursor:pointer;padding:3px 7px;border-radius:5px;transition:background .15s,color .15s;line-height:1;font-family:var(--font);letter-spacing:.02em} +.collapse-all-btn:hover{background:var(--surface2);color:var(--text)} +.collapse-all-btn::after{content:'Collapse all'} +.collapse-all-btn.all-collapsed::after{content:'Expand all'} .photo-card{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:10px;cursor:pointer;transition:background .15s;margin-bottom:3px;box-sizing:border-box} .photo-card:hover{background:var(--surface2)} .photo-card.active{background:var(--accent-dim);outline:1px solid rgba(91,142,240,.3)} diff --git a/index.html b/index.html index 2f3c43f..5714e68 100644 --- a/index.html +++ b/index.html @@ -69,7 +69,7 @@
-
+
🌍
Add photos to build your travel map
diff --git a/js/photos.js b/js/photos.js index 3741536..c2759fb 100644 --- a/js/photos.js +++ b/js/photos.js @@ -6,22 +6,53 @@ function photoSortKey(p) { return '9999-99-99T' + String(p.addedAt).padStart(16,'0'); } +// Duration scales with content height so small groups snap and large ones +// don't feel sluggish. Clamped to 180–420ms. +function _animDuration(h) { + return Math.max(180, Math.min(420, 120 + h * 0.25)); +} + function _animateBody(hdr, body, expand) { const prev = body._animEnd; if (prev) { body.removeEventListener('transitionend', prev); body._animEnd = null; } + + // Respect the user's reduced-motion preference — toggle instantly. + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + hdr.classList.toggle('collapsed', !expand); + body.style.maxHeight = ''; + return; + } + if (expand) { body.style.maxHeight = ''; const h = body.scrollHeight; + body.style.transitionDuration = _animDuration(h) + 'ms'; body.style.maxHeight = '0'; void body.scrollHeight; body.style.maxHeight = h + 'px'; hdr.classList.remove('collapsed'); - const onEnd = () => { body.style.maxHeight = ''; body.removeEventListener('transitionend', onEnd); body._animEnd = null; }; + // Clear inline overrides on completion so nested groups can grow later. + const onEnd = () => { + body.style.maxHeight = ''; + body.style.transitionDuration = ''; + body.removeEventListener('transitionend', onEnd); + body._animEnd = null; + }; body._animEnd = onEnd; body.addEventListener('transitionend', onEnd); } else { - body.style.maxHeight = body.scrollHeight + 'px'; - requestAnimationFrame(() => { hdr.classList.add('collapsed'); body.style.maxHeight = ''; }); + const h = body.scrollHeight; + body.style.transitionDuration = _animDuration(h) + 'ms'; + body.style.maxHeight = h + 'px'; + void body.scrollHeight; + requestAnimationFrame(() => { hdr.classList.add('collapsed'); body.style.maxHeight = '0'; }); + const onEnd = () => { + body.style.transitionDuration = ''; + body.removeEventListener('transitionend', onEnd); + body._animEnd = null; + }; + body._animEnd = onEnd; + body.addEventListener('transitionend', onEnd); } } @@ -133,7 +164,7 @@ function rebuildPhotoList() { const decadeHdr = document.createElement('div'); decadeHdr.className = _expandedDecades.has(dk) ? 'year-hdr' : 'year-hdr collapsed'; - decadeHdr.innerHTML = `${decadeLabel}${totalCount}`; + decadeHdr.innerHTML = `${decadeLabel}${totalCount}`; decadeGroup.appendChild(decadeHdr); const decadeBody = document.createElement('div'); @@ -147,7 +178,7 @@ function rebuildPhotoList() { const yearHdr = document.createElement('div'); yearHdr.className = _expandedYears.has(yr) ? 'year-hdr' : 'year-hdr collapsed'; - yearHdr.innerHTML = `${yr}${byDecade[dk][yr].length}`; + yearHdr.innerHTML = `${yr}${byDecade[dk][yr].length}`; yearGroup.appendChild(yearHdr); const yearBody = document.createElement('div'); @@ -296,7 +327,7 @@ function buildTimeline() { const frag = document.createDocumentFragment(); const tlHdr = document.createElement('div'); tlHdr.style.cssText = 'display:flex;align-items:center;justify-content:space-between'; - tlHdr.innerHTML = ``; + tlHdr.innerHTML = ``; frag.appendChild(tlHdr); Object.keys(byYear).sort().forEach(yr=>{ const group = document.createElement('div'); @@ -304,7 +335,7 @@ function buildTimeline() { const collapsed = _tlCollapsedYears.has(yr); const hdr = document.createElement('div'); hdr.className = collapsed ? 'year-hdr collapsed' : 'year-hdr'; - hdr.innerHTML = `${yr}${yearCounts[yr]}`; + hdr.innerHTML = `${yr}${yearCounts[yr]}`; group.appendChild(hdr); const body = document.createElement('div');