</main>
</div>
<footer class="site-footer">
<div class="page-shell" style="text-align: center;">
<div>Theres no time like 7pm dinner time.</div>
</div>
</footer>
<div class="image-modal-backdrop" data-image-modal>
<img src="" alt="">
</div>
<div class="upload-interactions-modal-backdrop" data-upload-interactions-modal style="align-items:center; justify-content:center;">
<div style="width: min(860px, 100%); max-height: 86vh; overflow:auto; border-radius: 18px; background: var(--bg-elevated); border: 1px solid rgba(255,255,255,0.18); box-shadow: 0 30px 100px rgba(0,0,0,0.55); padding: 18px 18px; position: relative;">
<div data-upload-interactions-body>
<!-- Server-rendered HTML goes here -->
</div>
<button
type="button"
class="pill"
data-close-upload-interactions
style="position: absolute; top: 12px; right: 12px; z-index: 1; font-size: 11px; padding: 6px 10px;"
aria-label="Close comments modal"
>
X
</button>
</div>
</div>
<script>
(function () {
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
return;
}
document.addEventListener('DOMContentLoaded', function () {
var initialCards = document.querySelectorAll('[data-animate-initial]');
var laterCards = document.querySelectorAll('[data-animate]');
initialCards.forEach(function (el) {
el.classList.add('fade-in-1');
});
laterCards.forEach(function (el) {
el.classList.add('fade-in-2');
});
var modal = document.querySelector('[data-image-modal]');
var modalImg = modal ? modal.querySelector('img') : null;
if (!modal || !modalImg) return;
function openModal(src) {
modalImg.src = src;
modal.classList.add('is-open');
}
function closeModal() {
modal.classList.remove('is-open');
modalImg.src = '';
}
document.addEventListener('click', function (event) {
var trigger = event.target.closest('[data-image-full]');
if (trigger && trigger.getAttribute('data-image-full')) {
event.preventDefault();
openModal(trigger.getAttribute('data-image-full'));
} else if (modal.classList.contains('is-open') && (event.target === modal || !modal.contains(event.target))) {
closeModal();
}
});
document.addEventListener('keyup', function (event) {
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
closeModal();
}
});
});
})();
</script>
<style>
.upload-interactions-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.72);
display: none;
z-index: 4200;
padding: 24px;
}
.upload-interactions-modal-backdrop.is-open {
display: flex;
}
</style>
<script>
(function () {
document.addEventListener('DOMContentLoaded', function () {
var backdrop = document.querySelector('[data-upload-interactions-modal]');
var body = backdrop ? backdrop.querySelector('[data-upload-interactions-body]') : null;
if (!backdrop || !body) return;
// No header/footer inside the modal; closing is handled via backdrop click and Escape.
function openModal() {
backdrop.classList.add('is-open');
}
function closeModal() {
backdrop.classList.remove('is-open');
}
var closeBtn = backdrop.querySelector('[data-close-upload-interactions]');
if (closeBtn) {
closeBtn.addEventListener('click', function (e) {
e.preventDefault();
closeModal();
});
}
function setBodyHtml(html) {
body.innerHTML = html;
// Re-bind forms inside the modal to refresh modal body via fetch.
var commentForms = body.querySelectorAll('form.upload-comment-form');
commentForms.forEach(function (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
submitFormForBody(form);
});
});
}
function submitFormForBody(form) {
var formData = new FormData(form);
var action = form.getAttribute('action') || '';
fetch(action, {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (r) { return r.text(); })
.then(function (html) { setBodyHtml(html); })
.catch(function () { /* leave existing content */ });
}
function loadInteractions(uploadId, opts) {
opts = opts || {};
openModal();
body.innerHTML = '<div class="muted" style="padding: 14px 0;">Loading…</div>';
var urlBase = '<?= addslashes(url('api/upload-interactions')) ?>';
var fullUrl = urlBase + '?upload_id=' + encodeURIComponent(uploadId);
var method = opts.reaction ? 'POST' : 'GET';
if (method === 'GET') {
fetch(fullUrl, {
method: 'GET',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (r) { return r.text(); })
.then(function (html) {
setBodyHtml(html);
if (opts.openComments) {
scrollToComments();
}
})
.catch(function () {
body.innerHTML = '<div style="color:#9b2c2c; font-size:13px;">Failed to load discussion.</div>';
});
return;
}
// POST reaction, then reload modal body via server-rendered HTML.
var fd = new FormData();
fd.append('upload_id', uploadId);
fd.append('reaction', opts.reaction);
fetch(urlBase, {
method: 'POST',
body: fd,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (r) { return r.text(); })
.then(function (html) {
setBodyHtml(html);
if (opts.openComments) {
scrollToComments();
}
})
.catch(function () {
body.innerHTML = '<div style="color:#9b2c2c; font-size:13px;">Failed to load discussion.</div>';
});
}
document.addEventListener('click', function (event) {
var trigger = event.target.closest('[data-open-upload-interactions]');
if (trigger) {
var uploadId = trigger.getAttribute('data-upload-id') || '';
if (!uploadId) return;
var reaction = trigger.getAttribute('data-reaction');
var openComments = trigger.getAttribute('data-open-comments');
// If this is a reaction click from the main page, do async update and do NOT open modal.
if (reaction) {
event.preventDefault();
var urlBase = '<?= addslashes(url('api/upload-interactions')) ?>';
var fd = new FormData();
fd.append('upload_id', uploadId);
fd.append('reaction', reaction);
fetch(urlBase, {
method: 'POST',
body: fd,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (r) { return r.json(); })
.then(function (data) {
// Update only the clicked card's like/dislike button labels.
var card = trigger.closest('figure');
if (!card) return;
var likeBtn = card.querySelector('[data-reaction="like"]');
var dislikeBtn = card.querySelector('[data-reaction="dislike"]');
if (likeBtn) likeBtn.textContent = '▲ ' + (data.like_count || 0);
if (dislikeBtn) dislikeBtn.textContent = '▼ ' + (data.dislike_count || 0);
})
.catch(function () { /* ignore */ });
return;
}
loadInteractions(uploadId, {
reaction: reaction ? reaction : null,
openComments: Boolean(openComments)
});
} else if (backdrop.classList.contains('is-open') && (event.target === backdrop)) {
closeModal();
}
});
function scrollToComments() {
// Scroll inside the modal container to the comments block.
var commentsHeading = body.querySelector('.upload-interactions-comments-heading');
if (!commentsHeading) return;
commentsHeading.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
document.addEventListener('keyup', function (event) {
if (event.key === 'Escape' && backdrop.classList.contains('is-open')) {
closeModal();
}
});
});
})();
</script>
</body>
</html>