Designing a visually appealing and functional user interface can be challenging, but grids make it easier. They provide structure and balance, ensuring a seamless user experience.
In this article, we'll explore 10 grid examples that showcase the versatility and power of grid layouts. Whether you're a seasoned designer or just starting, these examples will inspire your next project.
CODE1
Here's the code:
CODETEXT1
CODE2
Here's the code:
CODETEXT2
CODE3
Here's the code:
CODETEXT3
CODE4
Here's the code:
CODETEXT4
CODE5
Here's the code:
CODETEXT5
Subframe's drag-and-drop interface and intuitive, responsive canvas make it effortless to design pixel-perfect UI every time. Loved by designers and developers alike, it transforms your creative vision into reality with ease.
Ready to elevate your design game? Start for free today!
CODE6
Here's the code:
CODETEXT6
CODE7
Here's the code:
CODETEXT7
CODE8
Here's the code:
CODETEXT8
CODE9
Here's the code:
CODETEXT9
CODE10
Here's the code:
CODETEXT10
Unlock the power of Subframe to design stunning, pixel-perfect UIs with unmatched efficiency. Whether you're crafting intricate grids or any other layout, Subframe's intuitive tools make it a breeze.
Ready to bring your creative visions to life? Start for free and begin creating immediately!
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Premium Kitchenware Catalog</title> <style> :root { --primary-color: #2A3B4C; --accent-color: #E67E22; --light-accent: #F9E3CD; --text-color: #333; --light-color: #f8f9fa; --shadow-color: rgba(0, 0, 0, 0.1); --card-radius: 12px; --transition-speed: 0.3s; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f0f2f5; color: var(--text-color); overflow-x: hidden; padding: 20px; height: 100%; width: 100%; display: flex; flex-direction: column; align-items: center; } .container { max-width: 660px; margin: 0 auto; padding: 10px; } header { text-align: center; margin-bottom: 25px; position: relative; } h1 { font-size: 28px; font-weight: 700; color: var(--primary-color); margin-bottom: 10px; position: relative; display: inline-block; } h1::after { content: ''; position: absolute; bottom: -5px; left: 50%; transform: translateX(-50%); width: 70px; height: 3px; background-color: var(--accent-color); border-radius: 2px; } .filters { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; } .search-box { position: relative; flex-grow: 1; margin-right: 15px; } .search-box input { width: 100%; padding: 10px 15px; border-radius: 30px; border: 1px solid #ddd; outline: none; font-size: 14px; transition: all var(--transition-speed); padding-left: 40px; } .search-box input:focus { border-color: var(--accent-color); box-shadow: 0 0 0 2px var(--light-accent); } .search-box i { position: absolute; left: 15px; top: 50%; transform: translateY(-50%); color: #999; } .sort-dropdown { position: relative; min-width: 120px; } .sort-dropdown select { appearance: none; padding: 10px 30px 10px 15px; border-radius: 30px; border: 1px solid #ddd; background-color: white; cursor: pointer; font-size: 14px; outline: none; transition: all var(--transition-speed); } .sort-dropdown select:focus { border-color: var(--accent-color); box-shadow: 0 0 0 2px var(--light-accent); } .sort-dropdown::after { content: '▼'; font-size: 10px; position: absolute; right: 15px; top: 50%; transform: translateY(-50%); pointer-events: none; color: #999; } .product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; width: 100%; } .product-card { background-color: white; border-radius: var(--card-radius); overflow: hidden; box-shadow: 0 2px 10px var(--shadow-color); transition: transform var(--transition-speed), box-shadow var(--transition-speed); position: relative; display: flex; flex-direction: column; height: 100%; cursor: pointer; } .product-card:hover { transform: translateY(-8px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); } .product-card:hover .product-img::before { opacity: 0.3; } .product-card:hover .price { transform: translateY(0); opacity: 1; } .product-img { position: relative; padding-bottom: 100%; overflow: hidden; } .product-img img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .product-card:hover .product-img img { transform: scale(1.1); } .product-img::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.2)); opacity: 0; transition: opacity var(--transition-speed); z-index: 1; } .product-details { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; position: relative; } .product-title { font-size: 16px; font-weight: 600; color: var(--primary-color); margin-bottom: 5px; line-height: 1.2; } .product-description { font-size: 13px; color: #777; margin-bottom: 8px; line-height: 1.3; flex-grow: 1; } .price-rating { display: flex; justify-content: space-between; align-items: center; margin-top: auto; } .price { font-weight: 700; color: var(--accent-color); font-size: 18px; background-color: var(--light-accent); padding: 5px 10px; border-radius: 20px; position: relative; z-index: 2; transform: translateY(10px); opacity: 0.9; transition: transform var(--transition-speed), opacity var(--transition-speed); } .rating { display: flex; align-items: center; } .stars { color: #FFD700; letter-spacing: -2px; font-size: 14px; } .rating-count { font-size: 12px; color: #999; margin-left: 5px; } .badge { position: absolute; top: 10px; left: 10px; background-color: var(--accent-color); color: white; padding: 5px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; z-index: 2; } .add-to-cart { position: absolute; bottom: 15px; right: 15px; background-color: var(--primary-color); color: white; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0; transform: scale(0.8); transition: all var(--transition-speed); z-index: 2; } .product-card:hover .add-to-cart { opacity: 1; transform: scale(1); } .add-to-cart:hover { background-color: var(--accent-color); } .add-to-cart i { font-size: 16px; } .cart-notification { position: fixed; top: 20px; right: 20px; background-color: var(--primary-color); color: white; padding: 10px 20px; border-radius: 30px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); display: flex; align-items: center; transform: translateX(150%); transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 1000; } .cart-notification.show { transform: translateX(0); } .cart-notification i { margin-right: 10px; font-size: 18px; } @media (max-width: 600px) { .product-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; } .filters { flex-direction: column; align-items: stretch; } .search-box { margin-right: 0; margin-bottom: 10px; } .sort-dropdown { width: 100%; } .sort-dropdown select { width: 100%; } h1 { font-size: 24px; } .product-title { font-size: 14px; } .product-description { font-size: 12px; } .price { font-size: 16px; } } /* Pulse animation for add to cart */ @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(230, 126, 34, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(230, 126, 34, 0); } 100% { box-shadow: 0 0 0 0 rgba(230, 126, 34, 0); } } .add-to-cart.pulse { animation: pulse 1.5s infinite; } /* Shimmer effect for loading */ .shimmer { animation: shimmer 2s infinite linear; background: linear-gradient(to right, #f0f0f0 4%, #e0e0e0 25%, #f0f0f0 36%); background-size: 1000px 100%; } @keyframes shimmer { 0% { background-position: -1000px 0; } 100% { background-position: 1000px 0; } } #loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255, 255, 255, 0.9); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 1000; transition: opacity 0.5s ease; } .spinner { width: 40px; height: 40px; border-radius: 50%; border: 4px solid var(--light-accent); border-top-color: var(--accent-color); animation: spin 1s infinite linear; margin-bottom: 20px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Fancy scrollbar */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } ::-webkit-scrollbar-thumb { background: var(--accent-color); border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: #d35400; } /* Empty state */ .empty-state { display: none; text-align: center; padding: 30px; color: #999; } .empty-state i { font-size: 50px; margin-bottom: 15px; color: #ddd; } </style> </head> <body> <div id="loading-overlay"> <div class="spinner"></div> <p>Loading premium kitchenware...</p> </div> <div class="container"> <header> <h1>Artisanal Kitchenware</h1> </header> <div class="filters"> <div class="search-box"> <i>🔍</i> <input type="text" id="search-input" placeholder="Search kitchenware..."> </div> <div class="sort-dropdown"> <select id="sort-select"> <option value="featured">Featured</option> <option value="price-low">Price: Low to High</option> <option value="price-high">Price: High to Low</option> <option value="rating">Top Rated</option> </select> </div> </div> <div class="product-grid" id="product-grid"> <!-- Products will be loaded here --> </div> <div class="empty-state" id="empty-state"> <i>🔍</i> <h3>No products found</h3> <p>Try adjusting your search or filters</p> </div> </div> <div class="cart-notification" id="cart-notification"> <i>🛒</i> <span>Added to cart!</span> </div> <script> const products = [ { id: 1, title: "Artisan Ceramic Mug", description: "Hand-crafted ceramic mug with natural clay glaze and ergonomic handle", price: 24.95, image: "https://images.unsplash.com/photo-1566701829247-1dbe2638beba?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", rating: 4.8, reviews: 124, badge: "Handmade" }, { id: 2, title: "Scandinavian Chef Knife", description: "Carbon steel blade with walnut handle, perfect for precision cutting", price: 79.90, image: "https://images.unsplash.com/photo-1566385101042-1a0aa0c1268c?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", rating: 4.9, reviews: 87, badge: "Pro Grade" }, { id: 3, title: "Bamboo Cutting Board", description: "Sustainable bamboo with juice groove and non-slip feet", price: 34.50, image: "https://images.unsplash.com/photo-1590794056226-79ef3a8147e1?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", rating: 4.6, reviews: 213, badge: "Eco-Friendly" }, { id: 4, title: "Cast Iron Skillet", description: "Pre-seasoned 10-inch skillet with excellent heat retention", price: 49.95, image: "https://images.unsplash.com/photo-1542297709-a35c3fb1892a?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", rating: 4.9, reviews: 342, badge: null }, { id: 5, title: "Copper Measuring Set", description: "Precision copper measuring cups and spoons with engraved markings", price: 42.00, image: "https://images.unsplash.com/photo-1590166774851-bc49b23a18fe?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", rating: 4.7, reviews: 68, badge: "Bestseller" }, { id: 6, title: "Marble Mortar & Pestle", description: "Italian marble with unpolished interior for effective grinding", price: 38.75, image: "https://images.unsplash.com/photo-1590794056154-1b5a370a683a?ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80", rating: 4.5, reviews: 92, badge: null } ]; document.addEventListener('DOMContentLoaded', function() { setTimeout(() => { document.getElementById('loading-overlay').style.opacity = 0; setTimeout(() => { document.getElementById('loading-overlay').style.display = 'none'; }, 500); }, 1000); renderProducts(products); // Event listeners document.getElementById('search-input').addEventListener('input', filterProducts); document.getElementById('sort-select').addEventListener('change', filterProducts); }); function renderProducts(productsToRender) { const grid = document.getElementById('product-grid'); const emptyState = document.getElementById('empty-state'); // Clear the grid grid.innerHTML = ''; if (productsToRender.length === 0) { grid.style.display = 'none'; emptyState.style.display = 'block'; return; } grid.style.display = 'grid'; emptyState.style.display = 'none'; productsToRender.forEach(product => { const card = document.createElement('div'); card.className = 'product-card'; card.dataset.id = product.id; const stars = '★'.repeat(Math.floor(product.rating)) + (product.rating % 1 >= 0.5 ? '½' : '') + '☆'.repeat(5 - Math.ceil(product.rating)); card.innerHTML = ` <div class="product-img"> <img src="${product.image}" alt="${product.title}"> ${product.badge ? `<div class="badge">${product.badge}</div>` : ''} </div> <div class="product-details"> <h3 class="product-title">${product.title}</h3> <p class="product-description">${product.description}</p> <div class="price-rating"> <div class="price">$${product.price.toFixed(2)}</div> <div class="rating"> <span class="stars">${stars}</span> <span class="rating-count">(${product.reviews})</span> </div> </div> <div class="add-to-cart" title="Add to cart">+</div> </div> `; grid.appendChild(card); // Add event listener to the add to cart button const addToCartBtn = card.querySelector('.add-to-cart'); addToCartBtn.addEventListener('click', function(e) { e.stopPropagation(); addToCart(product); this.classList.add('pulse'); setTimeout(() => { this.classList.remove('pulse'); }, 1500); }); // Add event listener to the card for quick view card.addEventListener('click', function() { quickView(product); }); }); } function filterProducts() { const searchValue = document.getElementById('search-input').value.toLowerCase(); const sortValue = document.getElementById('sort-select').value; let filtered = products.filter(product => { return product.title.toLowerCase().includes(searchValue) || product.description.toLowerCase().includes(searchValue); }); switch(sortValue) { case 'price-low': filtered.sort((a, b) => a.price - b.price); break; case 'price-high': filtered.sort((a, b) => b.price - a.price); break; case 'rating': filtered.sort((a, b) => b.rating - a.rating); break; // Default is 'featured' - no sorting needed } renderProducts(filtered); } function addToCart(product) { // Show notification const notification = document.getElementById('cart-notification'); notification.classList.add('show'); // Hide notification after 3 seconds setTimeout(() => { notification.classList.remove('show'); }, 3000); // In a real app, this would add the product to a cart console.log(`Added to cart: ${product.title}`); } function quickView(product) { // In a real app, this would open a modal with product details console.log(`Quick view: ${product.title}`); } </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>News Mosaic Grid</title> <style> :root { --primary: #1a2e35; --secondary: #ff6b6b; --accent: #4ecdc4; --light: #f7f9f9; --medium: #e0e5e9; --dark: #30444e; --shadow: rgba(0, 0, 0, 0.1); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Helvetica Neue', Arial, sans-serif; } body { background-color: var(--light); color: var(--primary); line-height: 1.6; overflow-x: hidden; padding: 20px; max-width: 100%; max-height: 100vh; } .container { max-width: 700px; margin: 0 auto; overflow-y: auto; max-height: 660px; scrollbar-width: thin; scrollbar-color: var(--accent) var(--light); } .container::-webkit-scrollbar { width: 6px; } .container::-webkit-scrollbar-track { background: var(--light); } .container::-webkit-scrollbar-thumb { background-color: var(--accent); border-radius: 10px; } header { margin-bottom: 20px; position: sticky; top: 0; z-index: 100; background-color: var(--light); padding: 15px 0; border-bottom: 2px solid var(--medium); } h1 { color: var(--primary); font-size: 28px; position: relative; display: inline-block; } h1::after { content: ''; position: absolute; width: 30%; height: 3px; background-color: var(--secondary); bottom: -5px; left: 0; transition: width 0.3s ease; } h1:hover::after { width: 100%; } .filter-bar { display: flex; gap: 15px; margin-top: 10px; overflow-x: auto; padding-bottom: 5px; -ms-overflow-style: none; scrollbar-width: none; } .filter-bar::-webkit-scrollbar { display: none; } .filter-btn { padding: 8px 15px; background: transparent; border: 1px solid var(--medium); border-radius: 50px; color: var(--dark); cursor: pointer; transition: all 0.2s ease; white-space: nowrap; font-size: 14px; } .filter-btn.active, .filter-btn:hover { background-color: var(--accent); color: white; border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 4px 8px var(--shadow); } .news-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 20px; margin-top: 20px; } .news-card { background-color: white; border-radius: 12px; overflow: hidden; box-shadow: 0 8px 16px var(--shadow); transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); position: relative; display: flex; flex-direction: column; } .news-card:hover { transform: translateY(-8px); box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); } .news-card.featured { grid-column: span 12; grid-row: span 2; min-height: 300px; } .news-card.medium { grid-column: span 6; min-height: 240px; } .news-card.small { grid-column: span 4; min-height: 200px; } @media (max-width: 600px) { .news-card.medium, .news-card.small { grid-column: span 12; } } .card-image { height: 55%; overflow: hidden; position: relative; } .featured .card-image { height: 65%; } .card-image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.6s ease; } .news-card:hover .card-image img { transform: scale(1.05); } .card-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to bottom, rgba(0,0,0,0) 50%, rgba(0,0,0,0.7) 100%); } .card-category { position: absolute; top: 15px; left: 15px; padding: 5px 10px; background-color: var(--secondary); color: white; border-radius: 4px; font-size: 12px; font-weight: bold; text-transform: uppercase; z-index: 2; transition: transform 0.3s ease; } .news-card:hover .card-category { transform: translateY(-2px); } .card-content { padding: 20px; display: flex; flex-direction: column; flex-grow: 1; position: relative; } .card-date { color: var(--dark); font-size: 12px; margin-bottom: 8px; display: flex; align-items: center; } .card-date::before { content: ''; display: inline-block; width: 8px; height: 8px; background-color: var(--accent); border-radius: 50%; margin-right: 8px; } .card-title { font-size: 18px; color: var(--primary); margin-bottom: 10px; transition: color 0.3s ease; line-height: 1.3; } .featured .card-title { font-size: 24px; } .news-card:hover .card-title { color: var(--accent); } .card-excerpt { font-size: 14px; color: var(--dark); margin-bottom: 15px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; } .card-footer { margin-top: auto; display: flex; justify-content: space-between; align-items: center; } .card-author { display: flex; align-items: center; font-size: 13px; color: var(--dark); } .author-avatar { width: 24px; height: 24px; border-radius: 50%; margin-right: 8px; object-fit: cover; } .read-more { color: var(--accent); font-size: 14px; font-weight: 600; text-decoration: none; display: flex; align-items: center; transition: transform 0.3s ease; } .read-more svg { margin-left: 6px; transition: transform 0.3s ease; } .news-card:hover .read-more { transform: translateX(5px); } .news-card:hover .read-more svg { transform: translateX(3px); } .scroll-indicator { position: fixed; bottom: 20px; right: 20px; width: 40px; height: 40px; background-color: var(--accent); border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; transform: translateY(20px); z-index: 100; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); } .scroll-indicator.visible { opacity: 1; transform: translateY(0); } .scroll-indicator svg { fill: white; width: 20px; height: 20px; } .loading-bar { position: fixed; top: 0; left: 0; width: 0%; height: 3px; background: linear-gradient(to right, var(--accent), var(--secondary)); z-index: 1000; transition: width 0.2s ease-out; } /* Animation for cards appearing */ @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .news-card { animation: fadeInUp 0.5s ease-out forwards; opacity: 0; } .news-card:nth-child(1) { animation-delay: 0.1s; } .news-card:nth-child(2) { animation-delay: 0.2s; } .news-card:nth-child(3) { animation-delay: 0.3s; } .news-card:nth-child(4) { animation-delay: 0.4s; } .news-card:nth-child(5) { animation-delay: 0.5s; } .news-card:nth-child(6) { animation-delay: 0.6s; } .news-card:nth-child(7) { animation-delay: 0.7s; } </style> </head> <body> <div class="loading-bar" id="loading-bar"></div> <div class="container"> <header> <h1>TrendMosaic</h1> <div class="filter-bar"> <button class="filter-btn active">All</button> <button class="filter-btn">Technology</button> <button class="filter-btn">Climate</button> <button class="filter-btn">Business</button> <button class="filter-btn">Health</button> <button class="filter-btn">Culture</button> </div> </header> <div class="news-grid"> <article class="news-card featured"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1526304640581-d334cdbbf45e?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="AI integration in healthcare"> <div class="card-overlay"></div> <span class="card-category">Technology</span> </div> <div class="card-content"> <div class="card-date">June 15, 2023</div> <h2 class="card-title">AI Revolution in Healthcare: Predictive Analytics Saving Lives</h2> <p class="card-excerpt">Hospitals are seeing a 40% increase in early diagnosis rates with AI-powered imaging tools. New systems can identify potential health issues up to 18 months before traditional methods, creating a paradigm shift in preventative care.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/women/44.jpg" alt="Emma Chen" class="author-avatar"> <span>Emma Chen</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> <article class="news-card medium"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1617791160505-6f00504e3519?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Ocean plastic cleanup"> <div class="card-overlay"></div> <span class="card-category">Climate</span> </div> <div class="card-content"> <div class="card-date">June 12, 2023</div> <h2 class="card-title">Ocean Cleanup Breakthrough: Microplastic Filtration System Deployed</h2> <p class="card-excerpt">Engineers have launched a solar-powered filtration system that can remove particles as small as 10 micrometers from ocean water, tackling the invisible threat of microplastics.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/men/32.jpg" alt="Marcus Rivera" class="author-avatar"> <span>Marcus Rivera</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> <article class="news-card medium"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1590283603385-17ffb3a7f29f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Cryptocurrency regulation"> <div class="card-overlay"></div> <span class="card-category">Business</span> </div> <div class="card-content"> <div class="card-date">June 10, 2023</div> <h2 class="card-title">Global Framework for Crypto Regulation Takes Shape</h2> <p class="card-excerpt">Twenty-seven countries have agreed to a unified approach to cryptocurrency oversight, creating a standardized regulatory environment that could facilitate mainstream adoption.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/women/68.jpg" alt="Sophia Kim" class="author-avatar"> <span>Sophia Kim</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> <article class="news-card small"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1505751172876-fa1923c5c528?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Mental health app"> <div class="card-overlay"></div> <span class="card-category">Health</span> </div> <div class="card-content"> <div class="card-date">June 8, 2023</div> <h2 class="card-title">Mental Health App Uses Voice Analysis to Detect Depression</h2> <p class="card-excerpt">New smartphone application analyzes speech patterns to identify early signs of depression with 89% accuracy in clinical trials.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/men/52.jpg" alt="David Johnson" class="author-avatar"> <span>David Johnson</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> <article class="news-card small"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1501426026826-31c667bdf23d?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Virtual reality theater"> <div class="card-overlay"></div> <span class="card-category">Culture</span> </div> <div class="card-content"> <div class="card-date">June 5, 2023</div> <h2 class="card-title">First Virtual Reality Theater Opens with Immersive Performances</h2> <p class="card-excerpt">A revolutionary theater concept allows audiences to experience productions from any character's perspective through VR headsets.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/women/23.jpg" alt="Leila Ndiaye" class="author-avatar"> <span>Leila Ndiaye</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> <article class="news-card small"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1581091870591-01d77a21ca1f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Sustainable building"> <div class="card-overlay"></div> <span class="card-category">Climate</span> </div> <div class="card-content"> <div class="card-date">June 3, 2023</div> <h2 class="card-title">Carbon-Negative Buildings: The Future of Urban Architecture</h2> <p class="card-excerpt">New construction techniques combine mycelium insulation and carbon-capturing concrete to create buildings that remove more CO2 than they emit.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/men/78.jpg" alt="Raj Patel" class="author-avatar"> <span>Raj Patel</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> <article class="news-card medium"> <div class="card-image"> <img src="https://images.unsplash.com/photo-1518770660439-4636190af475?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Quantum computing"> <div class="card-overlay"></div> <span class="card-category">Technology</span> </div> <div class="card-content"> <div class="card-date">June 1, 2023</div> <h2 class="card-title">Quantum Computing Milestone: Error Correction Breakthrough</h2> <p class="card-excerpt">Scientists have developed a quantum error correction method that maintains qubit coherence for over 5 minutes, potentially solving one of quantum computing's biggest challenges.</p> <div class="card-footer"> <div class="card-author"> <img src="https://randomuser.me/api/portraits/women/12.jpg" alt="Ana Rodriguez" class="author-avatar"> <span>Ana Rodriguez</span> </div> <a href="#" class="read-more"> Read more <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="5" y1="12" x2="19" y2="12"></line> <polyline points="12 5 19 12 12 19"></polyline> </svg> </a> </div> </div> </article> </div> </div> <div class="scroll-indicator" id="scroll-top"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.828l-4.95 4.95-1.414-1.414L12 8l6.364 6.364-1.414 1.414z"/></svg> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Filter buttons interaction const filterButtons = document.querySelectorAll('.filter-btn'); filterButtons.forEach(button => { button.addEventListener('click', function() { // Remove active class from all buttons filterButtons.forEach(btn => btn.classList.remove('active')); // Add active class to clicked button this.classList.add('active'); // Show loading effect animateLoading(); }); }); // Scroll indicator functionality const scrollIndicator = document.getElementById('scroll-top'); const container = document.querySelector('.container'); container.addEventListener('scroll', function() { if (container.scrollTop > 300) { scrollIndicator.classList.add('visible'); } else { scrollIndicator.classList.remove('visible'); } }); scrollIndicator.addEventListener('click', function() { container.scrollTo({ top: 0, behavior: 'smooth' }); }); // Loading bar animation function animateLoading() { const loadingBar = document.getElementById('loading-bar'); loadingBar.style.width = '0%'; // Start animation setTimeout(() => { loadingBar.style.width = '30%'; }, 100); setTimeout(() => { loadingBar.style.width = '60%'; }, 300); setTimeout(() => { loadingBar.style.width = '80%'; }, 600); setTimeout(() => { loadingBar.style.width = '100%'; }, 900); // Reset after complete setTimeout(() => { loadingBar.style.width = '0%'; }, 1200); } // Card hover effects enhancement const cards = document.querySelectorAll('.news-card'); cards.forEach(card => { card.addEventListener('mouseenter', function() { const otherCards = Array.from(cards).filter(c => c !== card); otherCards.forEach(c => { c.style.opacity = '0.7'; c.style.transform = 'scale(0.98)'; }); }); card.addEventListener('mouseleave', function() { cards.forEach(c => { c.style.opacity = '1'; c.style.transform = ''; }); }); }); // Initialize with loading animation animateLoading(); }); </script> </body> </html>
<html> <head> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; } :root { --primary: #0F172A; --secondary: #475569; --accent: #4F46E5; --light: #F8FAFC; --overlay: rgba(15, 23, 42, 0.7); --transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); } body { background-color: var(--light); color: var(--primary); width: 100%; height: 100vh; overflow: hidden; } .portfolio-container { width: 100%; height: 100%; padding: 20px; overflow: auto; position: relative; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; position: sticky; top: 0; background-color: var(--light); padding: 15px 0; z-index: 10; } .logo { font-weight: 800; font-size: 1.8rem; letter-spacing: -0.03em; color: var(--primary); position: relative; } .logo::after { content: ''; position: absolute; width: 6px; height: 6px; background-color: var(--accent); border-radius: 50%; bottom: 4px; right: -8px; } .filters { display: flex; gap: 15px; } .filter-btn { border: none; background: none; padding: 8px 15px; font-size: 0.9rem; color: var(--secondary); border-radius: 20px; cursor: pointer; transition: var(--transition); } .filter-btn:hover, .filter-btn.active { background-color: var(--accent); color: white; } .grid { display: grid; grid-template-columns: repeat(12, 1fr); grid-auto-rows: minmax(100px, auto); gap: 20px; margin-bottom: 80px; } .project { cursor: pointer; position: relative; border-radius: 12px; overflow: hidden; transition: var(--transition); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); transform: translateY(0); } .project:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12); } .project-1 { grid-column: 1 / 7; grid-row: 1 / 4; } .project-2 { grid-column: 7 / 13; grid-row: 1 / 3; } .project-3 { grid-column: 7 / 10; grid-row: 3 / 5; } .project-4 { grid-column: 10 / 13; grid-row: 3 / 5; } .project-5 { grid-column: 1 / 5; grid-row: 4 / 6; } .project-6 { grid-column: 5 / 13; grid-row: 5 / 7; } .project-img { width: 100%; height: 100%; object-fit: cover; transition: var(--transition); } .project-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--overlay); opacity: 0; display: flex; flex-direction: column; justify-content: flex-end; padding: 25px; color: white; transition: var(--transition); } .project:hover .project-overlay { opacity: 1; } .project:hover .project-img { transform: scale(1.05); } .project-category { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; color: rgba(255, 255, 255, 0.8); transform: translateY(20px); opacity: 0; transition: var(--transition); transition-delay: 0.1s; } .project:hover .project-category { transform: translateY(0); opacity: 1; } .project-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 12px; transform: translateY(20px); opacity: 0; transition: var(--transition); transition-delay: 0.2s; } .project:hover .project-title { transform: translateY(0); opacity: 1; } .project-desc { font-size: 0.9rem; line-height: 1.5; margin-bottom: 15px; transform: translateY(20px); opacity: 0; transition: var(--transition); transition-delay: 0.3s; } .project:hover .project-desc { transform: translateY(0); opacity: 1; } .view-btn { align-self: flex-start; background-color: var(--accent); color: white; border: none; padding: 8px 15px; border-radius: 5px; font-size: 0.9rem; cursor: pointer; transform: translateY(20px); opacity: 0; transition: var(--transition); transition-delay: 0.4s; } .project:hover .view-btn { transform: translateY(0); opacity: 1; } .view-btn:hover { background-color: #4338ca; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); display: flex; justify-content: center; align-items: center; z-index: 100; opacity: 0; pointer-events: none; transition: var(--transition); } .modal.active { opacity: 1; pointer-events: all; } .modal-content { width: 90%; max-width: 650px; max-height: 90vh; overflow-y: auto; background-color: white; border-radius: 15px; padding: 30px; transform: translateY(30px); transition: var(--transition); } .modal.active .modal-content { transform: translateY(0); } .modal-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; } .modal-title { font-size: 1.8rem; font-weight: 700; color: var(--primary); } .close-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: var(--secondary); transition: var(--transition); } .close-btn:hover { color: var(--primary); transform: rotate(90deg); } .modal-image { width: 100%; border-radius: 10px; margin-bottom: 20px; } .modal-category { display: inline-block; padding: 5px 10px; background-color: var(--accent); color: white; border-radius: 4px; font-size: 0.8rem; margin-bottom: 15px; } .modal-desc { color: var(--secondary); line-height: 1.6; margin-bottom: 20px; } .modal-details { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 25px; } .detail-item { display: flex; flex-direction: column; } .detail-label { font-size: 0.8rem; color: var(--secondary); margin-bottom: 5px; } .detail-value { font-weight: 600; color: var(--primary); } .cursor { position: fixed; width: 40px; height: 40px; border: 2px solid var(--accent); border-radius: 50%; pointer-events: none; transform: translate(-50%, -50%); transition: transform 0.1s ease; z-index: 1000; opacity: 0; } @media (max-width: 900px) { .grid { grid-template-columns: repeat(6, 1fr); } .project-1 { grid-column: 1 / 7; grid-row: auto; } .project-2 { grid-column: 1 / 7; grid-row: auto; } .project-3 { grid-column: 1 / 4; grid-row: auto; } .project-4 { grid-column: 4 / 7; grid-row: auto; } .project-5 { grid-column: 1 / 7; grid-row: auto; } .project-6 { grid-column: 1 / 7; grid-row: auto; } } @media (max-width: 600px) { .header { flex-direction: column; align-items: flex-start; gap: 15px; } .filters { width: 100%; overflow-x: auto; padding-bottom: 10px; } .filter-btn { white-space: nowrap; } .grid { grid-template-columns: 1fr; } .project-1, .project-2, .project-3, .project-4, .project-5, .project-6 { grid-column: 1 / 2; grid-row: auto; } .modal-details { grid-template-columns: 1fr; } } </style> </head> <body> <div class="cursor"></div> <div class="portfolio-container"> <header class="header"> <div class="logo">portgrid</div> <div class="filters"> <button class="filter-btn active" data-filter="all">All Projects</button> <button class="filter-btn" data-filter="web">Web Design</button> <button class="filter-btn" data-filter="mobile">Mobile App</button> <button class="filter-btn" data-filter="print">Print Design</button> <button class="filter-btn" data-filter="identity">Brand Identity</button> </div> </header> <div class="grid"> <div class="project project-1" data-category="web"> <img src="https://images.unsplash.com/photo-1558655146-364adaf1fcc9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=900&q=80" alt="Eco-Tourism Platform" class="project-img"> <div class="project-overlay"> <div class="project-category">Web Design</div> <h3 class="project-title">Eco-Tourism Platform</h3> <p class="project-desc">An immersive booking platform that connects travelers with sustainable tourism experiences in over 80 countries.</p> <button class="view-btn">View Project</button> </div> </div> <div class="project project-2" data-category="mobile"> <img src="https://images.unsplash.com/photo-1605236453806-6ff36851218e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=900&q=80" alt="Fitness Tracker App" class="project-img"> <div class="project-overlay"> <div class="project-category">Mobile App</div> <h3 class="project-title">Fitness Tracker App</h3> <p class="project-desc">A personalized wellness companion that adapts training programs based on real-time biometric feedback.</p> <button class="view-btn">View Project</button> </div> </div> <div class="project project-3" data-category="identity"> <img src="https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=900&q=80" alt="Artisan Coffee Branding" class="project-img"> <div class="project-overlay"> <div class="project-category">Brand Identity</div> <h3 class="project-title">Artisan Coffee Branding</h3> <p class="project-desc">Visual identity system for a specialty coffee roaster with direct trade partnerships in Ethiopia.</p> <button class="view-btn">View Project</button> </div> </div> <div class="project project-4" data-category="print"> <img src="https://images.unsplash.com/photo-1594909122845-11baa439b7bf?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=900&q=80" alt="Literary Journal" class="project-img"> <div class="project-overlay"> <div class="project-category">Print Design</div> <h3 class="project-title">Literary Journal</h3> <p class="project-desc">Award-winning quarterly publication featuring experimental typography and emerging writers.</p> <button class="view-btn">View Project</button> </div> </div> <div class="project project-5" data-category="web"> <img src="https://images.unsplash.com/photo-1555421689-3f034debb7a6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=900&q=80" alt="Analytics Dashboard" class="project-img"> <div class="project-overlay"> <div class="project-category">Web Design</div> <h3 class="project-title">Analytics Dashboard</h3> <p class="project-desc">Real-time data visualization interface that transforms complex metrics into actionable business insights.</p> <button class="view-btn">View Project</button> </div> </div> <div class="project project-6" data-category="mobile"> <img src="https://images.unsplash.com/photo-1508739773434-c26b3d09e071?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=900&q=80" alt="Travel Planning App" class="project-img"> <div class="project-overlay"> <div class="project-category">Mobile App</div> <h3 class="project-title">Travel Planning App</h3> <p class="project-desc">AI-powered trip planner that creates personalized itineraries based on travel preferences and local insights.</p> <button class="view-btn">View Project</button> </div> </div> </div> </div> <div class="modal"> <div class="modal-content"> <div class="modal-header"> <h2 class="modal-title">Project Title</h2> <button class="close-btn">×</button> </div> <img src="" alt="" class="modal-image"> <span class="modal-category">Category</span> <p class="modal-desc">Project description will appear here.</p> <div class="modal-details"> <div class="detail-item"> <span class="detail-label">Client</span> <span class="detail-value">Client Name</span> </div> <div class="detail-item"> <span class="detail-label">Timeline</span> <span class="detail-value">Timeline</span> </div> <div class="detail-item"> <span class="detail-label">Role</span> <span class="detail-value">Role</span> </div> <div class="detail-item"> <span class="detail-label">Tools</span> <span class="detail-value">Tools</span> </div> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Custom cursor const cursor = document.querySelector('.cursor'); if (window.innerWidth > 768) { // Only on desktop document.addEventListener('mousemove', (e) => { cursor.style.opacity = '1'; cursor.style.left = e.clientX + 'px'; cursor.style.top = e.clientY + 'px'; }); document.addEventListener('mouseout', () => { cursor.style.opacity = '0'; }); const projectElements = document.querySelectorAll('.project, .filter-btn, .close-btn, .view-btn'); projectElements.forEach(element => { element.addEventListener('mouseenter', () => { cursor.style.transform = 'translate(-50%, -50%) scale(1.5)'; cursor.style.borderColor = 'rgba(79, 70, 229, 0.3)'; cursor.style.backgroundColor = 'rgba(79, 70, 229, 0.1)'; }); element.addEventListener('mouseleave', () => { cursor.style.transform = 'translate(-50%, -50%) scale(1)'; cursor.style.borderColor = 'var(--accent)'; cursor.style.backgroundColor = 'transparent'; }); }); } // Filter functionality const filterButtons = document.querySelectorAll('.filter-btn'); const projects = document.querySelectorAll('.project'); filterButtons.forEach(button => { button.addEventListener('click', () => { // Update active button filterButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); // Filter projects const filter = button.getAttribute('data-filter'); projects.forEach(project => { const category = project.getAttribute('data-category'); if (filter === 'all' || filter === category) { project.style.display = 'block'; setTimeout(() => { project.style.opacity = '1'; project.style.transform = 'translateY(0)'; }, 10); } else { project.style.opacity = '0'; project.style.transform = 'translateY(20px)'; setTimeout(() => { project.style.display = 'none'; }, 400); } }); }); }); // Modal functionality const modal = document.querySelector('.modal'); const closeBtn = document.querySelector('.close-btn'); const viewButtons = document.querySelectorAll('.view-btn'); const projectData = [ { title: "Eco-Tourism Platform", category: "Web Design", description: "A comprehensive booking platform that connects environmentally conscious travelers with verified sustainable tourism experiences across 80+ countries. The design emphasizes immersive imagery and transparent impact metrics, allowing users to see the direct environmental benefits of their travel choices. The platform includes features for carbon footprint calculation, community reviews, and local guide connections.", client: "GreenTravel Collective", timeline: "Jan 2023 - May 2023", role: "Lead UI/UX Designer", tools: "Figma, React, Webflow" }, { title: "Fitness Tracker App", category: "Mobile App", description: "An adaptive fitness application that creates personalized workout regimens based on real-time biometric data and user goals. The app features a machine learning system that adjusts training intensity based on performance patterns, recovery metrics, and nutritional inputs. The interface uses motion design to guide users through exercises with proper form detection via AR capabilities.", client: "FitTech Innovations", timeline: "Aug 2022 - Dec 2022", role: "Mobile UI Designer, Interaction Designer", tools: "Sketch, Swift, Flutter" }, { title: "Artisan Coffee Branding", category: "Brand Identity", description: "A comprehensive brand identity system for a specialty coffee roaster with direct trade partnerships in Ethiopia's Yirgacheffe region. The visual language draws from traditional Ethiopian coffee ceremonies while incorporating contemporary typographic approaches. The deliverables included packaging design, retail environment concepts, digital presence, and an educational component highlighting sustainable farming practices.", client: "Altitude Coffee Roasters", timeline: "Mar 2022 - Jun 2022", role: "Brand Designer, Packaging Designer", tools: "Illustrator, InDesign, Procreate" }, { title: "Literary Journal", category: "Print Design", description: "An award-winning quarterly literary publication featuring experimental typography and emerging writers from underrepresented communities. Each issue explores a different typographic approach while maintaining readability and narrative coherence. The journal uses specialized printing techniques including risograph for certain sections, creating a tactile experience that complements the literary content.", client: "Liminal Press", timeline: "Ongoing (Quarterly)", role: "Art Director, Typography Designer", tools: "InDesign, Photoshop, After Effects" }, { title: "Analytics Dashboard", category: "Web Design", description: "A comprehensive data visualization interface that transforms complex metrics into actionable business insights for a marketing technology company. The dashboard features modular components that allow users to customize their view based on role and priorities. Special attention was given to accessibility standards while maintaining sophisticated data visualization capabilities that adapt to various screen sizes.", client: "DataVue Technologies", timeline: "Oct 2022 - Feb 2023", role: "UI Designer, Data Visualization Specialist", tools: "Figma, D3.js, Vue.js" }, { title: "Travel Planning App", category: "Mobile App", description: "An AI-powered travel companion that creates personalized itineraries based on user preferences, budget constraints, and local insights. The app features an innovative mapping system that adapts to user movement patterns and learning preferences over time. Special features include augmented reality historical information overlays, offline capability for remote locations, and integration with local transit systems worldwide.", client: "Wayfarer Technologies", timeline: "Apr 2023 - Present", role: "Product Designer, UX Researcher", tools: "Figma, React Native, MapBox" } ]; viewButtons.forEach((button, index) => { button.addEventListener('click', (e) => { e.stopPropagation(); // Populate modal with project data const data = projectData[index]; document.querySelector('.modal-title').textContent = data.title; document.querySelector('.modal-category').textContent = data.category; document.querySelector('.modal-desc').textContent = data.description; // Set details document.querySelectorAll('.detail-value')[0].textContent = data.client; document.querySelectorAll('.detail-value')[1].textContent = data.timeline; document.querySelectorAll('.detail-value')[2].textContent = data.role; document.querySelectorAll('.detail-value')[3].textContent = data.tools; // Set image const projectImg = button.closest('.project').querySelector('img').src; document.querySelector('.modal-image').src = projectImg; document.querySelector('.modal-image').alt = data.title; // Show modal modal.classList.add('active'); document.body.style.overflow = 'hidden'; }); }); closeBtn.addEventListener('click', () => { modal.classList.remove('active'); document.body.style.overflow = 'auto'; }); modal.addEventListener('click', (e) => { if (e.target === modal) { modal.classList.remove('active'); document.body.style.overflow = 'auto'; } }); // Animation on scroll const projectElements = document.querySelectorAll('.project'); function checkScroll() { projectElements.forEach(project => { const projectPosition = project.getBoundingClientRect().top; const screenPosition = window.innerHeight / 1.3; if (projectPosition < screenPosition) { project.style.opacity = '1'; project.style.transform = 'translateY(0)'; } else { project.style.opacity = '0'; project.style.transform = 'translateY(20px)'; } }); } window.addEventListener('scroll', checkScroll); checkScroll(); // Initial check // Add some initial animations setTimeout(() => { document.querySelector('.logo').style.opacity = '1'; document.querySelector('.logo').style.transform = 'translateY(0)'; setTimeout(() => { filterButtons.forEach((btn, index) => { setTimeout(() => { btn.style.opacity = '1'; btn.style.transform = 'translateY(0)'; }, index * 100); }); setTimeout(() => { checkScroll(); }, filterButtons.length * 100 + 300); }, 300); }, 300); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Admin Dashboard Grid</title> <style> :root { --primary: #6366f1; --primary-hover: #4f46e5; --secondary: #f472b6; --success: #10b981; --warning: #f59e0b; --danger: #ef4444; --text-primary: #1f2937; --text-secondary: #6b7280; --bg-primary: #ffffff; --bg-secondary: #f9fafb; --border: #e5e7eb; --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } body { background-color: var(--bg-secondary); color: var(--text-primary); height: 700px; width: 700px; overflow: hidden; display: flex; flex-direction: column; } .dashboard { display: flex; flex-direction: column; height: 100%; background-color: var(--bg-primary); box-shadow: var(--shadow); border-radius: 8px; overflow: hidden; } .dashboard-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border); background-color: #f0f4ff; } .dashboard-title { font-size: 18px; font-weight: 600; color: var(--text-primary); } .dashboard-actions { display: flex; gap: 8px; } .action-button { padding: 8px 12px; background-color: var(--primary); color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; } .action-button:hover { background-color: var(--primary-hover); transform: translateY(-1px); } .action-button svg { width: 16px; height: 16px; } .filters { display: flex; align-items: center; padding: 12px 20px; background-color: var(--bg-secondary); border-bottom: 1px solid var(--border); gap: 12px; } .filter-input { padding: 8px 12px; border: 1px solid var(--border); border-radius: 4px; font-size: 14px; flex: 1; max-width: 240px; transition: all 0.2s ease; } .filter-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); } .filter-select { padding: 8px 12px; border: 1px solid var(--border); border-radius: 4px; font-size: 14px; background-color: white; } .filter-select:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); } .grid-container { flex: 1; overflow: auto; padding: 0; position: relative; } table { width: 100%; border-collapse: collapse; table-layout: fixed; } th { position: sticky; top: 0; background-color: var(--bg-secondary); padding: 12px 16px; text-align: left; font-weight: 600; font-size: 14px; color: var(--text-secondary); border-bottom: 1px solid var(--border); cursor: pointer; transition: background-color 0.15s ease; user-select: none; } th:hover { background-color: #eef2ff; } th.sorted { color: var(--primary); background-color: #eef2ff; } th span { display: flex; align-items: center; gap: 4px; } td { padding: 12px 16px; font-size: 14px; border-bottom: 1px solid var(--border); transition: all 0.15s ease; } tr:hover td { background-color: #f8faff; } tr.active td { background-color: #eef2ff; } .editable { position: relative; cursor: pointer; } .editable::after { content: '✎'; position: absolute; right: 10px; opacity: 0; transition: opacity 0.15s ease; font-size: 12px; color: var(--primary); } tr:hover .editable::after { opacity: 1; } .editable input { width: 100%; padding: 6px; font-size: 14px; border: 1px solid var(--primary); border-radius: 4px; outline: none; } .status { display: inline-flex; align-items: center; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; } .status-paid { background-color: rgba(16, 185, 129, 0.1); color: var(--success); } .status-pending { background-color: rgba(245, 158, 11, 0.1); color: var(--warning); } .status-overdue { background-color: rgba(239, 68, 68, 0.1); color: var(--danger); } .status-draft { background-color: rgba(107, 114, 128, 0.1); color: var(--text-secondary); } .trend { display: flex; align-items: center; gap: 4px; } .trend-up { color: var(--success); } .trend-down { color: var(--danger); } .actions { display: flex; gap: 8px; } .action-icon { padding: 4px; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; color: var(--text-secondary); } .action-icon:hover { background-color: var(--bg-secondary); color: var(--primary); } .spinner-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255, 255, 255, 0.7); display: flex; justify-content: center; align-items: center; z-index: 10; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .spinner-overlay.active { opacity: 1; pointer-events: all; } .spinner { width: 40px; height: 40px; border: 3px solid rgba(99, 102, 241, 0.2); border-radius: 50%; border-top-color: var(--primary); animation: spin 1s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .toast { position: fixed; bottom: 20px; right: 20px; background-color: var(--primary); color: white; padding: 12px 16px; border-radius: 4px; box-shadow: var(--shadow); transform: translateY(100px); opacity: 0; transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); z-index: 100; } .toast.show { transform: translateY(0); opacity: 1; } .toast.success { background-color: var(--success); } .toast.error { background-color: var(--danger); } .no-results { padding: 40px; text-align: center; color: var(--text-secondary); font-size: 16px; } .hidden { display: none; } .dashboard-footer { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; border-top: 1px solid var(--border); background-color: var(--bg-secondary); font-size: 14px; } .pagination { display: flex; align-items: center; gap: 8px; } .page-button { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 4px; background-color: transparent; border: 1px solid var(--border); cursor: pointer; transition: all 0.2s ease; color: var(--text-primary); } .page-button:hover:not(.active-page) { background-color: #eef2ff; border-color: var(--primary); } .page-button.active-page { background-color: var(--primary); color: white; border-color: var(--primary); } .data-summary { color: var(--text-secondary); } /* Pulse animation for cell highlighting */ @keyframes pulse-highlight { 0% { background-color: rgba(99, 102, 241, 0.1); } 50% { background-color: rgba(99, 102, 241, 0.2); } 100% { background-color: rgba(99, 102, 241, 0.1); } } .highlight { animation: pulse-highlight 1.5s ease-in-out; } /* Responsive */ @media (max-width: 700px) { .dashboard-actions { display: none; } .filters { flex-wrap: wrap; } .filter-input, .filter-select { max-width: none; width: 100%; } th, td { padding: 10px 12px; font-size: 13px; } .dashboard-header { padding: 12px 16px; } .action-text { display: none; } } </style> </head> <body> <div class="dashboard"> <div class="dashboard-header"> <h1 class="dashboard-title">E-commerce Performance Metrics</h1> <div class="dashboard-actions"> <button class="action-button" id="exportData"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> </svg> <span class="action-text">Export</span> </button> <button class="action-button" id="refreshData"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> </svg> <span class="action-text">Refresh</span> </button> </div> </div> <div class="filters"> <input type="text" class="filter-input" id="searchInput" placeholder="Search products..."> <select class="filter-select" id="categoryFilter"> <option value="">All Categories</option> <option value="Electronics">Electronics</option> <option value="Clothing">Clothing</option> <option value="Home Goods">Home Goods</option> <option value="Beauty">Beauty</option> </select> <select class="filter-select" id="statusFilter"> <option value="">All Statuses</option> <option value="In Stock">In Stock</option> <option value="Low Stock">Low Stock</option> <option value="Out of Stock">Out of Stock</option> </select> </div> <div class="grid-container"> <table> <thead> <tr> <th data-sort="product"><span>Product <svg class="sort-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M8 10L12 14L16 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg></span></th> <th data-sort="category"><span>Category</span></th> <th data-sort="sales"><span>Sales</span></th> <th data-sort="stock"><span>Stock</span></th> <th data-sort="revenue"><span>Revenue</span></th> <th data-sort="trend"><span>Trend</span></th> <th data-sort="status"><span>Status</span></th> <th><span>Actions</span></th> </tr> </thead> <tbody id="dataGrid"> <!-- Data will be populated by JavaScript --> </tbody> </table> <div class="no-results hidden" id="noResults"> No matching products found. Try adjusting your filters. </div> <div class="spinner-overlay" id="spinnerOverlay"> <div class="spinner"></div> </div> </div> <div class="dashboard-footer"> <div class="data-summary">Showing <span id="visibleCount">0</span> of <span id="totalCount">0</span> products</div> <div class="pagination" id="pagination"> <!-- Pagination will be populated by JavaScript --> </div> </div> </div> <div class="toast" id="toast"></div> <script> // Sample data for the grid const initialData = [ { id: 1, product: "MacBook Pro 16\"", category: "Electronics", sales: 127, stock: 23, revenue: 254000, trend: 12.3, status: "In Stock" }, { id: 2, product: "Premium Wireless Earbuds", category: "Electronics", sales: 348, stock: 112, revenue: 34800, trend: 24.7, status: "In Stock" }, { id: 3, product: "Ultra HD Smart TV", category: "Electronics", sales: 75, stock: 8, revenue: 67500, trend: -3.2, status: "Low Stock" }, { id: 4, product: "Organic Cotton T-Shirt", category: "Clothing", sales: 521, stock: 230, revenue: 15630, trend: 8.1, status: "In Stock" }, { id: 5, product: "Designer Denim Jeans", category: "Clothing", sales: 208, stock: 0, revenue: 24960, trend: -5.8, status: "Out of Stock" }, { id: 6, product: "Ergonomic Office Chair", category: "Home Goods", sales: 92, stock: 15, revenue: 18400, trend: 14.5, status: "In Stock" }, { id: 7, product: "Luxury Scented Candle", category: "Home Goods", sales: 184, stock: 42, revenue: 5520, trend: 2.3, status: "In Stock" }, { id: 8, product: "Professional Hair Dryer", category: "Beauty", sales: 127, stock: 4, revenue: 12700, trend: -1.7, status: "Low Stock" }, { id: 9, product: "Anti-Aging Face Serum", category: "Beauty", sales: 312, stock: 0, revenue: 21840, trend: 18.9, status: "Out of Stock" }, { id: 10, product: "Wireless Charging Pad", category: "Electronics", sales: 256, stock: 87, revenue: 7680, trend: 10.4, status: "In Stock" }, { id: 11, product: "Fitness Tracking Watch", category: "Electronics", sales: 164, stock: 29, revenue: 24600, trend: 15.7, status: "In Stock" }, { id: 12, product: "Leather Crossbody Bag", category: "Clothing", sales: 78, stock: 13, revenue: 11700, trend: -2.4, status: "Low Stock" } ]; // Store the data and state let gridData = [...initialData]; let currentPage = 1; const rowsPerPage = 7; let sortField = 'sales'; let sortDirection = 'desc'; let activeEditCell = null; // DOM elements const dataGrid = document.getElementById('dataGrid'); const searchInput = document.getElementById('searchInput'); const categoryFilter = document.getElementById('categoryFilter'); const statusFilter = document.getElementById('statusFilter'); const spinnerOverlay = document.getElementById('spinnerOverlay'); const refreshButton = document.getElementById('refreshData'); const exportButton = document.getElementById('exportData'); const toast = document.getElementById('toast'); const pagination = document.getElementById('pagination'); const visibleCount = document.getElementById('visibleCount'); const totalCount = document.getElementById('totalCount'); const noResults = document.getElementById('noResults'); // Initialize the grid renderGrid(); updatePagination(); // Event listeners searchInput.addEventListener('input', debounce(filterData, 300)); categoryFilter.addEventListener('change', filterData); statusFilter.addEventListener('change', filterData); document.querySelectorAll('th[data-sort]').forEach(th => { th.addEventListener('click', () => { const field = th.dataset.sort; if (sortField === field) { sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'; } else { sortField = field; sortDirection = 'desc'; } renderGrid(); updateSortIndicators(); }); }); refreshButton.addEventListener('click', refreshData); exportButton.addEventListener('click', exportGridData); document.addEventListener('click', (e) => { if (activeEditCell && !e.target.closest('.editable')) { finishEditing(); } }); // Functions function renderGrid() { // Apply sorting const sortedData = [...gridData].sort((a, b) => { if (sortDirection === 'asc') { return a[sortField] > b[sortField] ? 1 : -1; } else { return a[sortField] < b[sortField] ? 1 : -1; } }); // Apply pagination const startIndex = (currentPage - 1) * rowsPerPage; const paginatedData = sortedData.slice(startIndex, startIndex + rowsPerPage); // Update counts visibleCount.textContent = gridData.length; totalCount.textContent = initialData.length; // Check if no results if (gridData.length === 0) { noResults.classList.remove('hidden'); } else { noResults.classList.add('hidden'); } // Clear the table body dataGrid.innerHTML = ''; // Render rows paginatedData.forEach(row => { const tr = document.createElement('tr'); tr.dataset.id = row.id; tr.innerHTML = ` <td class="editable" data-field="product">${row.product}</td> <td>${row.category}</td> <td class="editable" data-field="sales">${row.sales.toLocaleString()}</td> <td class="editable" data-field="stock">${row.stock.toLocaleString()}</td> <td>$${row.revenue.toLocaleString()}</td> <td> <div class="trend ${row.trend >= 0 ? 'trend-up' : 'trend-down'}"> ${row.trend >= 0 ? '↑' : '↓'} ${Math.abs(row.trend)}% </div> </td> <td> <span class="status ${getStatusClass(row.status)}"> ${row.status} </span> </td> <td> <div class="actions"> <div class="action-icon view-details" title="View Details"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> </svg> </div> <div class="action-icon edit-row" title="Edit"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path> <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path> </svg> </div> </div> </td> `; // Add event listeners to editable cells const editableCells = tr.querySelectorAll('.editable'); editableCells.forEach(cell => { cell.addEventListener('click', () => { if (activeEditCell) finishEditing(); startEditing(cell, row.id); }); }); // Add event listeners to action buttons const viewDetailsBtn = tr.querySelector('.view-details'); const editRowBtn = tr.querySelector('.edit-row'); viewDetailsBtn.addEventListener('click', () => { showToast(`Viewing details for ${row.product}`, 'success'); highlightRow(tr); }); editRowBtn.addEventListener('click', () => { showToast(`Editing ${row.product}`, 'success'); highlightRow(tr); }); dataGrid.appendChild(tr); }); updateSortIndicators(); } function getStatusClass(status) { switch (status) { case 'In Stock': return 'status-paid'; case 'Low Stock': return 'status-pending'; case 'Out of Stock': return 'status-overdue'; default: return 'status-draft'; } } function updateSortIndicators() { document.querySelectorAll('th').forEach(th => { th.classList.remove('sorted'); if (th.dataset.sort === sortField) { th.classList.add('sorted'); const svg = th.querySelector('svg'); if (svg) { if (sortDirection === 'asc') { svg.innerHTML = '<path d="M16 14L12 10L8 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; } else { svg.innerHTML = '<path d="M8 10L12 14L16 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'; } } } }); } function filterData() { const searchTerm = searchInput.value.toLowerCase(); const categoryValue = categoryFilter.value; const statusValue = statusFilter.value; gridData = initialData.filter(row => { const matchesSearch = row.product.toLowerCase().includes(searchTerm); const matchesCategory = !categoryValue || row.category === categoryValue; const matchesStatus = !statusValue || row.status === statusValue; return matchesSearch && matchesCategory && matchesStatus; }); currentPage = 1; renderGrid(); updatePagination(); } function updatePagination() { const totalPages = Math.max(1, Math.ceil(gridData.length / rowsPerPage)); pagination.innerHTML = ''; // Previous button const prevButton = document.createElement('button'); prevButton.className = 'page-button'; prevButton.innerHTML = '←'; prevButton.disabled = currentPage === 1; prevButton.addEventListener('click', () => { if (currentPage > 1) { currentPage--; renderGrid(); updatePagination(); } }); pagination.appendChild(prevButton); // Page numbers const maxPagesToShow = 3; const startPage = Math.max(1, currentPage - Math.floor(maxPagesToShow / 2)); const endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); for (let i = startPage; i <= endPage; i++) { const pageButton = document.createElement('button'); pageButton.className = `page-button ${i === currentPage ? 'active-page' : ''}`; pageButton.textContent = i; pageButton.addEventListener('click', () => { currentPage = i; renderGrid(); updatePagination(); }); pagination.appendChild(pageButton); } // Next button const nextButton = document.createElement('button'); nextButton.className = 'page-button'; nextButton.innerHTML = '→'; nextButton.disabled = currentPage === totalPages; nextButton.addEventListener('click', () => { if (currentPage < totalPages) { currentPage++; renderGrid(); updatePagination(); } }); pagination.appendChild(nextButton); } function refreshData() { showLoading(); // Simulate API call setTimeout(() => { // Randomly update some values to simulate fresh data gridData.forEach(row => { const salesChange = Math.floor(Math.random() * 20) - 5; row.sales += salesChange; row.revenue = row.revenue * (1 + (Math.random() * 0.1 - 0.05)); row.trend = +(row.trend + (Math.random() * 4 - 2)).toFixed(1); if (row.stock > 0) { row.stock = Math.max(0, row.stock - Math.floor(Math.random() * 5)); } if (row.stock === 0) { row.status = 'Out of Stock'; } else if (row.stock <= 10) { row.status = 'Low Stock'; } else { row.status = 'In Stock'; } }); hideLoading(); renderGrid(); showToast('Data refreshed successfully', 'success'); }, 800); } function exportGridData() { showLoading(); // Simulate export process setTimeout(() => { hideLoading(); showToast('Data exported successfully', 'success'); }, 800); } function startEditing(cell, rowId) { const field = cell.dataset.field; const row = gridData.find(r => r.id === parseInt(rowId)); const value = row[field]; activeEditCell = { cell, rowId, field, originalValue: value }; const input = document.createElement('input'); input.type = ['sales', 'stock'].includes(field) ? 'number' : 'text'; input.value = value; input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { finishEditing(); } else if (e.key === 'Escape') { cancelEditing(); } }); cell.innerHTML = ''; cell.appendChild(input); input.focus(); input.select(); } function finishEditing() { if (!activeEditCell) return; const { cell, rowId, field, originalValue } = activeEditCell; const input = cell.querySelector('input'); let newValue = input.value; // Process numeric values if (['sales', 'stock'].includes(field)) { newValue = parseInt(newValue) || 0; } // Update the data const row = gridData.find(r => r.id === parseInt(rowId)); const index = initialData.findIndex(r => r.id === parseInt(rowId)); if (row && index !== -1) { row[field] = newValue; initialData[index][field] = newValue; // Update related fields if (field === 'sales' || field === 'stock') { // Update status based on stock levels if (field === 'stock') { if (newValue === 0) { row.status = 'Out of Stock'; initialData[index].status = 'Out of Stock'; } else if (newValue <= 10) { row.status = 'Low Stock'; initialData[index].status = 'Low Stock'; } else { row.status = 'In Stock'; initialData[index].status = 'In Stock'; } } // Update revenue if sales changed if (field === 'sales') { const avgPrice = row.revenue / originalValue; row.revenue = avgPrice * newValue; initialData[index].revenue = row.revenue; } } } // Restore the cell display cell.innerHTML = field === 'sales' || field === 'stock' ? newValue.toLocaleString() : newValue; // Add highlight effect cell.classList.add('highlight'); setTimeout(() => { cell.classList.remove('highlight'); }, 1500); activeEditCell = null; showToast('Data updated successfully', 'success'); } function cancelEditing() { if (!activeEditCell) return; const { cell, originalValue } = activeEditCell; cell.innerHTML = originalValue; activeEditCell = null; } function highlightRow(row) { row.classList.add('active'); setTimeout(() => { row.classList.remove('active'); }, 1500); } function showToast(message, type = '') { toast.textContent = message; toast.className = 'toast show'; if (type) toast.classList.add(type); setTimeout(() => { toast.classList.remove('show'); }, 3000); } function showLoading() { spinnerOverlay.classList.add('active'); } function hideLoading() { spinnerOverlay.classList.remove('active'); } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Social Media Feed Grid</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } :root { --primary: #6c5ce7; --primary-light: #a29bfe; --text-dark: #2d3436; --text-light: #636e72; --background: #f9f9f9; --card-bg: #ffffff; --accent: #fd79a8; --accent-light: #fab1a0; --shadow: 0 10px 30px rgba(0, 0, 0, 0.1); } body { background: var(--background); min-height: 100vh; display: flex; justify-content: center; align-items: center; overflow-x: hidden; padding: 15px; } .container { width: 100%; max-width: 700px; height: 700px; position: relative; overflow: hidden; } .header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: var(--card-bg); border-radius: 15px 15px 0 0; box-shadow: var(--shadow); position: sticky; top: 0; z-index: 10; } .header h1 { color: var(--primary); font-size: 22px; font-weight: 700; background: linear-gradient(135deg, var(--primary), var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .view-toggle { display: flex; gap: 10px; } .view-toggle button { background: none; border: none; font-size: 18px; cursor: pointer; color: var(--text-light); transition: all 0.3s ease; padding: 5px; } .view-toggle button.active { color: var(--primary); transform: scale(1.1); } .feed-container { height: calc(100% - 70px); overflow-y: auto; padding: 10px; scroll-behavior: smooth; background: var(--background); } .feed-container::-webkit-scrollbar { width: 8px; } .feed-container::-webkit-scrollbar-track { background: var(--background); } .feed-container::-webkit-scrollbar-thumb { background: var(--primary-light); border-radius: 10px; } .grid-view { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; padding-bottom: 15px; } .list-view { display: flex; flex-direction: column; gap: 20px; padding-bottom: 15px; } .post { background: var(--card-bg); border-radius: 15px; overflow: hidden; box-shadow: var(--shadow); transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); position: relative; } .post:hover { transform: translateY(-5px); box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15); } .post-img { width: 100%; height: 0; padding-bottom: 100%; position: relative; overflow: hidden; } .list-view .post-img { padding-bottom: 60%; } .post-img img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .post:hover .post-img img { transform: scale(1.05); } .post-content { padding: 15px; } .post-header { display: flex; align-items: center; margin-bottom: 10px; } .post-avatar { width: 32px; height: 32px; border-radius: 50%; overflow: hidden; margin-right: 10px; border: 2px solid var(--primary); } .post-avatar img { width: 100%; height: 100%; object-fit: cover; } .post-info { flex: 1; } .post-author { font-weight: 700; color: var(--text-dark); font-size: 14px; margin: 0; } .post-time { color: var(--text-light); font-size: 12px; } .post-caption { color: var(--text-dark); font-size: 14px; line-height: 1.4; margin-bottom: 12px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .list-view .post-caption { -webkit-line-clamp: 3; } .post-actions { display: flex; justify-content: space-between; align-items: center; } .action-buttons { display: flex; gap: 15px; } .action-button { background: none; border: none; font-size: 18px; cursor: pointer; color: var(--text-light); transition: all 0.3s ease; display: flex; align-items: center; gap: 5px; position: relative; } .action-button span { font-size: 13px; } .action-button.like:hover, .action-button.like.active { color: #e74c3c; } .action-button.comment:hover { color: var(--primary); } .action-button.share:hover { color: var(--accent); } .post-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; } .post-tag { background: var(--primary-light); color: white; font-size: 11px; padding: 3px 8px; border-radius: 12px; font-weight: 500; } .post-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; } .post:hover .post-overlay { opacity: 1; } .post-overlay-content { color: white; text-align: center; } .post-overlay-stats { display: flex; gap: 20px; margin-top: 10px; } .stat { display: flex; flex-direction: column; align-items: center; } .stat-value { font-size: 22px; font-weight: 700; } .stat-label { font-size: 14px; opacity: 0.8; } .reaction-popup { position: absolute; bottom: 100%; left: 0; background: var(--card-bg); padding: 5px; border-radius: 30px; display: flex; gap: 5px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); transform: translateY(10px); opacity: 0; pointer-events: none; transition: all 0.3s ease; } .action-button:hover .reaction-popup { opacity: 1; transform: translateY(0); pointer-events: auto; } .reaction { width: 30px; height: 30px; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: transform 0.2s ease; } .reaction:hover { transform: scale(1.2); } .reaction img { width: 20px; height: 20px; } /* For when a post is liked */ .post.liked .like-icon { color: #e74c3c; animation: likeAnimation 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); } @keyframes likeAnimation { 0% { transform: scale(1); } 50% { transform: scale(1.4); } 100% { transform: scale(1); } } /* Shimmer effect on load */ .shimmer { position: relative; overflow: hidden; } .shimmer::after { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient( 90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 100% ); animation: shimmer 1.5s infinite; } @keyframes shimmer { to { left: 100%; } } /* Hidden state for features toggle */ .hidden { display: none !important; } /* Media queries for responsiveness */ @media (max-width: 500px) { .grid-view { grid-template-columns: 1fr; } .header h1 { font-size: 18px; } } /* New post indicator */ .new-post-indicator { position: absolute; top: 10px; right: 10px; background: var(--accent); color: white; padding: 3px 8px; border-radius: 12px; font-size: 10px; font-weight: 700; z-index: 2; transform: rotate(5deg); } /* Double tap animation */ .double-tap-heart { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0); color: #e74c3c; font-size: 80px; pointer-events: none; z-index: 10; opacity: 0; } .heart-animation { animation: heartBurst 0.8s cubic-bezier(0.1, 0.7, 0.3, 1); } @keyframes heartBurst { 0% { transform: translate(-50%, -50%) scale(0); opacity: 0; } 15% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.9; } 30% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; } 100% { transform: translate(-50%, -50%) scale(2); opacity: 0; } } /* Floating music note for music post */ .floating-notes { position: absolute; top: 5px; right: 5px; z-index: 2; } .music-note { position: absolute; animation: float 3s ease-in-out infinite; color: white; text-shadow: 0 0 3px rgba(0,0,0,0.5); opacity: 0.8; } .music-note:nth-child(1) { animation-delay: 0s; font-size: 16px; } .music-note:nth-child(2) { animation-delay: 1s; font-size: 18px; right: 15px; } .music-note:nth-child(3) { animation-delay: 2s; font-size: 14px; right: 5px; top: 15px; } @keyframes float { 0% { transform: translateY(0) rotate(0deg); opacity: 0.8; } 50% { transform: translateY(-15px) rotate(10deg); opacity: 0.6; } 100% { transform: translateY(-30px) rotate(20deg); opacity: 0; } } /* Video play button */ .video-indicator { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 50px; height: 50px; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; justify-content: center; align-items: center; z-index: 2; cursor: pointer; transition: all 0.3s ease; } .video-indicator::after { content: ''; width: 0; height: 0; border-top: 10px solid transparent; border-bottom: 10px solid transparent; border-left: 15px solid white; margin-left: 3px; } .video-indicator:hover { background: rgba(0,0,0,0.7); transform: translate(-50%, -50%) scale(1.1); } /* Post with multiple images indicator */ .multiple-images { position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.6); color: white; width: 25px; height: 25px; border-radius: 5px; display: flex; justify-content: center; align-items: center; font-size: 12px; z-index: 2; } /* Comment input */ .comment-input { margin-top: 10px; position: relative; display: none; } .comment-field { width: 100%; padding: 8px 40px 8px 10px; border: 1px solid #ddd; border-radius: 20px; font-size: 13px; outline: none; } .comment-submit { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); background: var(--primary); color: white; border: none; width: 30px; height: 30px; border-radius: 50%; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: all 0.3s ease; } .comment-submit:hover { background: var(--primary-light); } /* Filter section */ .filter-section { display: flex; gap: 10px; padding: 10px 0; overflow-x: auto; scrollbar-width: none; margin-bottom: 10px; } .filter-section::-webkit-scrollbar { display: none; } .filter-pill { background: var(--card-bg); color: var(--text-light); border: 1px solid #ddd; padding: 6px 15px; border-radius: 20px; font-size: 13px; white-space: nowrap; cursor: pointer; transition: all 0.3s ease; } .filter-pill:hover, .filter-pill.active { background: var(--primary); color: white; border-color: var(--primary); } </style> </head> <body> <div class="container"> <div class="header"> <h1>Visually Trending</h1> <div class="view-toggle"> <button id="grid-toggle" class="active"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="7" height="7"></rect> <rect x="14" y="3" width="7" height="7"></rect> <rect x="14" y="14" width="7" height="7"></rect> <rect x="3" y="14" width="7" height="7"></rect> </svg> </button> <button id="list-toggle"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="8" y1="6" x2="21" y2="6"></line> <line x1="8" y1="12" x2="21" y2="12"></line> <line x1="8" y1="18" x2="21" y2="18"></line> <line x1="3" y1="6" x2="3" y2="6"></line> <line x1="3" y1="12" x2="3" y2="12"></line> <line x1="3" y1="18" x2="3" y2="18"></line> </svg> </button> </div> </div> <div class="feed-container"> <div class="filter-section"> <div class="filter-pill active">All</div> <div class="filter-pill">Photography</div> <div class="filter-pill">Design</div> <div class="filter-pill">Travel</div> <div class="filter-pill">Food</div> <div class="filter-pill">Music</div> <div class="filter-pill">Art</div> </div> <div id="feed" class="grid-view"> <!-- Post 1 --> <div class="post" data-category="photography"> <div class="post-img"> <img src="https://images.unsplash.com/photo-1501854140801-50d01698950b?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Sunset over mountain range"> <div class="new-post-indicator">NEW</div> <div class="post-overlay"> <div class="post-overlay-content"> <h3>Into the Wild</h3> <div class="post-overlay-stats"> <div class="stat"> <div class="stat-value">1.2k</div> <div class="stat-label">Likes</div> </div> <div class="stat"> <div class="stat-value">86</div> <div class="stat-label">Comments</div> </div> </div> </div> </div> <div class="double-tap-heart">❤️</div> </div> <div class="post-content"> <div class="post-header"> <div class="post-avatar"> <img src="https://randomuser.me/api/portraits/women/44.jpg" alt="User avatar"> </div> <div class="post-info"> <p class="post-author">Alex Morgan</p> <p class="post-time">2 hours ago</p> </div> </div> <p class="post-caption">Chasing light in the Cascade Mountains. The way the sun painted the peaks was absolutely magical! #naturephotography</p> <div class="post-actions"> <div class="action-buttons"> <button class="action-button like"> <svg class="like-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path> </svg> <span>1.2k</span> <div class="reaction-popup"> <div class="reaction"> <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMjAuODQgNC42MWE1LjUgNS41IDAgMCAwLTcuNzggMEwxMiA1LjY3bC0xLjA2LTEuMDZhNS41IDUuNSAwIDAgMC03Ljc4IDcuNzhsMS4wNiAxLjA2TDEyIDIxLjIzbDcuNzgtNy43OCAxLjA2LTEuMDZhNS41IDUuNSAwIDAgMCAwLTcuNzh6IiBmaWxsPSIjZTc0YzNjIi8+PC9zdmc+" alt="Like"> </div> <div class="reaction"> <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIgZmlsbD0iI2ZmZDcwMCIvPjxjaXJjbGUgY3g9IjgiIGN5PSI5IiByPSIxLjUiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSIxNiIgY3k9IjkiIHI9IjEuNSIgZmlsbD0iIzAwMCIvPjxwYXRoIGQ9Ik04LjUgMTVhNS41IDUuNSAwIDAgMCA3IDAiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvc3ZnPg==" alt="Love"> </div> <div class="reaction"> <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCIgZmlsbD0iI2ZmYTUwMCIvPjxjaXJjbGUgY3g9IjgiIGN5PSI5IiByPSIxLjUiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSIxNiIgY3k9IjkiIHI9IjEuNSIgZmlsbD0iIzAwMCIvPjxwYXRoIGQ9Ik03IDEzLjVhNi41IDUgMCAwIDAgMTAgMCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9zdmc+" alt="Wow"> </div> </div> </button> <button class="action-button comment"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path> </svg> <span>86</span> </button> <button class="action-button share"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="18" cy="5" r="3"></circle> <circle cx="6" cy="12" r="3"></circle> <circle cx="18" cy="19" r="3"></circle> <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line> <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line> </svg> </button> </div> </div> <div class="post-tags"> <span class="post-tag">Nature</span> <span class="post-tag">Photography</span> </div> <div class="comment-input"> <input type="text" class="comment-field" placeholder="Add a comment..."> <button class="comment-submit"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="22" y1="2" x2="11" y2="13"></line> <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> </svg> </button> </div> </div> </div> <!-- Post 2 --> <div class="post" data-category="design"> <div class="post-img"> <img src="https://images.unsplash.com/photo-1558655146-d09347e92766?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Colorful abstract design"> <div class="multiple-images">+3</div> <div class="post-overlay"> <div class="post-overlay-content"> <h3>Design Waves</h3> <div class="post-overlay-stats"> <div class="stat"> <div class="stat-value">943</div> <div class="stat-label">Likes</div> </div> <div class="stat"> <div class="stat-value">54</div> <div class="stat-label">Comments</div> </div> </div> </div> </div> <div class="double-tap-heart">❤️</div> </div> <div class="post-content"> <div class="post-header"> <div class="post-avatar"> <img src="https://randomuser.me/api/portraits/men/32.jpg" alt="User avatar"> </div> <div class="post-info"> <p class="post-author">Jamie Chen</p> <p class="post-time">6 hours ago</p> </div> </div> <p class="post-caption">Exploring color theory in my latest project. Each hue tells its own story in this abstract collection. Swipe to see all pieces! #designprocess</p> <div class="post-actions"> <div class="action-buttons"> <button class="action-button like"> <svg class="like-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path> </svg> <span>943</span> </button> <button class="action-button comment"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path> </svg> <span>54</span> </button> <button class="action-button share"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="18" cy="5" r="3"></circle> <circle cx="6" cy="12" r="3"></circle> <circle cx="18" cy="19" r="3"></circle> <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line> <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line> </svg> </button> </div> </div> <div class="post-tags"> <span class="post-tag">Design</span> <span class="post-tag">Abstract</span> </div> <div class="comment-input"> <input type="text" class="comment-field" placeholder="Add a comment..."> <button class="comment-submit"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="22" y1="2" x2="11" y2="13"></line> <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> </svg> </button> </div> </div> </div> <!-- Post 3 --> <div class="post" data-category="music"> <div class="post-img"> <img src="https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80" alt="Music studio setup"> <div class="floating-notes"> <div class="music-note">♪</div> <div class="music-note">♫</div> <div class="music-note">♩</div> </div> <div class="post-overlay"> <div class="post-overlay-content"> <h3>Studio Sessions</h3> <div class="post-overlay-stats"> <div class="stat"> <div class="stat-value">765</div> <div class="stat-label">Likes</div> </div> <div class="stat"> <div class="stat-value">42</div> <div class="stat-label">Comments</div> </div> </div> </div> </div> <div class="double-tap-heart">❤️</div> </div> <div class="post-content"> <div class="post-header"> <div class="post-avatar"> <img src="https://randomuser.me/api/portraits/women/65.jpg" alt="User avatar"> </div> <div class="post-info"> <p class="post-author">Taylor Reed</p> <p class="post-time">Yesterday</p> </div> </div> <p class="post-caption">Late night studio session for the upcoming EP. The creative energy was flowing! Link to the teaser in bio. #musicproduction #newrelease</p> <div class="post-actions"> <div class="action-buttons"> <button class="action-button like"> <svg class="like-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Event Scheduler Calendar Grid</title> <style> :root { --primary: #2c5282; --primary-light: #3182ce; --secondary: #ed64a6; --secondary-light: #f687b3; --bg-light: #f7fafc; --gray-100: #f7fafc; --gray-200: #edf2f7; --gray-300: #e2e8f0; --gray-400: #cbd5e0; --gray-500: #a0aec0; --gray-600: #718096; --gray-700: #4a5568; --gray-800: #2d3748; --text-dark: #1a202c; --text-light: #f7fafc; --success: #48bb78; --warning: #f6ad55; --error: #f56565; --info: #4299e1; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } body { display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: var(--bg-light); color: var(--text-dark); overflow-x: hidden; } .calendar-container { width: 100%; max-width: 700px; height: 700px; display: flex; flex-direction: column; background-color: white; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); overflow: hidden; position: relative; } .calendar-header { padding: 24px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--gray-300); } .calendar-title { font-size: 1.5rem; font-weight: 700; color: var(--primary); } .date-nav { display: flex; align-items: center; gap: 16px; } .date-display { font-size: 1rem; font-weight: 600; white-space: nowrap; } .nav-button { width: 36px; height: 36px; border-radius: 50%; border: none; background-color: var(--gray-200); color: var(--gray-700); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .nav-button:hover { background-color: var(--gray-300); transform: scale(1.05); } .days-row { display: flex; align-items: center; border-bottom: 1px solid var(--gray-300); padding: 12px 0; background-color: var(--gray-100); } .day-cell { flex: 1; text-align: center; font-weight: 600; font-size: 0.875rem; color: var(--gray-700); padding: 8px; } .calendar-grid { flex: 1; display: flex; flex-direction: column; overflow-y: auto; position: relative; scrollbar-width: thin; scrollbar-color: var(--gray-400) var(--gray-200); } .calendar-grid::-webkit-scrollbar { width: 8px; } .calendar-grid::-webkit-scrollbar-track { background: var(--gray-200); } .calendar-grid::-webkit-scrollbar-thumb { background-color: var(--gray-400); border-radius: 4px; } .time-row { display: flex; height: 60px; position: relative; } .time-row:not(:last-child) { border-bottom: 1px dashed var(--gray-300); } .time-label { width: 70px; padding: 0 12px; font-size: 0.75rem; color: var(--gray-600); display: flex; align-items: flex-start; justify-content: flex-end; position: sticky; left: 0; background-color: white; z-index: 2; border-right: 1px solid var(--gray-300); padding-top: 4px; } .time-slots { flex: 1; display: flex; } .time-slot { flex: 1; height: 100%; position: relative; transition: background-color 0.2s ease; } .time-slot:not(:last-child) { border-right: 1px dashed var(--gray-300); } .time-slot:hover { background-color: rgba(237, 100, 166, 0.05); } .time-slot.selected { background-color: rgba(237, 100, 166, 0.15); } .action-panel { position: absolute; bottom: 0; left: 0; right: 0; padding: 16px 24px; background-color: white; box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.05); transform: translateY(100%); transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); z-index: 10; display: flex; flex-direction: column; gap: 16px; border-top: 1px solid var(--gray-300); } .action-panel.visible { transform: translateY(0); } .selection-info { display: flex; justify-content: space-between; align-items: center; } .selection-details { font-size: 0.875rem; } .selection-time { font-weight: 600; color: var(--primary); } .action-buttons { display: flex; gap: 12px; } .btn { flex: 1; padding: 12px; border-radius: 8px; border: none; font-weight: 600; cursor: pointer; transition: all 0.2s ease; } .btn-cancel { background-color: var(--gray-200); color: var(--gray-700); } .btn-cancel:hover { background-color: var(--gray-300); } .btn-book { background-color: var(--secondary); color: white; } .btn-book:hover { background-color: var(--secondary-light); transform: translateY(-2px); } .event { position: absolute; left: 4px; right: 4px; border-radius: 6px; padding: 4px 8px; font-size: 0.75rem; color: white; z-index: 3; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); overflow: hidden; display: flex; flex-direction: column; justify-content: center; } .event-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .event-info { font-size: 0.7rem; opacity: 0.9; } .legend { display: flex; justify-content: center; gap: 16px; padding: 12px; border-top: 1px solid var(--gray-300); } .legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; color: var(--gray-600); } .legend-color { width: 12px; height: 12px; border-radius: 2px; } .event-type-1 { background-color: var(--primary-light); } .event-type-2 { background-color: var(--secondary); } .event-type-3 { background-color: var(--success); } .current-time-indicator { position: absolute; left: 0; right: 0; height: 2px; background-color: var(--error); z-index: 4; } .current-time-indicator::before { content: ''; position: absolute; left: 70px; top: -4px; width: 10px; height: 10px; background-color: var(--error); border-radius: 50%; } .booking-success { position: fixed; top: 20px; left: 50%; transform: translateX(-50%) translateY(-100px); background-color: var(--success); color: white; padding: 16px 32px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55); z-index: 100; } .booking-success.show { transform: translateX(-50%) translateY(0); } @media (max-width: 600px) { .calendar-header { padding: 16px; flex-direction: column; gap: 12px; align-items: flex-start; } .day-cell { font-size: 0.75rem; padding: 4px; } .time-label { width: 50px; font-size: 0.7rem; padding: 0 6px; } .current-time-indicator::before { left: 50px; } .action-panel { padding: 12px; } } /* Subtle background pattern */ .pattern-bg { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-image: radial-gradient(var(--gray-300) 1px, transparent 1px); background-size: 20px 20px; opacity: 0.3; pointer-events: none; } /* Decorative element */ .decoration { position: absolute; width: 200px; height: 200px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-light) 0%, var(--secondary) 100%); filter: blur(80px); opacity: 0.15; z-index: -1; } .decoration-1 { top: -100px; right: -50px; } .decoration-2 { bottom: -120px; left: -80px; } /* Accessibility focus styles */ button:focus-visible, .time-slot:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; } /* Pulse animation for current time */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } .current-time-indicator::before { animation: pulse 2s infinite; } </style> </head> <body> <div class="calendar-container"> <div class="pattern-bg"></div> <div class="decoration decoration-1"></div> <div class="decoration decoration-2"></div> <div class="calendar-header"> <h1 class="calendar-title">Work-Life Calendar</h1> <div class="date-nav"> <button class="nav-button" id="prev-day"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/> </svg> </button> <div class="date-display" id="current-date">May 15, 2023</div> <button class="nav-button" id="next-day"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/> </svg> </button> </div> </div> <div class="days-row"> <div class="day-cell" id="day-0">Mon</div> <div class="day-cell" id="day-1">Tue</div> <div class="day-cell" id="day-2">Wed</div> <div class="day-cell" id="day-3">Thu</div> <div class="day-cell" id="day-4">Fri</div> <div class="day-cell" id="day-5">Sat</div> <div class="day-cell" id="day-6">Sun</div> </div> <div class="calendar-grid" id="calendar-grid"> <!-- Time slots will be generated by JavaScript --> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color event-type-1"></div> <span>Meetings</span> </div> <div class="legend-item"> <div class="legend-color event-type-2"></div> <span>Personal</span> </div> <div class="legend-item"> <div class="legend-color event-type-3"></div> <span>Focus Time</span> </div> </div> <div class="action-panel" id="action-panel"> <div class="selection-info"> <div class="selection-details"> Selected time: <span class="selection-time" id="selection-time">9:00 AM - 10:00 AM</span> </div> </div> <div class="action-buttons"> <button class="btn btn-cancel" id="cancel-btn">Cancel</button> <button class="btn btn-book" id="book-btn">Book Time Slot</button> </div> </div> <div class="booking-success" id="booking-success"> Time slot booked successfully! </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Get DOM elements const calendarGrid = document.getElementById('calendar-grid'); const actionPanel = document.getElementById('action-panel'); const selectionTimeEl = document.getElementById('selection-time'); const cancelBtn = document.getElementById('cancel-btn'); const bookBtn = document.getElementById('book-btn'); const bookingSuccess = document.getElementById('booking-success'); const currentDateEl = document.getElementById('current-date'); const prevDayBtn = document.getElementById('prev-day'); const nextDayBtn = document.getElementById('next-day'); const dayElements = Array.from({ length: 7 }, (_, i) => document.getElementById(`day-${i}`)); // State let currentDate = new Date(); let selectedTimeSlot = null; let events = []; // Initialize events generateSampleEvents(); // Create time grid createTimeGrid(); // Set current time indicator setCurrentTimeIndicator(); // Update date display updateDateDisplay(); // Add event listeners prevDayBtn.addEventListener('click', () => { currentDate.setDate(currentDate.getDate() - 1); updateDateDisplay(); refreshEvents(); }); nextDayBtn.addEventListener('click', () => { currentDate.setDate(currentDate.getDate() + 1); updateDateDisplay(); refreshEvents(); }); cancelBtn.addEventListener('click', () => { deselectTimeSlot(); }); bookBtn.addEventListener('click', () => { if (selectedTimeSlot) { const timeSlotData = selectedTimeSlot.dataset; const day = currentDate.getDay(); const hour = parseInt(timeSlotData.hour); // Create new event const newEvent = { id: `event-${Date.now()}`, title: "New Booking", type: "event-type-2", day, startHour: hour, duration: 1, info: "Personal appointment" }; events.push(newEvent); // Show success message bookingSuccess.classList.add('show'); setTimeout(() => { bookingSuccess.classList.remove('show'); }, 3000); // Refresh events display refreshEvents(); deselectTimeSlot(); } }); // Helper functions function createTimeGrid() { // Generate time slots from 8 AM to 8 PM for (let hour = 8; hour <= 20; hour++) { const timeRow = document.createElement('div'); timeRow.className = 'time-row'; const timeLabel = document.createElement('div'); timeLabel.className = 'time-label'; timeLabel.textContent = formatHour(hour); timeRow.appendChild(timeLabel); const timeSlots = document.createElement('div'); timeSlots.className = 'time-slots'; // Create 7 slots for each day of the week for (let day = 0; day < 7; day++) { const timeSlot = document.createElement('div'); timeSlot.className = 'time-slot'; timeSlot.tabIndex = 0; // Make focusable for accessibility timeSlot.dataset.day = day; timeSlot.dataset.hour = hour; timeSlot.addEventListener('click', (e) => { // Check if clicking on an event if (e.target.closest('.event')) { return; } selectTimeSlot(timeSlot); }); timeSlots.appendChild(timeSlot); } timeRow.appendChild(timeSlots); calendarGrid.appendChild(timeRow); } // Add events to the grid refreshEvents(); } function selectTimeSlot(timeSlot) { // Deselect previously selected time slot if (selectedTimeSlot) { selectedTimeSlot.classList.remove('selected'); } // Select new time slot timeSlot.classList.add('selected'); selectedTimeSlot = timeSlot; // Update action panel const hour = parseInt(timeSlot.dataset.hour); selectionTimeEl.textContent = `${formatHour(hour)} - ${formatHour(hour + 1)}`; // Show action panel actionPanel.classList.add('visible'); } function deselectTimeSlot() { if (selectedTimeSlot) { selectedTimeSlot.classList.remove('selected'); selectedTimeSlot = null; } // Hide action panel actionPanel.classList.remove('visible'); } function formatHour(hour) { const ampm = hour >= 12 ? 'PM' : 'AM'; const hour12 = hour % 12 || 12; return `${hour12}:00 ${ampm}`; } function setCurrentTimeIndicator() { const now = new Date(); const currentHour = now.getHours(); const currentMinute = now.getMinutes(); // Only show indicator for current day and if time is between 8 AM and 8 PM if (isSameDay(now, currentDate) && currentHour >= 8 && currentHour <= 20) { const indicator = document.createElement('div'); indicator.className = 'current-time-indicator'; // Calculate position const hourHeight = 60; // height of each hour row in pixels const offsetFromTop = (currentHour - 8) * hourHeight + (currentMinute / 60) * hourHeight; indicator.style.top = `${offsetFromTop}px`; calendarGrid.appendChild(indicator); // Scroll to current time setTimeout(() => { const scrollPos = offsetFromTop - 120; // 120px above current time calendarGrid.scrollTo({ top: Math.max(0, scrollPos), behavior: 'smooth' }); }, 500); } } function updateDateDisplay() { // Set current date display currentDateEl.textContent = currentDate.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); // Highlight current day of week const currentDay = currentDate.getDay(); const adjustedCurrentDay = currentDay === 0 ? 6 : currentDay - 1; // Adjust for our Mon-Sun format dayElements.forEach((dayEl, index) => { if (index === adjustedCurrentDay) { dayEl.style.color = 'var(--secondary)'; dayEl.style.fontWeight = '700'; } else { dayEl.style.color = 'var(--gray-700)'; dayEl.style.fontWeight = '600'; } }); // Reset current time indicator const indicator = calendarGrid.querySelector('.current-time-indicator'); if (indicator) { indicator.remove(); } setCurrentTimeIndicator(); } function refreshEvents() { // Remove all existing events const existingEvents = calendarGrid.querySelectorAll('.event'); existingEvents.forEach(event => event.remove()); // Add events for current day const currentDay = currentDate.getDay(); const adjustedCurrentDay = currentDay === 0 ? 6 : currentDay - 1; // Adjust for our Mon-Sun format const todayEvents = events.filter(event => event.day === adjustedCurrentDay); todayEvents.forEach(event => { addEventToGrid(event); }); } function addEventToGrid(event) { const row = calendarGrid.children[event.startHour - 8]; // Adjust for our 8 AM start if (!row) return; const daySlot = row.querySelector(`.time-slots`).children[event.day]; if (!daySlot) return; const eventEl = document.createElement('div'); eventEl.className = `event ${event.type}`; eventEl.style.top = '0'; eventEl.style.height = `${event.duration * 60}px`; const eventTitle = document.createElement('div'); eventTitle.className = 'event-title'; eventTitle.textContent = event.title; const eventInfo = document.createElement('div'); eventInfo.className = 'event-info'; eventInfo.textContent = event.info; eventEl.appendChild(eventTitle); eventEl.appendChild(eventInfo); daySlot.appendChild(eventEl); } function isSameDay(date1, date2) { return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate(); } function generateSampleEvents() { const today = new Date().getDay(); const adjustedToday = today === 0 ? 6 : today - 1; // Adjust for our Mon-Sun format events = [ { id: 'event-1', title: 'Team Standup', type: 'event-type-1', day: adjustedToday, startHour: 9, duration: 1, info: 'Weekly sync with team' }, { id: 'event-2', title: 'UX Review', type: 'event-type-1', day: adjustedToday, startHour: 14, duration: 2, info: 'Design feedback session' }, { id: 'event-3', title: 'Yoga Class', type: 'event-type-2', day: adjustedToday, startHour: 17, duration: 1, info: 'Virtual wellness session' }, { id: 'event-4', title: 'Deep Work', type: 'event-type-3', day: adjustedToday, startHour: 11, duration: 2, info: 'No interruptions please' } ]; // Add events for tomorrow const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); const tomorrowDay = tomorrow.getDay(); const adjustedTomorrow = tomorrowDay === 0 ? 6 : tomorrowDay - 1; events.push( { id: 'event-5', title: 'Project Planning', type: 'event-type-1', day: adjustedTomorrow, startHour: 10, duration: 1, info: 'Q3 Roadmap Review' }, { id: 'event-6', title: 'Lunch with Alex', type: 'event-type-2', day: adjustedTomorrow, startHour: 12, duration: 1, info: 'Discuss partnership' } ); } }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Advanced E-Learning Course Grid</title> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@700&display=swap'); :root { --primary: #2A3FFB; --primary-light: #E5E8FF; --secondary: #FF6B6B; --dark: #1A1E2C; --light: #F8F9FC; --gray: #8A94A6; --gray-light: #E2E7F0; --green: #10B981; --yellow: #FBBF24; --blue: #3B82F6; --purple: #8B5CF6; --radius: 12px; --shadow: 0 4px 12px rgba(0, 0, 0, 0.05); --shadow-hover: 0 12px 24px rgba(0, 0, 0, 0.1); --transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background-color: var(--light); color: var(--dark); height: 100vh; display: flex; flex-direction: column; padding: 20px; line-height: 1.5; max-width: 700px; margin: 0 auto; overflow-x: hidden; } .header { margin-bottom: 24px; position: relative; } .title { font-family: 'Playfair Display', serif; font-size: 28px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 8px; background: linear-gradient(to right, var(--primary), var(--purple)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; position: relative; } .subtitle { font-size: 15px; color: var(--gray); font-weight: 400; max-width: 580px; } .filters { display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; } .filter-chip { padding: 8px 14px; background-color: white; border: 1px solid var(--gray-light); border-radius: 100px; font-size: 13px; font-weight: 500; color: var(--dark); cursor: pointer; transition: var(--transition); } .filter-chip:hover { background-color: var(--primary-light); border-color: var(--primary); color: var(--primary); transform: translateY(-2px); } .filter-chip.active { background-color: var(--primary); border-color: var(--primary); color: white; } .grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-bottom: 24px; } .course-card { background-color: white; border-radius: var(--radius); overflow: hidden; position: relative; transition: var(--transition); box-shadow: var(--shadow); cursor: pointer; height: 260px; display: flex; flex-direction: column; border: 1px solid var(--gray-light); } .course-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-hover); } .card-header { height: 8px; width: 100%; } .content-area { padding: 20px; flex: 1; display: flex; flex-direction: column; } .course-category { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; } .course-title { font-size: 18px; font-weight: 700; letter-spacing: -0.01em; margin-bottom: 12px; line-height: 1.3; } .course-meta { display: flex; align-items: center; gap: 12px; font-size: 13px; margin-bottom: 16px; } .meta-item { display: flex; align-items: center; gap: 4px; color: var(--gray); } .progress-container { width: 100%; height: 6px; background-color: var(--gray-light); border-radius: 100px; overflow: hidden; margin-top: auto; } .progress-bar { height: 100%; border-radius: 100px; transition: width 1s ease-in-out; } .course-card .expanded-info { position: absolute; padding: 20px; background-color: rgba(255, 255, 255, 0.98); bottom: 0; left: 0; right: 0; top: 0; transform: translateY(100%); transition: var(--transition); opacity: 0; display: flex; flex-direction: column; z-index: 2; border-radius: var(--radius); } .course-card:hover .expanded-info { transform: translateY(0); opacity: 1; } .expanded-description { font-size: 14px; color: var(--dark); margin-bottom: 16px; flex: 1; overflow-y: auto; } .expanded-description::-webkit-scrollbar { width: 4px; } .expanded-description::-webkit-scrollbar-thumb { background-color: var(--gray-light); border-radius: 100px; } .stats-container { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px; } .stat-item { background-color: var(--light); padding: 10px 12px; border-radius: 8px; } .stat-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--gray); font-weight: 600; } .stat-value { font-size: 16px; font-weight: 700; color: var(--dark); } .continue-btn { background-color: var(--primary); color: white; border: none; padding: 10px; border-radius: var(--radius); font-weight: 600; font-size: 14px; cursor: pointer; transition: var(--transition); margin-top: auto; } .continue-btn:hover { background-color: #1A2FEB; transform: translateY(-2px); } .instructor { display: flex; align-items: center; gap: 8px; margin-top: 8px; } .instructor-avatar { width: 24px; height: 24px; border-radius: 50%; background-size: cover; background-position: center; } .instructor-name { font-size: 13px; color: var(--gray); } .difficulty { display: flex; align-items: center; gap: 4px; margin-top: 8px; } .difficulty-dot { width: 8px; height: 8px; border-radius: 50%; background-color: var(--gray-light); } .difficulty-dot.active { background-color: var(--primary); } .glow-effect { position: absolute; width: 100px; height: 100px; background: radial-gradient(circle, rgba(42, 63, 251, 0.15) 0%, rgba(42, 63, 251, 0) 70%); border-radius: 50%; pointer-events: none; z-index: -1; transform: translate(-50%, -50%); opacity: 0; transition: opacity 0.3s ease; } @media (max-width: 600px) { .grid-container { grid-template-columns: 1fr; } .title { font-size: 24px; } .subtitle { font-size: 14px; } .filters { overflow-x: auto; padding-bottom: 8px; flex-wrap: nowrap; } } .loading-shimmer { background: linear-gradient(90deg, #f0f0f0 25%, #f8f8f8 50%, #f0f0f0 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } .scroll-indicator { width: 40px; height: 40px; background-color: white; border-radius: 50%; box-shadow: var(--shadow); position: absolute; right: 20px; bottom: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; transform: translateY(20px); z-index: 10; } .scroll-indicator.visible { opacity: 1; transform: translateY(0); } .scroll-indicator svg { width: 18px; height: 18px; fill: var(--primary); } </style> </head> <body> <div class="header"> <h1 class="title">Academic Pathways</h1> <p class="subtitle">Explore our curated collection of academic courses designed to enhance your knowledge and skills in various disciplines.</p> </div> <div class="filters"> <div class="filter-chip active" data-filter="all">All Courses</div> <div class="filter-chip" data-filter="science">Science</div> <div class="filter-chip" data-filter="humanities">Humanities</div> <div class="filter-chip" data-filter="technology">Technology</div> <div class="filter-chip" data-filter="business">Business</div> </div> <div class="grid-container" id="courseGrid"></div> <div class="scroll-indicator" id="scrollUp"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path></svg> </div> <div class="glow-effect" id="glowEffect"></div> <script> const courseData = [ { id: 1, title: "Introduction to Quantum Computing", category: "Science", categoryColor: "#3B82F6", progress: 75, duration: "8 weeks", modules: 12, difficulty: 3, description: "Explore the fundamentals of quantum computing, including quantum bits (qubits), quantum gates, and quantum algorithms. Learn how quantum computers have the potential to solve problems that are intractable for classical computers.", instructor: { name: "Dr. Eleanor Chen", avatar: "https://randomuser.me/api/portraits/women/44.jpg" }, assignments: 7, discussions: 15 }, { id: 2, title: "Renaissance Art & Architecture", category: "Humanities", categoryColor: "#8B5CF6", progress: 40, duration: "10 weeks", modules: 15, difficulty: 2, description: "Examine the artistic and architectural achievements of the Renaissance period (14th-17th centuries). Analyze works by masters like Leonardo da Vinci, Michelangelo, and Raphael, and understand how they revolutionized artistic techniques.", instructor: { name: "Prof. Marco Bianchi", avatar: "https://randomuser.me/api/portraits/men/22.jpg" }, assignments: 9, discussions: 12 }, { id: 3, title: "Advanced Machine Learning Systems", category: "Technology", categoryColor: "#10B981", progress: 60, duration: "12 weeks", modules: 18, difficulty: 3, description: "Delve into sophisticated machine learning architectures and systems design. This course covers neural network optimization, distributed training frameworks, and deployment strategies for production-level AI systems.", instructor: { name: "Dr. James Watkins", avatar: "https://randomuser.me/api/portraits/men/32.jpg" }, assignments: 12, discussions: 20 }, { id: 4, title: "Corporate Finance & Valuation", category: "Business", categoryColor: "#F59E0B", progress: 25, duration: "6 weeks", modules: 9, difficulty: 2, description: "Master the principles of corporate financial decision-making, capital structure optimization, and company valuation methodologies. Learn to analyze financial statements, calculate enterprise value, and make evidence-based investment decisions.", instructor: { name: "Prof. Sarah Johnson", avatar: "https://randomuser.me/api/portraits/women/28.jpg" }, assignments: 8, discussions: 10 }, { id: 5, title: "Molecular Biology Techniques", category: "Science", categoryColor: "#3B82F6", progress: 90, duration: "10 weeks", modules: 14, difficulty: 3, description: "Develop practical skills in cutting-edge molecular biology techniques, including PCR, gel electrophoresis, CRISPR gene editing, and next-generation sequencing. Includes virtual lab simulations and protocol optimization exercises.", instructor: { name: "Dr. Thomas Reeves", avatar: "https://randomuser.me/api/portraits/men/52.jpg" }, assignments: 11, discussions: 8 }, { id: 6, title: "World Philosophy Traditions", category: "Humanities", categoryColor: "#8B5CF6", progress: 10, duration: "8 weeks", modules: 12, difficulty: 2, description: "Explore diverse philosophical traditions from around the world, including Western philosophy, Eastern philosophy, African philosophy, and Indigenous thought systems. Examine how different cultures approach fundamental questions of existence, knowledge, and ethics.", instructor: { name: "Dr. Amara Okafor", avatar: "https://randomuser.me/api/portraits/women/62.jpg" }, assignments: 6, discussions: 18 } ]; document.addEventListener('DOMContentLoaded', function() { renderCourses('all'); // Filter functionality const filterChips = document.querySelectorAll('.filter-chip'); filterChips.forEach(chip => { chip.addEventListener('click', function() { filterChips.forEach(c => c.classList.remove('active')); this.classList.add('active'); const filter = this.getAttribute('data-filter'); renderCourses(filter); }); }); // Glow effect document.addEventListener('mousemove', function(e) { const glowEffect = document.getElementById('glowEffect'); glowEffect.style.left = e.clientX + 'px'; glowEffect.style.top = e.clientY + 'px'; glowEffect.style.opacity = '1'; setTimeout(() => { glowEffect.style.opacity = '0'; }, 1000); }); // Scroll indicator const scrollUp = document.getElementById('scrollUp'); window.addEventListener('scroll', function() { if (window.scrollY > 200) { scrollUp.classList.add('visible'); } else { scrollUp.classList.remove('visible'); } }); scrollUp.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); }); }); function renderCourses(filter) { const courseGrid = document.getElementById('courseGrid'); courseGrid.innerHTML = ''; let filteredCourses = courseData; if (filter !== 'all') { filteredCourses = courseData.filter(course => course.category.toLowerCase() === filter.toLowerCase() ); } if (filteredCourses.length === 0) { courseGrid.innerHTML = ` <div style="grid-column: 1/-1; text-align: center; padding: 40px; color: var(--gray);"> No courses found for this category. Try a different filter. </div> `; return; } filteredCourses.forEach(course => { const courseCard = document.createElement('div'); courseCard.className = 'course-card'; courseCard.setAttribute('data-id', course.id); courseCard.innerHTML = ` <div class="card-header" style="background-color: ${course.categoryColor};"></div> <div class="content-area"> <div class="course-category" style="color: ${course.categoryColor};">${course.category}</div> <h3 class="course-title">${course.title}</h3> <div class="course-meta"> <div class="meta-item"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <polyline points="12 6 12 12 16 14"></polyline> </svg> ${course.duration} </div> <div class="meta-item"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path> <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path> </svg> ${course.modules} modules </div> </div> <div class="instructor"> <div class="instructor-avatar" style="background-image: url('${course.instructor.avatar}')"></div> <div class="instructor-name">${course.instructor.name}</div> </div> <div class="difficulty"> ${Array(3).fill().map((_, i) => `<div class="difficulty-dot ${i < course.difficulty ? 'active' : ''}"></div>`).join('')} </div> <div class="progress-container"> <div class="progress-bar" style="width: 0%; background-color: ${course.categoryColor};"></div> </div> </div> <div class="expanded-info"> <div class="course-category" style="color: ${course.categoryColor};">${course.category}</div> <h3 class="course-title">${course.title}</h3> <div class="expanded-description">${course.description}</div> <div class="stats-container"> <div class="stat-item"> <div class="stat-label">Progress</div> <div class="stat-value">${course.progress}%</div> </div> <div class="stat-item"> <div class="stat-label">Modules</div> <div class="stat-value">${course.modules}</div> </div> <div class="stat-item"> <div class="stat-label">Assignments</div> <div class="stat-value">${course.assignments}</div> </div> <div class="stat-item"> <div class="stat-label">Discussions</div> <div class="stat-value">${course.discussions}</div> </div> </div> <button class="continue-btn">Continue Learning</button> </div> `; courseGrid.appendChild(courseCard); // Animate progress bar after a short delay setTimeout(() => { const progressBar = courseCard.querySelector('.progress-bar'); progressBar.style.width = `${course.progress}%`; }, 300); }); } </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Mobile-First Compact Grid</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background-color: #f0f5ff; display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow-x: hidden; padding: 20px; } .container { width: 100%; max-width: 700px; margin: 0 auto; overflow: hidden; } .grid-controls { background: #fff; border-radius: 12px; padding: 20px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0, 62, 255, 0.08); } h1 { color: #1a2269; font-size: 22px; margin-bottom: 10px; } p { color: #5f6496; font-size: 14px; margin-bottom: 20px; line-height: 1.5; } .controls { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 10px; } .btn { background: #e9eeff; border: none; border-radius: 8px; padding: 10px 15px; color: #3345b9; font-weight: 600; font-size: 14px; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; position: relative; overflow: hidden; } .btn:hover { background: #d1daff; } .btn:active { transform: scale(0.97); } .btn::after { content: ''; position: absolute; width: 100%; height: 100%; top: 0; left: 0; background: rgba(255, 255, 255, 0.4); border-radius: 50%; transform: scale(0); transition: transform 0.5s ease; } .btn:active::after { transform: scale(3); opacity: 0; } .grid-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: 12px; transition: all 0.5s ease; } .grid-item { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0, 62, 255, 0.08); transition: all 0.3s ease; position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 15px 10px; cursor: pointer; aspect-ratio: 1/1; } .grid-item:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(0, 62, 255, 0.12); } .grid-item::after { content: ''; position: absolute; width: 100%; height: 100%; top: 0; left: 0; background: rgba(104, 132, 255, 0.1); border-radius: 50%; transform: scale(0); transition: transform 0.5s ease; } .grid-item:active::after { transform: scale(3); opacity: 0; } .icon { font-size: 24px; margin-bottom: 10px; color: #3345b9; } .label { font-size: 12px; font-weight: 600; color: #565b7f; text-align: center; } .grid-item.wide { grid-column: span 2; } .grid-item.tall { grid-row: span 2; } .grid-item.featured { background: linear-gradient(135deg, #4461ff, #3345b9); } .grid-item.featured .icon, .grid-item.featured .label { color: #fff; } .notification { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(100px); background: #1a2269; color: white; padding: 10px 20px; border-radius: 8px; font-size: 14px; opacity: 0; transition: all 0.3s ease; z-index: 100; } .notification.show { transform: translateX(-50%) translateY(0); opacity: 1; } /* Small device adjustments */ @media (max-width: 480px) { .grid-container { grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); gap: 8px; } .grid-item { padding: 10px 8px; } .icon { font-size: 20px; margin-bottom: 6px; } .label { font-size: 10px; } } /* Grid transition effect */ @keyframes gridFadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .grid-item { animation: gridFadeIn 0.4s ease forwards; } .grid-item:nth-child(1) { animation-delay: 0.05s; } .grid-item:nth-child(2) { animation-delay: 0.1s; } .grid-item:nth-child(3) { animation-delay: 0.15s; } .grid-item:nth-child(4) { animation-delay: 0.2s; } .grid-item:nth-child(5) { animation-delay: 0.25s; } .grid-item:nth-child(6) { animation-delay: 0.3s; } .grid-item:nth-child(7) { animation-delay: 0.35s; } .grid-item:nth-child(8) { animation-delay: 0.4s; } .grid-item:nth-child(9) { animation-delay: 0.45s; } .grid-item:nth-child(10) { animation-delay: 0.5s; } .grid-item:nth-child(11) { animation-delay: 0.55s; } .grid-item:nth-child(12) { animation-delay: 0.6s; } </style> </head> <body> <div class="container"> <div class="grid-controls"> <h1>Touch-Optimized App Grid</h1> <p>Our smart grid dynamically adjusts for your fingertips. Tap a tile to launch, or customize your layout with the options below.</p> <div class="controls"> <button class="btn" id="btnCompact">Compact</button> <button class="btn" id="btnComfort">Comfort</button> <button class="btn" id="btnRearrange">Shuffle</button> <button class="btn" id="btnFeature">Highlight Task</button> </div> </div> <div class="grid-container" id="grid"> <div class="grid-item" data-app="calendar"> <div class="icon">📅</div> <div class="label">Calendar</div> </div> <div class="grid-item" data-app="messages"> <div class="icon">💬</div> <div class="label">Messages</div> </div> <div class="grid-item" data-app="camera"> <div class="icon">📷</div> <div class="label">Camera</div> </div> <div class="grid-item" data-app="maps"> <div class="icon">🗺️</div> <div class="label">Maps</div> </div> <div class="grid-item" data-app="weather"> <div class="icon">🌤️</div> <div class="label">Weather</div> </div> <div class="grid-item" data-app="tasks"> <div class="icon">✓</div> <div class="label">Tasks</div> </div> <div class="grid-item" data-app="notes"> <div class="icon">📝</div> <div class="label">Notes</div> </div> <div class="grid-item" data-app="photos"> <div class="icon">🖼️</div> <div class="label">Photos</div> </div> <div class="grid-item" data-app="music"> <div class="icon">🎵</div> <div class="label">Music</div> </div> <div class="grid-item" data-app="files"> <div class="icon">📁</div> <div class="label">Files</div> </div> <div class="grid-item" data-app="settings"> <div class="icon">⚙️</div> <div class="label">Settings</div> </div> <div class="grid-item" data-app="health"> <div class="icon">❤️</div> <div class="label">Health</div> </div> </div> <div class="notification" id="notification">Notification message</div> </div> <script> document.addEventListener('DOMContentLoaded', () => { const grid = document.getElementById('grid'); const notification = document.getElementById('notification'); const gridItems = document.querySelectorAll('.grid-item'); const btnCompact = document.getElementById('btnCompact'); const btnComfort = document.getElementById('btnComfort'); const btnRearrange = document.getElementById('btnRearrange'); const btnFeature = document.getElementById('btnFeature'); // Show notification function function showNotification(message, duration = 2000) { notification.textContent = message; notification.classList.add('show'); setTimeout(() => { notification.classList.remove('show'); }, duration); } // Grid item tap handler gridItems.forEach(item => { item.addEventListener('click', () => { const appName = item.getAttribute('data-app'); showNotification(`Opening ${appName.charAt(0).toUpperCase() + appName.slice(1)}`); // Add ripple effect item.classList.add('ripple'); setTimeout(() => { item.classList.remove('ripple'); }, 500); }); }); // Compact grid layout btnCompact.addEventListener('click', () => { grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(70px, 1fr))'; grid.style.gap = '8px'; showNotification('Switched to compact view'); }); // Comfortable grid layout btnComfort.addEventListener('click', () => { grid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(100px, 1fr))'; grid.style.gap = '15px'; showNotification('Switched to comfort view'); }); // Rearrange grid items btnRearrange.addEventListener('click', () => { const itemsArray = Array.from(gridItems); // Shuffle the array for (let i = itemsArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [itemsArray[i], itemsArray[j]] = [itemsArray[j], itemsArray[i]]; } // Remove all items gridItems.forEach(item => { grid.removeChild(item); }); // Add them back in new order itemsArray.forEach(item => { item.style.animation = 'none'; grid.appendChild(item); // Trigger reflow void item.offsetWidth; // Re-apply animation item.style.animation = ''; }); showNotification('Grid rearranged'); }); // Feature/highlight Tasks btnFeature.addEventListener('click', () => { const tasksItem = document.querySelector('[data-app="tasks"]'); // Reset all grid items gridItems.forEach(item => { item.classList.remove('featured', 'wide', 'tall'); }); // Make tasks featured and larger tasksItem.classList.add('featured', 'wide', 'tall'); // Move tasks to front grid.removeChild(tasksItem); grid.insertBefore(tasksItem, grid.firstChild); showNotification('Tasks highlighted for priority access'); }); // Initialize with default tap effect gridItems.forEach(item => { item.addEventListener('touchstart', function(e) { // Prevent default to avoid scrolling e.preventDefault(); this.style.transform = 'scale(0.97)'; this.style.backgroundColor = '#f0f5ff'; }); item.addEventListener('touchend', function() { this.style.transform = ''; this.style.backgroundColor = ''; }); item.addEventListener('touchcancel', function() { this.style.transform = ''; this.style.backgroundColor = ''; }); }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; } :root { --primary: #5664D2; --primary-light: #7A86E7; --accent: #FF6B6B; --bg: #F9FAFC; --text: #2C3E50; --text-light: #7F8C9A; --border: #E1E8ED; --white: #FFFFFF; --gray-100: #F0F4F8; --gray-200: #D9E2EC; --gray-300: #CBD5E0; --highlight: rgba(86, 100, 210, 0.1); --highlight-hover: rgba(86, 100, 210, 0.2); } body { background-color: var(--bg); color: var(--text); font-size: 14px; line-height: 1.5; width: 100%; height: 700px; overflow: auto; padding: 20px; } .container { max-width: 700px; margin: 0 auto; background-color: var(--white); border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); overflow: hidden; } header { padding: 20px 25px; background-color: var(--white); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 10; } .title { font-size: 18px; font-weight: 600; color: var(--text); margin-bottom: 6px; display: flex; align-items: center; gap: 10px; } .title-icon { color: var(--primary); font-size: 20px; } .subtitle { font-size: 13px; color: var(--text-light); margin-bottom: 15px; } .controls { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px; align-items: center; } .filter-container { display: flex; gap: 10px; flex-wrap: wrap; flex: 1; } select, button { background-color: var(--white); border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px; font-size: 13px; color: var(--text); cursor: pointer; transition: all 0.2s ease; outline: none; } select { min-width: 120px; } select:hover, button:hover { border-color: var(--primary-light); } select:focus, button:focus { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(86, 100, 210, 0.2); } button { display: flex; align-items: center; gap: 6px; background-color: var(--white); font-weight: 500; } button.active { background-color: var(--primary); color: var(--white); border-color: var(--primary); } button.reset { background-color: var(--gray-100); } .legend { display: flex; align-items: center; gap: 15px; font-size: 12px; margin-top: 12px; } .legend-item { display: flex; align-items: center; gap: 5px; } .legend-box { width: 12px; height: 12px; border-radius: 3px; } .legend-box.better { background-color: rgba(52, 211, 153, 0.2); border: 1px solid rgba(52, 211, 153, 0.5); } .legend-box.worse { background-color: rgba(255, 107, 107, 0.2); border: 1px solid rgba(255, 107, 107, 0.5); } .legend-box.highlight { background-color: var(--highlight); border: 1px solid var(--primary-light); } .grid-container { overflow-x: auto; position: relative; } table { width: 100%; border-collapse: collapse; border-spacing: 0; } th, td { padding: 14px 20px; text-align: left; position: relative; vertical-align: middle; border-bottom: 1px solid var(--border); transition: background-color 0.2s ease; } th { background-color: var(--gray-100); font-weight: 600; color: var(--text); position: sticky; top: 0; z-index: 5; cursor: pointer; user-select: none; } th:after { content: "⇅"; margin-left: 5px; font-size: 10px; color: var(--text-light); opacity: 0.5; } th.sort-asc:after { content: "↑"; opacity: 1; color: var(--primary); } th.sort-desc:after { content: "↓"; opacity: 1; color: var(--primary); } tr:hover td { background-color: rgba(86, 100, 210, 0.03); } .product-name { font-weight: 600; } .price, .rating, .features, .availability { font-weight: 500; } .price { color: var(--accent); } .rating { display: flex; align-items: center; gap: 5px; } .stars { color: #FFD700; letter-spacing: -2px; } .rating-count { color: var(--text-light); font-size: 12px; } .better { background-color: rgba(52, 211, 153, 0.1); } .worse { background-color: rgba(255, 107, 107, 0.1); } td.highlight { animation: pulse 2s infinite; } @keyframes pulse { 0% { background-color: var(--highlight); } 50% { background-color: var(--highlight-hover); } 100% { background-color: var(--highlight); } } .chip { display: inline-block; padding: 3px 8px; margin: 2px; background-color: var(--gray-100); border-radius: 12px; font-size: 11px; color: var(--text); white-space: nowrap; } .availability { padding: 4px 8px; border-radius: 4px; font-size: 12px; } .in-stock { color: #22C55E; background-color: rgba(34, 197, 94, 0.1); } .limited { color: #F59E0B; background-color: rgba(245, 158, 11, 0.1); } .pre-order { color: #6366F1; background-color: rgba(99, 102, 241, 0.1); } .out-of-stock { color: #EF4444; background-color: rgba(239, 68, 68, 0.1); } .highlight-col { border-left: 2px solid var(--primary); border-right: 2px solid var(--primary); } .sticky-first { position: sticky; left: 0; background-color: var(--white); z-index: 1; } th.sticky-first { background-color: var(--gray-100); z-index: 6; } tr:hover td.sticky-first { background-color: rgba(86, 100, 210, 0.05); } .search-container { position: relative; flex: 1; max-width: 250px; } .search-input { width: 100%; padding: 8px 12px 8px 36px; border: 1px solid var(--border); border-radius: 6px; font-size: 13px; transition: all 0.2s ease; } .search-input:focus { border-color: var(--primary); box-shadow: 0 0 0 2px rgba(86, 100, 210, 0.2); outline: none; } .search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-light); font-size: 14px; } .tooltip { position: absolute; top: -40px; left: 50%; transform: translateX(-50%); background-color: var(--text); color: var(--white); padding: 5px 10px; border-radius: 4px; font-size: 12px; white-space: nowrap; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; z-index: 100; } .tooltip::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: var(--text) transparent transparent transparent; } td:hover .tooltip { opacity: 1; } .checkbox-container { display: flex; align-items: center; gap: 6px; margin-left: 10px; } .checkbox-container input { margin: 0; } @media (max-width: 700px) { body { padding: 10px; } .controls { flex-direction: column; align-items: stretch; } .filter-container { flex-wrap: wrap; } th, td { padding: 10px 15px; } .search-container { max-width: none; } } /* Animation for row selection */ @keyframes selectRow { 0% { background-color: var(--white); } 50% { background-color: var(--highlight); } 100% { background-color: var(--white); } } tr.selected { animation: selectRow 1s ease; } tr.selected td { background-color: var(--highlight); } /* Cell selection cursor */ td { cursor: pointer; } /* Badge for product name */ .product-badge { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; background-color: var(--primary); color: white; font-size: 10px; font-weight: bold; margin-right: 6px; position: relative; top: -1px; } /* Empty table state */ .empty-state { padding: 30px; text-align: center; color: var(--text-light); font-style: italic; display: none; } </style> </head> <body> <div class="container"> <header> <div class="title"> <span class="title-icon">⚖️</span>Smart Tech Comparator </div> <div class="subtitle">Compare cutting-edge tech products across key specs and features</div> <div class="controls"> <div class="search-container"> <span class="search-icon">🔍</span> <input type="text" class="search-input" placeholder="Search products..." id="searchInput"> </div> <div class="filter-container"> <select id="categoryFilter"> <option value="">All Categories</option> <option value="Smartphone">Smartphones</option> <option value="Laptop">Laptops</option> <option value="Headphone">Headphones</option> <option value="Smartwatch">Smartwatches</option> </select> <select id="priceFilter"> <option value="">All Prices</option> <option value="0-500">$0 - $500</option> <option value="500-1000">$500 - $1000</option> <option value="1000+">$1000+</option> </select> <select id="availabilityFilter"> <option value="">All Availability</option> <option value="In Stock">In Stock</option> <option value="Limited Stock">Limited Stock</option> <option value="Pre-order">Pre-order</option> <option value="Out of Stock">Out of Stock</option> </select> <button id="resetFilters" class="reset"> <span>Reset</span> </button> </div> <div class="checkbox-container"> <input type="checkbox" id="highlightDifferences"> <label for="highlightDifferences">Highlight Differences</label> </div> </div> <div class="legend"> <div class="legend-item"> <div class="legend-box better"></div> <span>Better Value</span> </div> <div class="legend-item"> <div class="legend-box worse"></div> <span>Lower Value</span> </div> <div class="legend-item"> <div class="legend-box highlight"></div> <span>Selected</span> </div> </div> </header> <div class="grid-container"> <table id="productTable"> <thead> <tr> <th class="sticky-first" data-sort="product">Product</th> <th data-sort="category">Category</th> <th data-sort="price">Price</th> <th data-sort="rating">Rating</th> <th data-sort="">Features</th> <th data-sort="availability">Availability</th> </tr> </thead> <tbody> <!-- Product data will be inserted here by JavaScript --> </tbody> </table> <div class="empty-state" id="emptyState">No products match your filters. Try adjusting your criteria.</div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Sample product data const products = [ { id: 1, product: "iPhone 15 Pro", category: "Smartphone", price: 999, rating: 4.8, ratingCount: 1243, features: ["A17 Bionic", "48MP Camera", "Dynamic Island", "USB-C"], availability: "In Stock" }, { id: 2, product: "Samsung Galaxy S23 Ultra", category: "Smartphone", price: 1199, rating: 4.7, ratingCount: 892, features: ["200MP Camera", "S Pen", "Snapdragon 8 Gen 2", "8K Video"], availability: "In Stock" }, { id: 3, product: "Google Pixel 8 Pro", category: "Smartphone", price: 899, rating: 4.6, ratingCount: 456, features: ["Tensor G3", "AI Photo Editing", "7-year Updates", "50MP Camera"], availability: "Limited Stock" }, { id: 4, product: "MacBook Pro 16\"", category: "Laptop", price: 2499, rating: 4.9, ratingCount: 763, features: ["M3 Max", "Liquid Retina XDR", "32GB RAM", "1TB SSD"], availability: "In Stock" }, { id: 5, product: "Dell XPS 15", category: "Laptop", price: 1899, rating: 4.5, ratingCount: 532, features: ["Intel i9", "RTX 4060", "32GB RAM", "1TB SSD"], availability: "In Stock" }, { id: 6, product: "Sony WH-1000XM5", category: "Headphone", price: 399, rating: 4.8, ratingCount: 1205, features: ["Active Noise Cancellation", "30-hour Battery", "LDAC", "Adaptive Sound"], availability: "In Stock" }, { id: 7, product: "Apple AirPods Pro 2", category: "Headphone", price: 249, rating: 4.7, ratingCount: 2435, features: ["Active Noise Cancellation", "Transparency Mode", "H2 Chip", "USB-C"], availability: "In Stock" }, { id: 8, product: "Apple Watch Ultra 2", category: "Smartwatch", price: 799, rating: 4.8, ratingCount: 421, features: ["Titanium Case", "Action Button", "36-hour Battery", "Dual-frequency GPS"], availability: "Limited Stock" }, { id: 9, product: "Samsung Galaxy Watch 6", category: "Smartwatch", price: 299, rating: 4.5, ratingCount: 356, features: ["BioActive Sensor", "Advanced Sleep Coaching", "Sapphire Crystal", "WearOS"], availability: "In Stock" }, { id: 10, product: "ASUS ROG Zephyrus G14", category: "Laptop", price: 1499, rating: 4.6, ratingCount: 289, features: ["AMD Ryzen 9", "RTX 4070", "16GB RAM", "1TB SSD"], availability: "Limited Stock" }, { id: 11, product: "Bose QuietComfort Ultra", category: "Headphone", price: 429, rating: 4.7, ratingCount: 167, features: ["Spatial Audio", "CustomTune Technology", "24-hour Battery", "Aware Mode"], availability: "Pre-order" }, { id: 12, product: "Nothing Phone (2)", category: "Smartphone", price: 699, rating: 4.3, ratingCount: 312, features: ["Glyph Interface", "Snapdragon 8+ Gen 1", "50MP Camera", "4700mAh Battery"], availability: "In Stock" } ]; const tableBody = document.querySelector('#productTable tbody'); const searchInput = document.getElementById('searchInput'); const categoryFilter = document.getElementById('categoryFilter'); const priceFilter = document.getElementById('priceFilter'); const availabilityFilter = document.getElementById('availabilityFilter'); const resetButton = document.getElementById('resetFilters'); const highlightDifferences = document.getElementById('highlightDifferences'); const emptyState = document.getElementById('emptyState'); let selectedCells = []; let currentSort = { column: null, direction: null }; // Initialize the table function renderTable(data) { tableBody.innerHTML = ''; if (data.length === 0) { emptyState.style.display = 'block'; return; } emptyState.style.display = 'none'; data.forEach(product => { const row = document.createElement('tr'); row.dataset.id = product.id; // Create star rating display const fullStars = Math.floor(product.rating); const hasHalfStar = product.rating % 1 >= 0.5; let starsHTML = '★'.repeat(fullStars); if (hasHalfStar) starsHTML += '½'; // Create availability class let availabilityClass = ''; switch(product.availability) { case 'In Stock': availabilityClass = 'in-stock'; break; case 'Limited Stock': availabilityClass = 'limited'; break; case 'Pre-order': availabilityClass = 'pre-order'; break; case 'Out of Stock': availabilityClass = 'out-of-stock'; break; } row.innerHTML = ` <td class="sticky-first product-name"><span class="product-badge">${product.id}</span>${product.product}</td> <td>${product.category}</td> <td class="price">$${product.price.toLocaleString()}</td> <td> <div class="rating"> <span class="stars">${starsHTML}</span> <span class="rating-count">(${product.ratingCount})</span> </div> </td> <td class="features"> ${product.features.map(feature => `<span class="chip">${feature}</span>` ).join('')} </td> <td> <span class="availability ${availabilityClass}">${product.availability}</span> </td> `; tableBody.appendChild(row); }); // Apply highlighting for differences if enabled if (highlightDifferences.checked) { highlightDifferentValues(); } } // Filter products based on search and dropdown filters function filterProducts() { const searchTerm = searchInput.value.toLowerCase(); const categoryValue = categoryFilter.value; const priceValue = priceFilter.value; const availabilityValue = availabilityFilter.value; const filtered = products.filter(product => { // Search text const matchesSearch = product.product.toLowerCase().includes(searchTerm) || product.features.some(feature => feature.toLowerCase().includes(searchTerm)); // Category filter const matchesCategory = categoryValue === '' || product.category === categoryValue; // Price filter let matchesPrice = true; if (priceValue === '0-500') { matchesPrice = product.price <= 500; } else if (priceValue === '500-1000') { matchesPrice = product.price > 500 && product.price <= 1000; } else if (priceValue === '1000+') { matchesPrice = product.price > 1000; } // Availability filter const matchesAvailability = availabilityValue === '' || product.availability === availabilityValue; return matchesSearch && matchesCategory && matchesPrice && matchesAvailability; }); return filtered; } // Sort products by column function sortProducts(products, column, direction) { return [...products].sort((a, b) => { let valueA, valueB; // Determine values to compare based on column switch(column) { case 'product': valueA = a.product; valueB = b.product; break; case 'category': valueA = a.category; valueB = b.category; break; case 'price': valueA = a.price; valueB = b.price; break; case 'rating': valueA = a.rating; valueB = b.rating; break; case 'availability': const availabilityOrder = { 'In Stock': 1, 'Limited Stock': 2, 'Pre-order': 3, 'Out of Stock': 4 }; valueA = availabilityOrder[a.availability]; valueB = availabilityOrder[b.availability]; break; default: return 0; } // Perform comparison if (typeof valueA === 'string') { return direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA); } else { return direction === 'asc' ? valueA - valueB : valueB - valueA; } }); } // Highlight differences between products function highlightDifferentValues() { const rows = tableBody.querySelectorAll('tr'); if (rows.length <= 1) return; // For each column except product name for (let colIndex = 1; colIndex <= 5; colIndex++) { let values = []; // Collect values for this column rows.forEach(row => { const cell = row.cells[colIndex]; let value; // Different extraction method based on column type if (colIndex === 2) { // Price column value = parseFloat(cell.textContent.replace('$', '').replace(',', '')); } else if (colIndex === 3) { // Rating column value = parseFloat(cell.textContent.match(/[0-9.]+/)[0]); } else { value = cell.textContent.trim(); } values.push({ cell, value }); }); // Skip comparison for feature column if (colIndex === 4) continue; // For numeric columns, add better/worse classes if (colIndex === 2 || colIndex === 3) { const isPrice = colIndex === 2; const sortedValues = [...values].sort((a, b) => isPrice ? a.value - b.value : b.value - a.value); // For price, lower is better. For rating, higher is better const bestValue = sortedValues[0].value; const worstValue = sortedValues[sortedValues.length - 1].value; // Only highlight if there's a difference if (bestValue !== worstValue) { values.forEach(item => { if (item.value === bestValue) { item.cell.classList.add('better'); } else if (item.value === worstValue) { item.cell.classList.add('worse'); } }); } } } } // Handle header click for sorting document.querySelectorAll('#productTable th').forEach(th => { if (!th.dataset.sort) return; th.addEventListener('click', () => { const column = th.dataset.sort; let direction = 'asc'; // Toggle sort direction if same column is clicked if (currentSort.column === column) { direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } // Update sort state currentSort = { column, direction }; // Update header classes document.querySelectorAll('#productTable th').forEach(header => { header.classList.remove('sort-asc', 'sort-desc'); }); th.classList.add(`sort-${direction}`); // Sort and render const filtered = filterProducts(); const sorted = sortProducts(filtered, column, direction); renderTable(sorted); }); }); // Cell click to highlight tableBody.addEventListener('click', (e) => { const cell = e.target.closest('td'); if (!cell) return; // Toggle highlight class if (cell.classList.contains('highlight')) { cell.classList.remove('highlight'); selectedCells = selectedCells.filter(c => c !== cell); } else { cell.classList.add('highlight'); selectedCells.push(cell); // Add subtle animation to selected cell cell.animate([ { backgroundColor: 'var(--white)' }, { backgroundColor: 'var(--highlight)' }, { backgroundColor: 'var(--highlight-hover)' }, { backgroundColor: 'var(--highlight)' } ], { duration: 1000, easing: 'ease' }); } }); // Event listeners for filters searchInput.addEventListener('input', () => { const filtered = filterProducts(); const sorted = currentSort.column ? sortProducts(filtered, currentSort.column, currentSort.direction) : filtered; renderTable(sorted); }); categoryFilter.addEventListener('change', () => { const filtered = filterProducts(); const sorted = currentSort.column ? sortProducts(filtered, currentSort.column, currentSort.direction) : filtered; renderTable(sorted); }); priceFilter.addEventListener('change', () => { const filtered = filterProducts(); const sorted = currentSort.column ? sortProducts(filtered, currentSort.column, currentSort.direction) : filtered; renderTable(sorted); }); availabilityFilter.addEventListener('change', () => { const filtered = filterProducts(); const sorted = currentSort.column ? sortProducts(filtered, currentSort.column, currentSort.direction) : filtered; renderTable(sorted); }); resetButton.addEventListener('click', () => { searchInput.value = ''; categoryFilter.value = ''; priceFilter.value = ''; availabilityFilter.value = ''; highlightDifferences.checked = false; // Clear sorting currentSort = { column: null, direction: null }; document.querySelectorAll('#productTable th').forEach(header => { header.classList.remove('sort-asc', 'sort-desc'); }); renderTable(products); // Clear all highlights selectedCells.forEach(cell => { cell.classList.remove('highlight'); }); selectedCells = []; }); highlightDifferences.addEventListener('change', () => { // Re-render to apply or remove highlights const filtered = filterProducts(); const sorted = currentSort.column ? sortProducts(filtered, currentSort.column, currentSort.direction) : filtered; renderTable(sorted); }); // Initial render renderTable(products); // Add row selection on product name click tableBody.addEventListener('click', (e) => { const productCell = e.target.closest('td.product-name'); if (!productCell) return; const row = productCell.closest('tr'); // Toggle selected class if (row.classList.contains('selected')) { row.classList.remove('selected'); } else { // Deselect any previously selected row document.querySelectorAll('tr.selected').forEach(r => { r.classList.remove('selected'); }); row.classList.add('selected'); } }); }); </script> </body> </html>
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Wanderlust Explorer</title> <style> :root { --primary: #2A6BFF; --secondary: #FF6B2A; --dark: #1A2138; --light: #F8F9FC; --accent: #00D4C8; --card-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); --transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); } * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; } body { background-color: var(--light); color: var(--dark); padding: 16px; overflow-x: hidden; height: 100%; } .container { max-width: 700px; margin: 0 auto; height: 100%; } header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; position: relative; } .logo { font-weight: 800; font-size: 20px; letter-spacing: -0.5px; color: var(--dark); display: flex; align-items: center; } .logo-icon { background: linear-gradient(135deg, var(--primary), var(--accent)); color: white; width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; margin-right: 10px; box-shadow: 0 4px 10px rgba(42, 107, 255, 0.2); } .search-bar { position: relative; width: 100%; max-width: 320px; } .search-input { width: 100%; padding: 12px 16px 12px 42px; border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.08); background: white; font-size: 14px; transition: var(--transition); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); } .search-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(42, 107, 255, 0.15); } .search-icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: #9CA3AF; } .filters { display: flex; gap: 10px; margin-bottom: 24px; overflow-x: auto; padding-bottom: 8px; -ms-overflow-style: none; scrollbar-width: none; } .filters::-webkit-scrollbar { display: none; } .filter-btn { background: white; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 24px; padding: 8px 16px; font-size: 14px; font-weight: 500; cursor: pointer; white-space: nowrap; transition: var(--transition); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.04); } .filter-btn.active { background: var(--primary); color: white; border-color: var(--primary); } .filter-btn:hover:not(.active) { background: rgba(0, 0, 0, 0.03); } .destination-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; margin-bottom: 24px; } .destination-card { position: relative; border-radius: 16px; overflow: hidden; background: white; height: 280px; cursor: pointer; box-shadow: var(--card-shadow); transition: var(--transition); } .destination-card:hover { transform: translateY(-6px); box-shadow: 0 16px 30px rgba(0, 0, 0, 0.15); } .destination-card:hover .destination-img { transform: scale(1.05); } .destination-img-container { height: 180px; overflow: hidden; position: relative; } .destination-img { width: 100%; height: 100%; object-fit: cover; transition: var(--transition); } .destination-badge { position: absolute; top: 16px; left: 16px; background: rgba(255, 107, 42, 0.9); color: white; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 2; } .destination-save { position: absolute; top: 16px; right: 16px; background: rgba(255, 255, 255, 0.9); border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); z-index: 2; transition: var(--transition); } .destination-save:hover { background: white; transform: scale(1.1); } .destination-save.saved { color: var(--secondary); } .destination-info { padding: 16px; } .destination-name { font-weight: 700; font-size: 18px; margin-bottom: 4px; color: var(--dark); } .destination-location { font-size: 14px; color: #6B7280; margin-bottom: 12px; display: flex; align-items: center; } .location-icon { margin-right: 4px; color: #9CA3AF; } .destination-meta { display: flex; justify-content: space-between; align-items: center; } .destination-price { font-weight: 700; font-size: 16px; color: var(--primary); } .destination-rating { display: flex; align-items: center; font-weight: 600; font-size: 14px; } .rating-icon { color: #FFB800; margin-right: 4px; } .no-results { text-align: center; padding: 40px 20px; font-size: 16px; color: #6B7280; background: white; border-radius: 16px; box-shadow: var(--card-shadow); grid-column: 1 / -1; } .no-results-icon { font-size: 40px; margin-bottom: 16px; color: #9CA3AF; } .pagination { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 20px; } .page-btn { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 8px; border: 1px solid rgba(0, 0, 0, 0.08); background: white; font-weight: 500; cursor: pointer; transition: var(--transition); } .page-btn.active { background: var(--primary); color: white; border-color: var(--primary); } .page-btn:hover:not(.active) { background: rgba(0, 0, 0, 0.03); } .quick-view { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 100; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .quick-view.active { opacity: 1; pointer-events: auto; } .quick-view-content { background: white; border-radius: 16px; width: 90%; max-width: 600px; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); transform: translateY(20px); transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1); } .quick-view.active .quick-view-content { transform: translateY(0); } .quick-view-header { position: relative; height: 240px; } .quick-view-img { width: 100%; height: 100%; object-fit: cover; border-top-left-radius: 16px; border-top-right-radius: 16px; } .quick-view-close { position: absolute; top: 16px; right: 16px; background: rgba(255, 255, 255, 0.9); border-radius: 50%; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 2; transition: var(--transition); } .quick-view-close:hover { background: white; transform: rotate(90deg); } .quick-view-body { padding: 24px; } .quick-view-title { font-size: 24px; font-weight: 700; margin-bottom: 8px; } .quick-view-location { font-size: 16px; color: #6B7280; margin-bottom: 16px; display: flex; align-items: center; } .quick-view-stats { display: flex; gap: 16px; margin-bottom: 20px; } .stat-item { display: flex; flex-direction: column; align-items: center; } .stat-value { font-weight: 700; font-size: 18px; color: var(--dark); } .stat-label { font-size: 13px; color: #6B7280; } .quick-view-description { margin-bottom: 20px; line-height: 1.6; color: #4B5563; } .highlights { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; } .highlight { background: rgba(42, 107, 255, 0.1); color: var(--primary); padding: 6px 12px; border-radius: 20px; font-size: 13px; font-weight: 500; } .booking-bar { display: flex; align-items: center; justify-content: space-between; background: #F3F4F6; padding: 16px; border-radius: 12px; } .booking-price { font-size: 22px; font-weight: 700; color: var(--dark); } .booking-price span { font-size: 14px; font-weight: 400; color: #6B7280; } .booking-btn { background: var(--primary); color: white; border: none; border-radius: 10px; padding: 12px 24px; font-weight: 600; font-size: 15px; cursor: pointer; transition: var(--transition); } .booking-btn:hover { background: #1A5AFF; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(42, 107, 255, 0.3); } .loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.8); display: flex; align-items: center; justify-content: center; z-index: 200; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; } .loading-overlay.active { opacity: 1; pointer-events: auto; } .loading-spinner { width: 40px; height: 40px; border: 3px solid rgba(42, 107, 255, 0.2); border-radius: 50%; border-top-color: var(--primary); animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } .float-label { position: absolute; top: -40px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.8); color: white; padding: 8px 12px; border-radius: 6px; font-size: 13px; opacity: 0; transition: opacity 0.2s ease, top 0.2s ease; pointer-events: none; white-space: nowrap; } .float-label:after { content: ''; position: absolute; left: 50%; bottom: -6px; transform: translateX(-50%); border-left: 6px solid transparent; border-right: 6px solid transparent; border-top: 6px solid rgba(0, 0, 0, 0.8); } .destination-save:hover .float-label { opacity: 1; top: -36px; } .parallax-bg { position: absolute; top: 0; left: 0; right: 0; height: 100%; background: radial-gradient(circle at 10% 20%, rgba(42, 107, 255, 0.05) 0%, rgba(0, 212, 200, 0.05) 100%); transform: translateY(0); pointer-events: none; } .shimmer-effect { position: relative; overflow: hidden; } .shimmer-effect::after { content: ''; position: absolute; top: 0; left: -100%; width: 50%; height: 100%; background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.3), transparent); animation: shimmerEffect 2s infinite; } @keyframes shimmerEffect { to { left: 200%; } } @media (max-width: 700px) { .destination-grid { grid-template-columns: 1fr; } .search-bar { max-width: 200px; } .quick-view-content { width: 95%; } .quick-view-header { height: 180px; } } </style> </head> <body> <div class="parallax-bg" id="parallax-bg"></div> <div class="container"> <header> <div class="logo"> <div class="logo-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"></path><path d="M2 12h20"></path> </svg> </div> Wanderlust </div> <div class="search-bar"> <span class="search-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="11" cy="11" r="8"></circle> <line x1="21" y1="21" x2="16.65" y2="16.65"></line> </svg> </span> <input type="text" class="search-input" placeholder="Search destinations..." id="search-input"> </div> </header> <div class="filters" id="filters"> <button class="filter-btn active" data-filter="all">All Destinations</button> <button class="filter-btn" data-filter="beach">Beach Getaways</button> <button class="filter-btn" data-filter="mountain">Mountain Escapes</button> <button class="filter-btn" data-filter="city">City Breaks</button> <button class="filter-btn" data-filter="historic">Historic Sites</button> <button class="filter-btn" data-filter="adventure">Adventure Trips</button> </div> <div class="destination-grid" id="destination-grid"> <!-- Destinations will be loaded here by JavaScript --> </div> <div class="pagination" id="pagination"> <button class="page-btn active">1</button> <button class="page-btn">2</button> <button class="page-btn">3</button> <button class="page-btn"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polyline points="9 18 15 12 9 6"></polyline> </svg> </button> </div> </div> <div class="quick-view" id="quick-view"> <div class="quick-view-content"> <div class="quick-view-header"> <img src="" alt="" class="quick-view-img" id="quick-view-img"> <div class="quick-view-close" id="quick-view-close"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </div> </div> <div class="quick-view-body"> <h2 class="quick-view-title" id="quick-view-title"></h2> <div class="quick-view-location" id="quick-view-location"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; color: #9CA3AF;"> <path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> <span id="quick-view-location-text"></span> </div> <div class="quick-view-stats"> <div class="stat-item"> <div class="stat-value" id="quick-view-rating"></div> <div class="stat-label">Rating</div> </div> <div class="stat-item"> <div class="stat-value" id="quick-view-duration"></div> <div class="stat-label">Duration</div> </div> <div class="stat-item"> <div class="stat-value" id="quick-view-weather"></div> <div class="stat-label">Weather</div> </div> </div> <p class="quick-view-description" id="quick-view-description"></p> <div class="highlights" id="quick-view-highlights"> <!-- Highlights will be added here --> </div> <div class="booking-bar"> <div class="booking-price" id="quick-view-price"></div> <button class="booking-btn">Book This Trip</button> </div> </div> </div> </div> <div class="loading-overlay" id="loading-overlay"> <div class="loading-spinner"></div> </div> <script> // Sample destination data const destinations = [ { id: 1, name: "Santorini Cliffside Retreat", location: "Oia, Santorini, Greece", price: 1299, rating: 4.9, image: "https://images.unsplash.com/photo-1613395877344-13d4a8e0d49e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Best Seller", category: "beach", description: "Experience the iconic white-washed buildings and breathtaking sunsets of Santorini from your private villa overlooking the azure waters of the Aegean Sea. Explore charming villages, volcanic beaches, and world-class restaurants.", duration: "7 Days", weather: "Sunny", highlights: ["Sunset Views", "Private Pool", "Wine Tour", "Volcanic Beach", "Boat Excursion"] }, { id: 2, name: "Alpine Lodge Experience", location: "Zermatt, Switzerland", price: 1850, rating: 4.8, image: "https://images.unsplash.com/photo-1531366599837-ce0c0c90a531?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Luxury", category: "mountain", description: "Nestled at the foot of the Matterhorn, this Alpine retreat offers unparalleled access to world-class skiing and hiking trails. Relax in a timber lodge with modern amenities and spectacular mountain views in every direction.", duration: "6 Days", weather: "Crisp", highlights: ["Ski Access", "Hot Tub", "Mountain Views", "Gourmet Dining", "Hiking Trails"] }, { id: 3, name: "Tokyo Urban Adventure", location: "Shibuya, Tokyo, Japan", price: 1450, rating: 4.7, image: "https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Popular", category: "city", description: "Immerse yourself in the electric energy of Tokyo from our expertly located hotel in Shibuya. Experience the perfect blend of ancient traditions and cutting-edge technology that make this metropolis truly unique.", duration: "8 Days", weather: "Varied", highlights: ["Shibuya Crossing", "Food Tour", "Robot Restaurant", "Mt. Fuji Day Trip", "Anime District"] }, { id: 4, name: "Machu Picchu Expedition", location: "Sacred Valley, Peru", price: 1899, rating: 4.9, image: "https://images.unsplash.com/photo-1587595431973-160d0d94add1?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Adventure", category: "historic", description: "Journey through the Sacred Valley to the ancient Incan citadel of Machu Picchu. This carefully curated expedition combines archaeological wonders, breathtaking mountain scenery, and authentic cultural experiences.", duration: "9 Days", weather: "Mild", highlights: ["Guided Tour", "Inca Trail", "Local Cuisine", "Traditional Markets", "Mountain Views"] }, { id: 5, name: "Bali Beachfront Villa", location: "Seminyak, Bali, Indonesia", price: 1150, rating: 4.6, image: "https://images.unsplash.com/photo-1537996194471-e657df975ab4?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Top Rated", category: "beach", description: "Escape to your private beachfront villa in Bali where lush tropical gardens meet pristine sandy beaches. Enjoy personalized service, traditional Balinese architecture, and easy access to vibrant local culture.", duration: "10 Days", weather: "Tropical", highlights: ["Private Beach", "Spa Services", "Cooking Class", "Temple Tours", "Surf Lessons"] }, { id: 6, name: "Marrakech Riad Escape", location: "Medina, Marrakech, Morocco", price: 899, rating: 4.7, image: "https://images.unsplash.com/photo-1539020140153-e479b8c5a262?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Cultural", category: "historic", description: "Stay in an authentic riad in the heart of Marrakech's historic medina. Discover a sensory feast of colors, scents, and flavors as you navigate ancient souks, ornate palaces, and lush gardens in this enchanting city.", duration: "5 Days", weather: "Warm", highlights: ["Medina Tour", "Desert Excursion", "Hammam Spa", "Cooking Workshop", "Garden Visit"] }, { id: 7, name: "New Zealand Adventure", location: "Queenstown, New Zealand", price: 2150, rating: 4.9, image: "https://images.unsplash.com/photo-1493606278519-11aa9f86e40a?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Thrilling", category: "adventure", description: "Embark on the ultimate adventure in the adrenaline capital of the world. From bungee jumping and jet boating to hiking through Middle-earth landscapes, this trip delivers unforgettable experiences with breathtaking backdrops.", duration: "12 Days", weather: "Varied", highlights: ["Bungee Jump", "Milford Sound", "Lord of the Rings Tour", "Helicopter Ride", "Wine Tasting"] }, { id: 8, name: "Amalfi Coast Villa", location: "Positano, Italy", price: 1750, rating: 4.8, image: "https://images.unsplash.com/photo-1533587851505-d119d2707805?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80", badge: "Romantic", category: "beach", description: "Perched on the cliffs of the stunning Amalfi Coast, this elegant villa offers panoramic sea views, private terraces, and unparalleled Italian hospitality. Explore picturesque villages, hidden beaches, and mouthwatering cuisine.", duration: "7 Days", weather: "Sunny", highlights: ["Coastal Views", "Boat Tour", "Limoncello Tasting", "Historic Sites", "Mediterranean Cuisine"] } ]; document.addEventListener('DOMContentLoaded', function() { // Initialize the grid with all destinations renderDestinations(destinations); // Add event listener to filter buttons const filterButtons = document.querySelectorAll('.filter-btn'); filterButtons.forEach(button => { button.addEventListener('click', function() { // Show loading overlay showLoading(); // Reset active state filterButtons.forEach(btn => btn.classList.remove('active')); this.classList.add('active'); const filter = this.getAttribute('data-filter'); // Filter destinations const filteredDestinations = filter === 'all' ? destinations : destinations.filter(destination => destination.category === filter); // Add slight delay to show loading effect setTimeout(() => { renderDestinations(filteredDestinations); hideLoading(); }, 500); }); }); // Search functionality const searchInput = document.getElementById('search-input'); searchInput.addEventListener('input', function() { const searchTerm = this.value.toLowerCase(); if (searchTerm.length > 2) { showLoading(); setTimeout(() => { const searchResults = destinations.filter(destination => destination.name.toLowerCase().includes(searchTerm) || destination.location.toLowerCase().includes(searchTerm) ); renderDestinations(searchResults); hideLoading(); }, 300); } else if (searchTerm.length === 0) { // Reset to show all when search is cleared renderDestinations(destinations); } }); // Quick view close button document.getElementById('quick-view-close').addEventListener('click', function() { document.getElementById('quick-view').classList.remove('active'); document.body.style.overflow = 'auto'; }); // Pagination buttons click event const paginationButtons = document.querySelectorAll('.page-btn'); paginationButtons.forEach(button => { button.addEventListener('click', function() { paginationButtons.forEach(btn => btn.classList.remove('active')); this.classList.add('active'); showLoading(); // Simulate page change setTimeout(() => { // We're just reshuffling the same destinations for demo purposes let shuffled = [...destinations].sort(() => 0.5 - Math.random()); renderDestinations(shuffled); hideLoading(); window.scrollTo({top: 0, behavior: 'smooth'}); }, 600); }); }); // Parallax effect on background window.addEventListener('mousemove', function(e) { const moveX = (e.clientX / window.innerWidth) * 10; const moveY = (e.clientY / window.innerHeight) * 10; document.getElementById('parallax-bg').style.transform = `translate(${-moveX}px, ${-moveY}px)`; }); }); function renderDestinations(destinationsToRender) { const grid = document.getElementById('destination-grid'); // Clear existing cards grid.innerHTML = ''; if (destinationsToRender.length === 0) { grid.innerHTML = ` <div class="no-results"> <div class="no-results-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12.01" y2="16"></line> </svg> </div> <p>No destinations found. Try different search criteria.</p> </div>`; return; } // Create destination cards destinationsToRender.forEach((destination, index) => { const card = document.createElement('div'); card.className = 'destination-card'; card.setAttribute('data-id', destination.id); // Add animation delay based on index card.style.animation = `fadeIn 0.5s ${index * 0.1}s forwards`; card.style.opacity = '0'; card.innerHTML = ` <div class="destination-img-container"> <img src="${destination.image}" alt="${destination.name}" class="destination-img"> <div class="destination-badge">${destination.badge}</div> <div class="destination-save" data-id="${destination.id}"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path> </svg> <div class="float-label">Save to wishlist</div> </div> </div> <div class="destination-info"> <h3 class="destination-name">${destination.name}</h3> <div class="destination-location"> <span class="location-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"></path> <circle cx="12" cy="10" r="3"></circle> </svg> </span> ${destination.location} </div> <div class="destination-meta"> <div class="destination-price">$${destination.price} <span>per person</span></div> <div class="destination-rating"> <span class="rating-icon"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17