You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
13 KiB
Plaintext

---
import { Image } from 'astro:assets'
// Import all JPG images from the assets/images folder
const images = import.meta.glob('@/assets/images/*.jpg', { eager: true })
const imageList = Object.entries(images).map(([path, module]) => {
const imageModule = module as { default: ImageMetadata }
const fileName = path.split('/').pop() || 'unknown'
return {
src: imageModule.default,
name: fileName.replace('.jpg', '').replace('_cropped', ''),
}
})
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/frigo_icon.png" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<!-- OpenGraph Tags -->
<meta property="og:title" content="Meme AulaStud" />
<meta
property="og:description"
content="Galleria con gli scan dei meme appesi sulle pareti dell'AulaStud di Matematica dell'Università di Pisa."
/>
<meta property="og:image" content="/frigo_icon.png" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://meme.phc.dm.unipi.it" />
<title>Meme AulaStud</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, sans-serif;
background: #334;
}
.page-header {
background: #334;
text-align: center;
padding: 2rem 1rem 1rem;
z-index: 100;
}
.page-title {
color: white;
font-size: 2.5rem;
font-weight: bold;
margin: 0;
text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
}
@media (max-width: 768px) {
.page-title {
font-size: 2rem;
}
.page-header {
padding: 1.5rem 1rem 0.5rem;
}
}
@media (max-width: 480px) {
.page-title {
font-size: 1.5rem;
}
}
.masonry-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-template-rows: repeat(auto, 10px);
grid-auto-flow: row dense;
padding: 0.25rem;
gap: 0.25rem;
width: 100vw;
padding-bottom: 30vh;
justify-content: center;
}
.masonry-item {
cursor: pointer;
transition: opacity 0.2s ease;
overflow: hidden;
grid-row-end: span var(--img-height);
}
.masonry-item:hover {
opacity: 0.8;
}
.masonry-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Modal styles */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.95);
display: none;
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal.active {
display: grid;
grid-template-rows: 1fr auto;
padding: 2rem 1rem;
}
.modal-content {
position: relative;
/* max-width: 90vw; */
max-height: none;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.modal img {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
}
.modal-info {
color: white;
text-align: center;
margin-top: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.modal-title {
font-size: 1.2rem;
font-family: monospace;
}
.modal-actions {
position: static;
transform: none;
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
align-self: end;
justify-self: center;
}
.modal-actions-row {
display: flex;
gap: 1rem;
justify-content: center;
}
.btn {
padding: 0.5rem 1rem;
background: white;
color: black;
border: none;
cursor: pointer;
font-family: inherit;
text-decoration: none;
display: inline-block;
transition: background 0.2s ease;
}
.btn:hover {
background: #f0f0f0;
}
#imageModal {
padding: 4rem 0;
}
@media (max-width: 1400px) {
.masonry-grid {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
}
@media (max-width: 1000px) {
.masonry-grid {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
@media (max-width: 768px) {
.masonry-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
}
@media (max-width: 480px) {
.masonry-grid {
grid-template-columns: 1fr 1fr;
gap: 2px;
padding: 2px;
}
/* Mobile-specific adjustments */
.modal img {
max-height: 70vh;
}
}
</style>
</head>
<body>
<header class="page-header">
<h1 class="page-title">Meme AulaStud</h1>
</header>
<!-- Modal -->
<div class="modal" id="imageModal">
<div class="modal-content">
<img id="modalImage" src="" alt="" />
<div class="modal-info">
<div class="modal-title" id="modalTitle"></div>
</div>
</div>
<div class="modal-actions">
<div class="modal-actions-row">
<a id="downloadBtn" class="btn" download>Download</a>
<button class="btn" onclick="closeModal()">Close</button>
</div>
<div class="modal-actions-row">
<button class="btn" onclick="previousImage()">Previous</button>
<button class="btn" onclick="nextImage()">Next</button>
</div>
</div>
</div>
<div class="masonry-grid">
{
imageList.map((image, index) => {
// Calculate the height span based on image aspect ratio
// Assuming 250px base width (from minmax), calculate proportional height
const aspectRatio = image.src.width / image.src.height
const baseWidth = 300
const calculatedHeight = Math.ceil(baseWidth / aspectRatio + 2) // +2 for gap
return (
<div
class="masonry-item"
data-index={index}
data-src={image.src.src}
data-name={image.name}
style={`--img-height: ${Math.floor(calculatedHeight / 10)}`}
>
<Image src={image.src} alt={image.name} format="webp" quality={90} loading="lazy" />
</div>
)
})
}
</div>
<script is:inline>
let currentImageIndex = 0
let allItems = []
function openModal(src, name) {
const modal = document.getElementById('imageModal')
const modalImage = document.getElementById('modalImage')
const modalTitle = document.getElementById('modalTitle')
const downloadBtn = document.getElementById('downloadBtn')
if (modal && modalImage && modalTitle && downloadBtn) {
modalImage.src = src
modalImage.alt = name
modalTitle.textContent = name
downloadBtn.href = src
downloadBtn.download = name + '.jpg'
modal.classList.add('active')
document.body.style.overflow = 'hidden'
}
}
function closeModal() {
const modal = document.getElementById('imageModal')
const modalImage = document.getElementById('modalImage')
if (modal) {
modal.classList.remove('active')
document.body.style.overflow = 'auto'
// Remove the modal image element directly
if (modalImage) {
modalImage.remove()
// Create a new modal image element for next use
const newModalImage = document.createElement('img')
newModalImage.id = 'modalImage'
newModalImage.src = ''
newModalImage.alt = ''
// Insert it back into the modal content
const modalContent = modal.querySelector('.modal-content')
const modalInfo = modal.querySelector('.modal-info')
if (modalContent && modalInfo) {
modalContent.insertBefore(newModalImage, modalInfo)
}
}
}
}
function previousImage() {
if (allItems.length === 0) return
currentImageIndex = (currentImageIndex - 1 + allItems.length) % allItems.length
const item = allItems[currentImageIndex]
const src = item.dataset.src
const name = item.dataset.name
if (src && name) {
openModal(src, name)
}
}
function nextImage() {
if (allItems.length === 0) return
currentImageIndex = (currentImageIndex + 1) % allItems.length
const item = allItems[currentImageIndex]
const src = item.dataset.src
const name = item.dataset.name
if (src && name) {
openModal(src, name)
}
}
// Add click listeners to all masonry items
document.addEventListener('DOMContentLoaded', function () {
const items = document.querySelectorAll('.masonry-item')
allItems = Array.from(items)
// Set up click listeners
items.forEach(function (item, index) {
item.addEventListener('click', function () {
const src = item.dataset.src
const name = item.dataset.name
if (src && name) {
currentImageIndex = index
openModal(src, name)
}
})
})
// Close modal on background click
const modal = document.getElementById('imageModal')
if (modal) {
modal.addEventListener('click', function (e) {
if (e.target === modal) {
closeModal()
}
})
}
// Close modal on Escape key and add navigation
document.addEventListener('keydown', function (e) {
const modal = document.getElementById('imageModal')
const isModalOpen = modal && modal.classList.contains('active')
if (!isModalOpen) return
if (e.key === 'Escape') {
closeModal()
} else if (e.key === 'ArrowLeft') {
e.preventDefault()
previousImage()
} else if (e.key === 'ArrowRight') {
e.preventDefault()
nextImage()
}
})
})
</script>
</body>
</html>