<div class="projects-grid">
<div class="project-card" data-project-id="project1">
<img src="https://picsum.photos/800/400" alt="Project 1" />
</div>
<div class="project-card" data-project-id="project2">
<img src="https://picsum.photos/800/500" alt="Project 2" />
</div>
</div>
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
padding: 20px;
}
.project-card {
position: relative;
cursor: pointer;
}
.project-card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.75);
opacity: 0;
pointer-events: none;
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 1000;
}
.modal-overlay.active {
opacity: 1;
pointer-events: auto;
}
.modal-content {
position: fixed;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
width: 90%;
height: 80vh;
background: white;
transform: translateY(100%);
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
z-index: 1001;
display: flex;
flex-direction: row;
overflow: hidden;
border-radius: 20px 20px 0 0;
}
.modal-content.active {
transform: translateY(0);
}
.modal-content.expanded {
width: 100%;
height: 100vh;
border-radius: 0;
}
.modal-sidebar {
width: 250px;
padding: 20px;
border-right: 1px solid #eee;
background: white;
height: 100%;
overflow-y: auto;
flex-shrink: 0;
display: block; /* Make sidebar always visible */
}
.modal-main {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
height: 100%;
overflow: hidden; /* Prevent double scroll */
margin: 0 auto;
max-width: 1200px;
transition: inherit;
}
.modal-content.expanded .modal-main {
max-width: none;
}
.modal-header {
height: 70px;
padding: 20px;
background: white;
border-bottom: 1px solid #eee;
z-index: 2;
flex-shrink: 0;
}
.modal-body {
flex: 1;
overflow-y: scroll;
padding: 20px;
height: calc(100% - 70px); /* Account for header height */
scroll-behavior: smooth;
}
.sidebar-section {
margin-bottom: 20px;
}
.sidebar-section h3 {
margin-bottom: 10px;
}
.modal-image {
width: 100%;
height: auto;
margin: 20px 0;
border-radius: 8px;
}
.modal-text {
font-size: 16px;
line-height: 1.6;
margin: 20px 0;
}
.modal-close {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
font-size: 24px;
background: white;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
// State variables
const originalURL = window.location.pathname;
// Create modal DOM element
function createModal(projectId) {
const modal = document.createElement("div");
// CMS DATA- Simulated content with text
modal.innerHTML = `
<div class="modal-overlay">
<div class="modal-content">
<div class="modal-sidebar">
<div class="sidebar-section">
<h3>Project Info</h3>
<p>Created: Jan 1, 2024</p>
<p>Category: Design</p>
<p>Status: Active</p>
</div>
<div class="sidebar-section">
<h3>Technologies</h3>
<ul>
<li>HTML/CSS</li>
<li>JavaScript</li>
<li>React</li>
</ul>
</div>
<div class="sidebar-section">
<h3>Team</h3>
<ul>
<li>John Doe - Lead</li>
<li>Jane Smith - Design</li>
</ul>
</div>
</div>
<div class="modal-main">
<div class="modal-header">
<span class="modal-close">×</span>
<h2>${projectId}</h2>
</div>
<div class="modal-body">
<div class="loading">Loading project details...</div>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
// Improved scroll handler
const content = modal.querySelector(".modal-content");
const modalOverlay = modal.querySelector(".modal-overlay");
let lastScrollY = window.scrollY;
let scrollTimer = null;
// Handle scroll events on both window and modal content
const handleScroll = (e) => {
if (scrollTimer !== null) {
clearTimeout(scrollTimer);
}
const delta = e.deltaY || window.scrollY - lastScrollY;
lastScrollY = window.scrollY;
scrollTimer = setTimeout(() => {
if (delta > 0 && !content.classList.contains("expanded")) {
content.classList.add("expanded");
} else if (
delta < 0 &&
content.classList.contains("expanded") &&
window.scrollY <= 0
) {
content.classList.remove("expanded");
}
}, 20);
};
// Add wheel event listener to the entire modal
modalOverlay.addEventListener("wheel", handleScroll, { passive: true });
// Initialize modal
requestAnimationFrame(() => {
modalOverlay.classList.add("active");
content.classList.add("active");
});
return modal;
}
// Load project content
async function loadProjectContent(projectId) {
try {
const response = await fetch(`/api/projects/${projectId}`);
const data = await response.json();
return data;
} catch (error) {
// CMS CONTENT - Simulated content with images and text
return {
title: "Project Title",
description: "Project description...",
content: `
<img class="modal-image" src="https://picsum.photos/800/400" alt="Project image 1">
<p class="modal-text">Detailed project description paragraph 1...</p>
<img class="modal-image" src="https://picsum.photos/800/600" alt="Project image 2">
<p class="modal-text">Detailed project description paragraph 2...</p>
<img class="modal-image" src="https://picsum.photos/800/500" alt="Project image 3">
<p class="modal-text">Detailed project description paragraph 3...</p>
`
};
}
}
// Close modal
function closeModal(modal) {
const overlay = modal.querySelector(".modal-overlay");
const content = modal.querySelector(".modal-content");
content.style.transform = "translateY(100%)";
overlay.style.opacity = "0";
setTimeout(() => {
modal.remove();
history.pushState(null, "", originalURL);
}, 300);
}
// Initialize event listeners
function initializeModalHandlers() {
// Handle project clicks
document.querySelectorAll(".project-card").forEach((card) => {
card.addEventListener("click", async (e) => {
e.preventDefault();
const projectId = card.dataset.projectId;
// display changeg URL
history.pushState({ modal: true, projectId }, "", `/projects/${projectId}`);
const modal = createModal(projectId);
const content = await loadProjectContent(projectId);
modal.querySelector(".modal-body").innerHTML = `
<h3>${content.title}</h3>
<p>${content.description}</p>
${content.content}
`;
modal
.querySelector(".modal-close")
.addEventListener("click", () => closeModal(modal));
});
});
// Handle browser back/forward
window.addEventListener("popstate", (event) => {
const modal = document.querySelector(".modal-overlay")?.parentElement;
if (!event.state?.modal && modal) {
closeModal(modal);
}
});
}
// Initialize
initializeModalHandlers();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.