Selfies: Difference between revisions
From Treeverse
More actions
No edit summary |
No edit summary |
||
| Line 9: | Line 9: | ||
background:#f4f8f4; padding:10px; border-radius:12px; | background:#f4f8f4; padding:10px; border-radius:12px; | ||
box-shadow:0 6px 20px rgba(0,0,0,.25); | box-shadow:0 6px 20px rgba(0,0,0,.25); | ||
width: | width:320px; font:14px/1.3 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; | ||
border:2px solid #3b5d3f; | border:2px solid #3b5d3f; | ||
} | } | ||
| Line 16: | Line 16: | ||
box-shadow:0 0 0 2px #fff,0 2px 6px rgba(0,0,0,.25); font-size:18px; line-height:1; background:#16a085} | box-shadow:0 0 0 2px #fff,0 2px 6px rgba(0,0,0,.25); font-size:18px; line-height:1; background:#16a085} | ||
.tv-input{display:block;width:100%;margin:6px 0;box-sizing:border-box} | .tv-input{display:block;width:100%;margin:6px 0;box-sizing:border-box} | ||
.tv-text | .tv-text{padding:8px 10px;border:1px solid #ccc;border-radius:6px;font-size:15px} | ||
.tv-btn{display:block;width:100%;margin-top:8px;padding:12px;border:none;border-radius:6px; | |||
.tv-btn{display:block;width:100%;margin-top: | font-weight:bold;cursor:pointer;background:#27ae60;color:#fff} | ||
.tv-btn:hover{background:#1f8b4d} | |||
/* Bigger file chooser */ | |||
#toast{position:fixed;top:10px;left:50%;transform:translateX(-50%);background:#2ecc71;color:#fff;padding:6px 12px;border-radius:6px;font-size:13px;opacity:0;transition:opacity .3s;pointer-events:none;z-index:2000} | #m-photo{padding:8px;border:1px solid #ccc;border-radius:6px;font-size:15px;background:#fff} | ||
/* Toast */ | |||
#toast{position:fixed;top:10px;left:50%;transform:translateX(-50%); | |||
background:#2ecc71;color:#fff;padding:6px 12px;border-radius:6px;font-size:13px; | |||
opacity:0;transition:opacity .3s;pointer-events:none;z-index:2000} | |||
#toast.show{opacity:1} | #toast.show{opacity:1} | ||
/* Wider, responsive popup with full-width image */ | |||
.tv-popup .leaflet-popup-content{margin:10px 12px; width:360px !important; max-width:360px !important} | |||
.tv-popup .leaflet-popup-content img{display:block;width:100%;height:auto;border-radius:6px} | |||
@media (max-width:520px){ | |||
.tv-popup .leaflet-popup-content{width:72vw !important; max-width:72vw !important} | |||
} | |||
</style> | </style> | ||
| Line 32: | Line 44: | ||
<h3>Map Tools</h3> | <h3>Map Tools</h3> | ||
< | <!-- Username required --> | ||
<input id="m-title" class="tv-input tv-text" placeholder="Enter Player Username *" required> | |||
<textarea id="m-desc" class="tv-input tv-text" rows="2" placeholder="Add description"></textarea> | |||
<input id="m-title" class="tv-input tv-text" placeholder=" | |||
<textarea id="m-desc" class="tv-input tv-text" rows="2" placeholder="Add | |||
<!-- allow jpg/jpeg/png/webp (≤10MB checked in JS + PHP) --> | <!-- allow jpg/jpeg/png/webp (≤10MB checked in JS + PHP) --> | ||
<input type="file" id="m-photo" class="tv-input" accept="image/jpeg,image/png,image/webp"> | <input type="file" id="m-photo" class="tv-input" accept="image/jpeg,image/png,image/webp"> | ||
<button id="m-add" class="tv-btn | <button id="m-add" class="tv-btn">Add to Map</button> | ||
<div style="font-size:12px;opacity:.75;margin-top:6px;line-height:1.4"> | <div style="font-size:12px;opacity:.75;margin-top:6px;line-height:1.4"> | ||
Click on pin to view selfie. | |||
</div> | </div> | ||
</div> | </div> | ||
| Line 56: | Line 62: | ||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | ||
<script> | <script> | ||
// --- CONFIG (endpoints) --- | // --- CONFIG (endpoints) --- | ||
| Line 70: | Line 75: | ||
const t=(title||'').trim(); | const t=(title||'').trim(); | ||
const d=(desc||'').trim().replace(/</g,'<').replace(/>/g,'>'); | const d=(desc||'').trim().replace(/</g,'<').replace(/>/g,'>'); | ||
const img = imgUrl ? `<div style="margin-top:6px"><img src="${imgUrl}" alt=" | const img = imgUrl ? `<div style="margin-top:6px"><img src="${imgUrl}" alt=""></div>` : ''; | ||
return `<b>${t||'Untitled'}</b>${d?`<div style="margin-top: | return `<b>${t||'Untitled'}</b>${d?`<div style="margin-top:6px">${d}</div>`:''}${img}`; | ||
} | } | ||
// Load background image to get pixel bounds | |||
const img = new Image(); | const img = new Image(); | ||
img.onload = () => { | img.onload = () => { | ||
| Line 82: | Line 88: | ||
map.fitBounds(bounds); | map.fitBounds(bounds); | ||
// Single layer for selfies (no legend) | |||
const selfies = L.layerGroup().addTo(map); | const selfies = L.layerGroup().addTo(map); | ||
// Add / build marker | |||
function addMarker(latlng, title, desc, imgUrl){ | function addMarker(latlng, title, desc, imgUrl){ | ||
const m = L.marker(latlng,{icon:icon(), title:title||'Selfie', desc:desc||'', imgUrl:imgUrl||null}).addTo(selfies); | const m = L.marker(latlng,{icon:icon(), title:title||'Selfie', desc:desc||'', imgUrl:imgUrl||null}).addTo(selfies); | ||
m.bindPopup(popupHtml(title,desc,imgUrl)); | m.bindPopup(popupHtml(title,desc,imgUrl), { className:'tv-popup', maxWidth:600, minWidth:200, autoPan:true }); | ||
m.on('click', () => m.openPopup()); | m.on('click', () => m.openPopup()); | ||
return m; | return m; | ||
} | } | ||
function collectMarkers(){ | function collectMarkers(){ | ||
const all=[]; selfies.eachLayer(l=>{ const ll=l.getLatLng(); | const all=[]; selfies.eachLayer(l=>{ const ll=l.getLatLng(); | ||
| Line 102: | Line 104: | ||
}); return all; | }); return all; | ||
} | } | ||
async function saveShared(){ | async function saveShared(){ | ||
const payload = JSON.stringify(collectMarkers()); | const payload = JSON.stringify(collectMarkers()); | ||
| Line 108: | Line 111: | ||
toast('Saved ✅'); | toast('Saved ✅'); | ||
} | } | ||
function applyMarkers(arr){ arr.forEach(m => addMarker([m.y,m.x], m.title||'Selfie', m.desc||'', m.img||null)); } | function applyMarkers(arr){ arr.forEach(m => addMarker([m.y,m.x], m.title||'Selfie', m.desc||'', m.img||null)); } | ||
// UI refs | // UI refs | ||
const tI=document.getElementById('m-title'), dI=document.getElementById('m-desc'), pI=document.getElementById('m-photo'), addBtn=document.getElementById('m-add'); | const tI=document.getElementById('m-title'), | ||
dI=document.getElementById('m-desc'), | |||
pI=document.getElementById('m-photo'), | |||
addBtn=document.getElementById('m-add'); | |||
// Client-side file | // Client-side checks: username required; file type/size optional but validated | ||
function | function validateInputs(){ | ||
const f = pI.files[0]; if( | const name = tI.value.trim(); | ||
if(!name){ alert('Please enter a player username.'); tI.focus(); return false; } | |||
const f = pI.files[0]; | |||
if(f){ | |||
const ok = ['image/jpeg','image/png','image/webp']; | |||
if(!ok.includes(f.type)){ alert('Only JPG/JPEG, PNG, or WEBP allowed.'); pI.value=''; return false; } | |||
if(f.size > 10*1024*1024){ alert('Image is over 10MB.'); pI.value=''; return false; } | |||
} | |||
return true; | return true; | ||
} | } | ||
async function uploadPhotoIfAny(){ | async function uploadPhotoIfAny(){ | ||
const f = pI.files[0]; if(!f) return null; | const f = pI.files[0]; if(!f) return null; | ||
| Line 129: | Line 141: | ||
} | } | ||
// Add | // Add → click to place | ||
let placing=null; | let placing=null; | ||
addBtn.onclick = () => { | addBtn.onclick = () => { | ||
if(! | if(!validateInputs()) return; | ||
placing = { title: tI.value, desc: dI.value }; | placing = { title: tI.value, desc: dI.value }; | ||
addBtn.textContent='Click map…'; addBtn.disabled=true; map.getContainer().style.cursor='crosshair'; | addBtn.textContent='Click map…'; addBtn.disabled=true; map.getContainer().style.cursor='crosshair'; | ||
| Line 147: | Line 159: | ||
finally{ cancel(); tI.value=''; dI.value=''; pI.value=''; } | finally{ cancel(); tI.value=''; dI.value=''; pI.value=''; } | ||
}); | }); | ||
}; | }; | ||
Revision as of 22:28, 27 August 2025
Map Tools
Click on pin to view selfie.
Saved ✅