Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
Expand Down Expand Up @@ -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)}
Expand Down
7 changes: 7 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,13 @@
<div id="lb-caption"></div>
<div id="lb-camera"></div>
</div>
<div id="country-strip">
<div id="country-strip-header">
<span id="country-strip-label"></span>
<button id="country-strip-close" onclick="closeCountryStrip()">✕</button>
</div>
<div id="country-strip-photos"></div>
</div>
<div id="toast"></div>
<div id="offline-banner">Offline — browsing cached data</div>

Expand Down
56 changes: 55 additions & 1 deletion js/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => `<span data-name="${esc(countryName(c))}">${countryFlag(c)}</span>`).join(' ');
const hasPhotos = new Set(photos.filter(p => p.countryCode && p.lat !== null && !p.isEmptyPin).map(p => p.countryCode));
flagsEl.innerHTML = sorted.map(c => `<span data-name="${esc(countryName(c))}" data-cc="${c}"${hasPhotos.has(c) ? ' data-has-photos' : ''}>${countryFlag(c)}</span>`).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`;
Expand Down Expand Up @@ -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 = `<span class="strip-flag">${countryFlag(cc)}</span>${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
// ═══════════════════════════════════════
Expand Down
5 changes: 4 additions & 1 deletion js/lightbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();}
}
});
23 changes: 21 additions & 2 deletions js/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -106,14 +106,33 @@ 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 = {};
if (layer.type === 'fill' && /^water/.test(layer.id)) {
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;
Expand Down