Clicking a gallery image now opens a dedicated viewer instead of dumping you on the raw file. - GET /b/<name>/view?f=<img> — full-viewport viewer (registered before the file catch-all so /view wins; non-image f 307-redirects to the raw file, traversal and missing f 404). - Fit (downscale-only) / 1:1 (natural pixels, scroll-to-pan) toggle that only appears when the image is larger than the viewport — when it already fits, Fit ≡ 1:1 so the toggle is hidden. Re-evaluates on resize. - Download button + ✕/Esc back to the gallery. Australis-themed, progressive JS (degrades to fit-only, no-JS still shows the image + download + back). - 5 new tests (34 total, all green); verified Fit/1:1/hidden-toggle states in a real browser.
58 lines
2.2 KiB
HTML
58 lines
2.2 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ file }} · {{ name }} · The Booth{% endblock %}
|
|
{% block content %}
|
|
<div class="viewer">
|
|
<div class="vbar">
|
|
<a class="vbtn vx" href="/b/{{ name_url }}/" title="back to gallery (Esc)">✕</a>
|
|
<span class="vname">{{ file }}</span>
|
|
<span class="vspacer"></span>
|
|
<span class="vtoggle" id="vtoggle" style="display:none">
|
|
<button type="button" class="vseg on" id="btn-fit">Fit</button><button type="button" class="vseg" id="btn-one">1:1</button>
|
|
</span>
|
|
<a class="vbtn" href="{{ file_url }}" download title="download {{ file }}">⬇</a>
|
|
</div>
|
|
<div class="vstage fit" id="vstage"><img id="vimg" src="{{ file_url }}" alt="{{ file }}"></div>
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
var img = document.getElementById('vimg');
|
|
var stage = document.getElementById('vstage');
|
|
var toggle = document.getElementById('vtoggle');
|
|
var bFit = document.getElementById('btn-fit');
|
|
var bOne = document.getElementById('btn-one');
|
|
var BACK = {{ ('/b/' ~ name_url ~ '/')|tojson }};
|
|
|
|
function setMode(mode) {
|
|
var fit = mode === 'fit';
|
|
stage.classList.toggle('fit', fit);
|
|
stage.classList.toggle('one', !fit);
|
|
bFit.classList.toggle('on', fit);
|
|
bOne.classList.toggle('on', !fit);
|
|
}
|
|
// "fits" == the image at natural size already sits inside the stage, so Fit
|
|
// and 1:1 would render identically — in that case we hide the toggle entirely.
|
|
function fits() {
|
|
return img.naturalWidth <= stage.clientWidth && img.naturalHeight <= stage.clientHeight;
|
|
}
|
|
function evaluate() {
|
|
if (!img.naturalWidth) return;
|
|
if (fits()) {
|
|
toggle.style.display = 'none';
|
|
setMode('fit');
|
|
} else {
|
|
toggle.style.display = 'inline-flex';
|
|
if (!stage.classList.contains('one')) setMode('fit');
|
|
}
|
|
}
|
|
bFit.addEventListener('click', function () { setMode('fit'); });
|
|
bOne.addEventListener('click', function () { setMode('one'); });
|
|
img.addEventListener('load', evaluate);
|
|
window.addEventListener('resize', evaluate);
|
|
if (img.complete) evaluate();
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape') window.location.href = BACK;
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|