style(portfolio): nedladdnings-UI i linje med sajtens formsprak

Knappen var kantig medan allt runt omkring ar rundat. Nu:
- rounded-full + hover:scale-105 som sajtens ovriga CTA-knappar
- nedladdningsikon, uppercase tracking-widest
- menyn rundad med backdrop-blur och mjuk in/ut-overgang
- losenordsrutan rundad med samma kort-kansla som lightboxen
- Escape stanger menyn innan lightboxen
This commit is contained in:
portfolio-sync
2026-09-19 16:21:53 +00:00
parent 59383f703f
commit ce93f90250
+38 -17
View File
@@ -166,12 +166,15 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value
<p id="lb-model" class="text-gray-400 text-sm font-medium tracking-wide mt-1"></p> <p id="lb-model" class="text-gray-400 text-sm font-medium tracking-wide mt-1"></p>
</div> </div>
<div class="relative"> <div class="relative">
<button id="lb-download" class="bg-white text-black font-bold tracking-wide px-6 py-3 hover:bg-gray-200 transition-colors">DOWNLOAD</button> <button id="lb-download" class="group inline-flex items-center gap-2 px-7 py-3 bg-white text-black font-bold text-sm uppercase tracking-widest rounded-full hover:bg-gray-200 transition transform hover:scale-105">
<div id="lb-sizes" class="hidden absolute bottom-full mb-2 left-0 min-w-[220px] bg-neutral-900 border border-white/10 shadow-xl z-50"> <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v12m0 0l-4-4m4 4l4-4M4 19h16"/></svg>
<a data-size="s" class="dl-opt block px-4 py-3 text-sm text-gray-200 hover:bg-white/10 cursor-pointer border-b border-white/5">Small <span class="text-gray-500 float-right">800 px</span></a> Download
<a data-size="m" class="dl-opt block px-4 py-3 text-sm text-gray-200 hover:bg-white/10 cursor-pointer border-b border-white/5">Medium <span class="text-gray-500 float-right">1600 px</span></a> </button>
<a data-size="l" class="dl-opt block px-4 py-3 text-sm text-gray-200 hover:bg-white/10 cursor-pointer border-b border-white/5">Large <span class="text-gray-500 float-right">3000 px</span></a> <div id="lb-sizes" class="hidden absolute bottom-full mb-3 left-0 min-w-[260px] bg-neutral-900/95 backdrop-blur-md border border-white/10 rounded-xl shadow-2xl z-50 overflow-hidden origin-bottom opacity-0 translate-y-2 transition-all duration-200 ease-out">
<a data-size="orig" class="dl-opt block px-4 py-3 text-sm text-white hover:bg-white/10 cursor-pointer">Original <span class="text-gray-500 float-right">full storlek</span></a> <a data-size="s" class="dl-opt group flex items-center justify-between px-5 py-3.5 text-sm text-gray-200 hover:bg-white/10 hover:text-white cursor-pointer transition-colors border-b border-white/5"><span class="font-medium uppercase tracking-widest text-xs">Small</span><span class="text-gray-500 text-xs font-mono group-hover:text-gray-300 transition-colors">800 px</span></a>
<a data-size="m" class="dl-opt group flex items-center justify-between px-5 py-3.5 text-sm text-gray-200 hover:bg-white/10 hover:text-white cursor-pointer transition-colors border-b border-white/5"><span class="font-medium uppercase tracking-widest text-xs">Medium</span><span class="text-gray-500 text-xs font-mono group-hover:text-gray-300 transition-colors">1600 px</span></a>
<a data-size="l" class="dl-opt group flex items-center justify-between px-5 py-3.5 text-sm text-gray-200 hover:bg-white/10 hover:text-white cursor-pointer transition-colors border-b border-white/5"><span class="font-medium uppercase tracking-widest text-xs">Large</span><span class="text-gray-500 text-xs font-mono group-hover:text-gray-300 transition-colors">3000 px</span></a>
<a data-size="orig" class="dl-opt group flex items-center justify-between px-5 py-3.5 text-sm text-gray-200 hover:bg-white/10 hover:text-white cursor-pointer transition-colors "><span class="font-medium uppercase tracking-widest text-xs">Original<span class="block text-[10px] text-gray-500 normal-case tracking-normal mt-0.5">Kräver lösenord</span></span><span class="text-gray-500 text-xs font-mono group-hover:text-gray-300 transition-colors">full storlek</span></a>
</div> </div>
</div> </div>
<div class="flex flex-wrap gap-3 md:gap-6 text-xs md:text-sm font-mono text-gray-300"> <div class="flex flex-wrap gap-3 md:gap-6 text-xs md:text-sm font-mono text-gray-300">
@@ -187,16 +190,16 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value
<!-- Losenordsruta for ORIGINAL. Originalen ligger inte i repot (27-70 MB/st) <!-- Losenordsruta for ORIGINAL. Originalen ligger inte i repot (27-70 MB/st)
utan hamtas fran Immich via /download/original, som kraver losenord. --> utan hamtas fran Immich via /download/original, som kraver losenord. -->
<div id="pw-modal" class="hidden fixed inset-0 z-[60] bg-black/80 flex items-center justify-center p-4"> <div id="pw-modal" class="hidden fixed inset-0 z-[60] bg-black/85 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-neutral-900 border border-white/10 p-6 w-full max-w-sm"> <div class="bg-neutral-900 border border-white/10 rounded-xl shadow-2xl p-7 w-full max-w-sm">
<h4 class="text-white font-bold text-lg mb-1">Ladda ner original</h4> <h4 class="text-white font-bold text-lg tracking-tight mb-1">Ladda ner original</h4>
<p class="text-gray-400 text-sm mb-4">Originalfilen är i full upplösning och kräver lösenord.</p> <p class="text-gray-400 text-sm mb-4">Originalfilen är i full upplösning och kräver lösenord.</p>
<input id="pw-input" type="password" placeholder="Lösenord" autocomplete="current-password" <input id="pw-input" type="password" placeholder="Lösenord" autocomplete="current-password"
class="w-full bg-black border border-white/20 text-white px-3 py-2 mb-2 focus:outline-none focus:border-white/60" /> class="w-full bg-black/60 border border-white/20 rounded-lg text-white px-4 py-2.5 mb-2 focus:outline-none focus:border-white/60 focus:ring-1 focus:ring-white/20 transition" />
<p id="pw-error" class="hidden text-red-400 text-sm mb-2">Fel lösenord — försök igen.</p> <p id="pw-error" class="hidden text-red-400 text-xs tracking-wide mb-2">Fel lösenord — försök igen.</p>
<div class="flex gap-2 justify-end mt-2"> <div class="flex gap-2 justify-end mt-2">
<button id="pw-cancel" class="px-4 py-2 text-sm text-gray-400 hover:text-white">Avbryt</button> <button id="pw-cancel" class="px-5 py-2.5 text-xs uppercase tracking-widest text-gray-400 hover:text-white rounded-full transition-colors">Avbryt</button>
<button id="pw-ok" class="px-4 py-2 text-sm bg-white text-black font-bold hover:bg-gray-200">Ladda ner</button> <button id="pw-ok" class="px-5 py-2.5 text-xs uppercase tracking-widest bg-white text-black font-bold rounded-full hover:bg-gray-200 transition transform hover:scale-105">Ladda ner</button>
</div> </div>
</div> </div>
</div> </div>
@@ -321,20 +324,31 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value
a.remove(); a.remove();
} }
function visaMeny(visa: boolean) {
if (!dlMenu) return;
if (visa) {
dlMenu.classList.remove('hidden');
requestAnimationFrame(() => dlMenu.classList.remove('opacity-0', 'translate-y-2'));
} else {
dlMenu.classList.add('opacity-0', 'translate-y-2');
setTimeout(() => dlMenu.classList.add('hidden'), 180);
}
}
dlBtn?.addEventListener('click', (e) => { dlBtn?.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
dlMenu?.classList.toggle('hidden'); visaMeny(dlMenu?.classList.contains('hidden') ?? true);
}); });
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (!dlMenu || dlMenu.classList.contains('hidden')) return; if (!dlMenu || dlMenu.classList.contains('hidden')) return;
if (!dlMenu.contains(e.target as Node) && e.target !== dlBtn) dlMenu.classList.add('hidden'); if (!dlMenu.contains(e.target as Node) && !dlBtn?.contains(e.target as Node)) visaMeny(false);
}); });
document.querySelectorAll('.dl-opt').forEach((opt) => { document.querySelectorAll('.dl-opt').forEach((opt) => {
opt.addEventListener('click', () => { opt.addEventListener('click', () => {
const size = (opt as HTMLElement).dataset.size || 'l'; const size = (opt as HTMLElement).dataset.size || 'l';
dlMenu?.classList.add('hidden'); visaMeny(false);
const url = urlForSize(size); const url = urlForSize(size);
if (!url) { alert('Den storleken saknas for den har bilden.'); return; } if (!url) { alert('Den storleken saknas for den har bilden.'); return; }
if (size === 'orig') { if (size === 'orig') {
@@ -371,6 +385,13 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value
pwInput?.addEventListener('keydown', (e) => { if ((e as KeyboardEvent).key === 'Enter') forsokOriginal(); }); pwInput?.addEventListener('keydown', (e) => { if ((e as KeyboardEvent).key === 'Enter') forsokOriginal(); });
document.getElementById('pw-cancel')?.addEventListener('click', () => pwModal?.classList.add('hidden')); document.getElementById('pw-cancel')?.addEventListener('click', () => pwModal?.classList.add('hidden'));
document.addEventListener('keydown', (e) => {
if ((e as KeyboardEvent).key === 'Escape' && !dlMenu?.classList.contains('hidden')) {
e.stopImmediatePropagation();
visaMeny(false);
}
}, true);
function closeLightbox() { function closeLightbox() {
lightbox?.classList.add('opacity-0'); lightbox?.classList.add('opacity-0');
setTimeout(() => { setTimeout(() => {
@@ -382,7 +403,7 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value
function updateLightboxContent() { function updateLightboxContent() {
const item = visibleItems[currentImageIndex]; const item = visibleItems[currentImageIndex];
if (!item) return; if (!item) return;
dlMenu?.classList.add('hidden'); visaMeny(false);
lbImg.src = item.dataset.src || ''; lbImg.src = item.dataset.src || '';