diff --git a/css/styles.css b/css/styles.css index 90b39d8..382f7a0 100644 --- a/css/styles.css +++ b/css/styles.css @@ -52,8 +52,10 @@ input,textarea,select{font-family:var(--font)} #countries-bar .cb-label{font-size:.58rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px} #countries-bar .cb-status{font-size:.68rem;color:var(--text);height:1.1em;margin-top:10px;transition:opacity .15s;opacity:.5} #countries-bar .cb-flags{font-size:1rem;letter-spacing:2px;line-height:1.4;overflow:hidden;transition:max-height .3s ease} -#countries-bar .cb-flags span{cursor:default;transition:transform .1s} -#countries-bar .cb-flags span:hover{transform:scale(1.25)} +#countries-bar .cb-flags span{cursor:default;transition:transform .1s,background .15s;border-radius:50%;padding:2px;margin:-2px} +#countries-bar .cb-flags span[data-has-photos]{cursor:pointer} +#countries-bar .cb-flags span[data-has-photos]:hover{transform:scale(1.2);background:rgba(91,142,240,.25)} +#countries-bar .cb-flags span:not([data-has-photos]):hover{transform:scale(1.2)} #countries-bar .cb-flags.collapsed{max-height:2.8em} #countries-bar .cb-toggle{background:none;border:none;color:var(--muted);font-size:1rem;cursor:pointer;padding:0;margin:6px 0 -4px;transition:color .15s, transform .3s ease;display:block;width:100%;text-align:center;line-height:1} #countries-bar .cb-toggle:hover{color:var(--text)} @@ -355,6 +357,20 @@ textarea.fi{resize:vertical;min-height:60px;line-height:1.5} #lb-caption{position:absolute;bottom:44px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.65);color:#fff;padding:6px 16px;border-radius:20px;font-size:.74rem;backdrop-filter:blur(4px);max-width:80vw;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} #lb-camera{position:absolute;bottom:18px;left:50%;transform:translateX(-50%);color:rgba(255,255,255,.45);font-size:.62rem;text-align:center;white-space:nowrap;letter-spacing:.02em} +/* COUNTRY FILM STRIP */ +#country-strip{position:fixed;bottom:0;left:0;right:0;height:80px;background:rgba(0,0,0,.88);backdrop-filter:blur(10px);display:flex;align-items:center;z-index:600;transform:translateY(100%);transition:transform .3s ease;border-top:1px solid rgba(255,255,255,.08)} +#country-strip.open{transform:translateY(0)} +#country-strip-header{display:flex;align-items:center;gap:8px;padding:0 16px;flex-shrink:0;border-right:1px solid rgba(255,255,255,.1);height:100%} +#country-strip-label{color:#fff;font-size:.82rem;font-weight:500;white-space:nowrap;font-family:var(--font);display:flex;align-items:center;gap:6px} +.strip-flag{font-size:1rem;line-height:1} +#country-strip-close{background:rgba(255,255,255,.1);border:none;color:#fff;font-size:.8rem;width:26px;height:26px;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;flex-shrink:0} +#country-strip-close:hover{background:rgba(255,255,255,.22)} +#country-strip-photos{display:flex;align-items:center;gap:8px;overflow-x:auto;overflow-y:hidden;padding:0 14px;flex:1;height:100%;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.2) transparent} +#country-strip-photos::-webkit-scrollbar{height:4px} +#country-strip-photos::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);border-radius:2px} +.strip-thumb{width:58px;height:58px;object-fit:cover;border-radius:8px;flex-shrink:0;cursor:pointer;transition:transform .12s,box-shadow .12s;box-shadow:0 2px 8px rgba(0,0,0,.4)} +.strip-thumb:hover{transform:scale(1.08);box-shadow:0 4px 16px rgba(0,0,0,.6)} + /* OFFLINE BANNER */ #offline-banner{position:fixed;top:0;left:0;right:0;background:rgba(232,119,74,.92);color:#fff;text-align:center;padding:6px 12px;font-size:.75rem;font-weight:500;z-index:10000;transform:translateY(-100%);transition:transform .3s ease;pointer-events:none;font-family:var(--font);letter-spacing:.3px} #offline-banner.show{transform:translateY(0)} diff --git a/index.html b/index.html index 5a65d0e..2f3c43f 100644 --- a/index.html +++ b/index.html @@ -202,6 +202,13 @@
+
+
+ + +
+
+
Offline — browsing cached data
diff --git a/js/data.js b/js/data.js index b136cbc..687362e 100644 --- a/js/data.js +++ b/js/data.js @@ -46,7 +46,8 @@ function updateCountriesBar() { const codes = Object.keys(earliest); if (!codes.length) { bar.style.display = 'none'; flagsEl.innerHTML = ''; return; } const sorted = codes.sort((a, b) => earliest[a] < earliest[b] ? -1 : earliest[a] > earliest[b] ? 1 : 0); - flagsEl.innerHTML = sorted.map(c => `${countryFlag(c)}`).join(' '); + const hasPhotos = new Set(photos.filter(p => p.countryCode && p.lat !== null && !p.isEmptyPin).map(p => p.countryCode)); + flagsEl.innerHTML = sorted.map(c => `${countryFlag(c)}`).join(' '); bar.style.display = 'block'; const labelEl = document.querySelector('#countries-bar .cb-label'); labelEl.textContent = sorted.length === 1 ? '1 Country Visited' : `${sorted.length} Countries Visited`; @@ -91,8 +92,61 @@ function toggleCountriesBar() { if (span) { statusEl.textContent = span.dataset.name; statusEl.style.opacity = '1'; } }); flagsEl.addEventListener('mouseleave', () => { statusEl.textContent = '\u00a0'; statusEl.style.opacity = '.5'; }); + flagsEl.addEventListener('click', e => { + const span = e.target.closest('span[data-cc]'); + if (span) toggleCountryStrip(span.dataset.cc); + }); })(); +// ═══════════════════════════════════════ +// COUNTRY FILM STRIP +// ═══════════════════════════════════════ +let _stripCountry = null; + +function toggleCountryStrip(cc) { + if (_stripCountry === cc) { closeCountryStrip(); return; } + openCountryStrip(cc); +} + +function openCountryStrip(cc) { + const strip = document.getElementById('country-strip'); + const photosEl = document.getElementById('country-strip-photos'); + const labelEl = document.getElementById('country-strip-label'); + + const countryPhotos = photos + .filter(p => p.countryCode === cc && p.lat !== null && !p.isEmptyPin) + .sort((a, b) => photoSortKey(a) < photoSortKey(b) ? -1 : 1); + + if (!countryPhotos.length) return; + + _stripCountry = cc; + labelEl.innerHTML = `${countryFlag(cc)}${esc(countryName(cc))}`; + photosEl.innerHTML = ''; + + const ids = countryPhotos.map(p => p.id); + countryPhotos.forEach((p, i) => { + const img = document.createElement('img'); + img.className = 'strip-thumb'; + img.src = p.thumbUrl; + img.alt = p.placeName || p.name || ''; + img.loading = 'lazy'; + img.addEventListener('click', () => { + lbIds = ids; + lbIdx = i; + showLbPhoto(); + document.getElementById('lightbox').classList.add('open'); + }); + photosEl.appendChild(img); + }); + + strip.classList.add('open'); +} + +function closeCountryStrip() { + _stripCountry = null; + document.getElementById('country-strip').classList.remove('open'); +} + // ═══════════════════════════════════════ // DND + UPLOAD // ═══════════════════════════════════════ diff --git a/js/lightbox.js b/js/lightbox.js index 38b21a4..36cb4f9 100644 --- a/js/lightbox.js +++ b/js/lightbox.js @@ -89,5 +89,8 @@ document.addEventListener('keydown',e=>{ if(lb&&e.key==='Escape')closeLightbox(); if(lb&&e.key==='ArrowLeft')lbNav(-1); if(lb&&e.key==='ArrowRight')lbNav(1); - if(!lb&&e.key==='Escape'){closeMetaModal();clearDestSearch();} + if(!lb&&e.key==='Escape'){ + if(document.getElementById('country-strip').classList.contains('open')){closeCountryStrip();} + else{closeMetaModal();clearDestSearch();} + } }); \ No newline at end of file diff --git a/js/map.js b/js/map.js index 42ca9eb..b7ad899 100644 --- a/js/map.js +++ b/js/map.js @@ -11,7 +11,7 @@ const STYLE_DARK = 'https://tiles.openfreemap.org/styles/dark'; const STYLE_SAT = { version:8, sources:{sat:{type:'raster',tiles:['https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2020_3857/default/g/{z}/{y}/{x}.jpg'],tileSize:256,maxzoom:18}}, - layers:[{id:'sat',type:'raster',source:'sat',paint:{'raster-fade-duration':0}}] + layers:[{id:'sat',type:'raster',source:'sat',paint:{'raster-fade-duration':0,'raster-saturation':0.15,'raster-contrast':0.08,'raster-brightness-max':0.95}}] }; function normalizeDarkLabels() { // Dark style uses uppercase + regular weight on all place labels. @@ -106,7 +106,6 @@ function _patchStyleWater(styleObj) { // Apple Maps Dark palette — neutral dark land, distinctly blue water. // Colors pre-compensated for canvas filter brightness(1.8) contrast(0.9). if (_mapStyle === 'dark') { - // Darker water for Dark Map const waterColor = '#17212b'; for (const layer of styleObj.layers) { if (!layer.paint) layer.paint = {}; @@ -114,6 +113,26 @@ function _patchStyleWater(styleObj) { layer.paint['fill-color'] = waterColor; } } + // Coastline glow — subtle blue edge where water meets land + const waterIdx = styleObj.layers.findIndex(l => l.id === 'water'); + if (waterIdx !== -1) { + styleObj.layers.splice(waterIdx + 1, 0, { + id: 'water-glow', + type: 'line', + source: 'openmaptiles', + 'source-layer': 'water', + filter: ['all', + ['match', ['geometry-type'], ['MultiPolygon', 'Polygon'], true, false], + ['!=', ['get', 'brunnel'], 'tunnel'] + ], + paint: { + 'line-color': '#1a3a5c', + 'line-width': ['interpolate', ['linear'], ['zoom'], 0, 2, 6, 3.5, 12, 5], + 'line-blur': ['interpolate', ['linear'], ['zoom'], 0, 3, 6, 6, 12, 10], + 'line-opacity': 0.8 + } + }); + } } const color = _mapStyle === 'dark' ? '#6a9fd8' : '#2c5f8a'; let hasPointLabel = false;