{% extends "base.html" %}
{% block title %}Photos · Kishaloy Roy{% endblock %}
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/photos_page.css') }}">
{% endblock %}
{% block content %}
<h1 class="page-title">My Photo Gallery</h1>
<p class="page-desc">Some Photos I Clicked.</p>
{% if photos|length == 0 %}
<p class="empty">No photos yet. Upload via <a href="{{ url_for('admin_photos') }}">admin panel</a>.</p>
{% else %}
<section class="gallery" aria-label="Photo gallery">
{% for p in photos %}
<button
class="tile"
data-index="{{ loop.index0 }}"
data-src="{{ p.url }}"
data-original="{{ p.original_url or '' }}"
data-title="{{ p.title or '' }}"
data-camera="{{ p.camera or '' }}"
data-lens="{{ p.lens or '' }}"
data-focal="{{ p.focal_length or '' }}"
data-aperture="{{ p.aperture or '' }}"
data-shutter="{{ p.shutter_speed or '' }}"
data-iso="{{ p.iso or '' }}"
data-date="{{ p.date_taken or '' }}"
aria-label="{{ p.title or 'Photo ' ~ loop.index }}"
>
<img src="{{ p.url }}" alt="{{ p.title or 'Photo ' ~ loop.index }}" loading="lazy">
</button>
{% endfor %}
</section>
<!-- Lightbox -->
<div class="lb-overlay" id="lb" role="dialog" aria-modal="true" aria-label="Photo viewer">
<!-- Top-right toolbar -->
<div class="lb-toolbar">
<a class="lb-btn lb-load-orig" id="lb-orig" href="#" target="_blank" rel="noreferrer" style="display:none;">Load original</a>
<button class="lb-btn lb-info-btn" id="lb-info-btn" aria-label="Toggle info" aria-pressed="false">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.4"/>
<rect x="7.2" y="6.8" width="1.6" height="5.2" rx="0.8" fill="currentColor"/>
<circle cx="8" cy="4.4" r="0.9" fill="currentColor"/>
</svg>
</button>
<button class="lb-btn lb-close-btn" id="lb-close" aria-label="Close">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
</svg>
</button>
</div>
<!-- Main stage: image + sliding meta panel -->
<div class="lb-stage" id="lb-stage">
<button class="lb-nav lb-prev" id="lb-prev" aria-label="Previous">‹</button>
<div class="lb-img-wrap">
<img class="lb-img" id="lb-img" src="" alt="">
</div>
<button class="lb-nav lb-next" id="lb-next" aria-label="Next">›</button>
<!-- Metadata panel — slides in from right, beside the image -->
<aside class="lb-meta-panel" id="lb-meta-panel" aria-label="Photo information">
<div class="lb-meta-scroll">
<p class="lb-meta-title" id="lb-meta-title"></p>
<div id="lb-meta-list"></div>
</div>
</aside>
</div>
<div class="lb-counter" id="lb-counter"></div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
{% if photos|length > 0 %}
<script>
(function () {
const tiles = Array.from(document.querySelectorAll('.tile'));
const lb = document.getElementById('lb');
const lbStage = document.getElementById('lb-stage');
const lbImg = document.getElementById('lb-img');
const lbPrev = document.getElementById('lb-prev');
const lbNext = document.getElementById('lb-next');
const lbClose = document.getElementById('lb-close');
const lbOrig = document.getElementById('lb-orig');
const lbCount = document.getElementById('lb-counter');
const lbPanel = document.getElementById('lb-meta-panel');
const lbInfo = document.getElementById('lb-info-btn');
const lbTitle = document.getElementById('lb-meta-title');
const lbList = document.getElementById('lb-meta-list');
let current = 0;
let metaOpen = false;
const FIELDS = [
{ label: 'Camera', key: 'camera' },
{ label: 'Lens', key: 'lens' },
{ label: 'Focal Length', key: 'focal' },
{ label: 'Aperture', key: 'aperture' },
{ label: 'Shutter Speed', key: 'shutter' },
{ label: 'ISO', key: 'iso' },
{ label: 'Date', key: 'date' },
];
function openAt(idx) {
current = idx;
lb.classList.add('lb-open');
document.body.style.overflow = 'hidden';
render();
}
function close() {
lb.classList.remove('lb-open');
document.body.style.overflow = '';
setMeta(false);
}
function setMeta(open) {
metaOpen = open;
lbPanel.classList.toggle('meta-open', open);
lbStage.classList.toggle('stage-meta-open', open);
lbInfo.setAttribute('aria-pressed', open ? 'true' : 'false');
lbInfo.classList.toggle('lb-info-active', open);
}
function render() {
const tile = tiles[current];
const total = tiles.length;
lbImg.classList.add('lb-loading');
const preload = new Image();
preload.onload = () => { lbImg.src = preload.src; lbImg.classList.remove('lb-loading'); };
preload.src = tile.dataset.src;
lbImg.alt = tile.dataset.title || '';
lbCount.textContent = `${current + 1} / ${total}`;
lbPrev.disabled = current === 0;
lbNext.disabled = current === total - 1;
lbPrev.classList.toggle('lb-nav-off', current === 0);
lbNext.classList.toggle('lb-nav-off', current === total - 1);
const origUrl = tile.dataset.original;
lbOrig.style.display = origUrl ? '' : 'none';
if (origUrl) lbOrig.href = origUrl;
const title = tile.dataset.title;
lbTitle.textContent = title || '';
lbTitle.style.display = title ? 'block' : 'none';
const rows = FIELDS
.filter(f => tile.dataset[f.key])
.map(f => `<div class="meta-row"><span class="meta-label">${f.label}</span><span class="meta-value">${tile.dataset[f.key]}</span></div>`)
.join('');
lbList.innerHTML = rows || '<span class="meta-empty">No metadata available.</span>';
const hasAnyMeta = title || FIELDS.some(f => tile.dataset[f.key]);
lbInfo.style.display = hasAnyMeta ? 'flex' : 'none';
if (!hasAnyMeta) setMeta(false);
}
tiles.forEach(t => t.addEventListener('click', () => openAt(+t.dataset.index)));
lbPrev.addEventListener('click', e => { e.stopPropagation(); if (current > 0) { current--; render(); } });
lbNext.addEventListener('click', e => { e.stopPropagation(); if (current < tiles.length - 1) { current++; render(); } });
lbClose.addEventListener('click', close);
lb.addEventListener('click', e => { if (e.target === lb) close(); });
lbInfo.addEventListener('click', e => { e.stopPropagation(); setMeta(!metaOpen); });
lbPanel.addEventListener('click', e => e.stopPropagation());
document.addEventListener('keydown', e => {
if (!lb.classList.contains('lb-open')) return;
if (e.key === 'Escape') close();
if (e.key === 'ArrowLeft' && current > 0) { current--; render(); }
if (e.key === 'ArrowRight' && current < tiles.length - 1) { current++; render(); }
});
})();
</script>
{% endif %}
{% endblock %}