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
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>
|