diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-04-27 16:45:49 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-04-27 16:58:00 -0700 |
| commit | c3acd89b02680cd3c75674ef713875953d84b738 (patch) | |
| tree | 9aa3fad4a3164f8170ee9adc39210ae6cefb22b4 /src/pages/projects.astro | |
| parent | 2cdbdc1d9443578822482515fde6e91e9d2fc655 (diff) | |
update projects
Diffstat (limited to 'src/pages/projects.astro')
| -rw-r--r-- | src/pages/projects.astro | 854 |
1 files changed, 448 insertions, 406 deletions
diff --git a/src/pages/projects.astro b/src/pages/projects.astro index 7373603..1cb84ba 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -5,437 +5,479 @@ import Card from "../components/Card.astro"; --- <Layout title="Projects"> - <main> - <div class="header-container"> - <h1 class="text-4xl font-semibold text-center py-6">Projects</h1> - <p class="text-center mb-8"> - Here are some of my projects. I love tinkering with stuff so for a more complete list, visit my <a class="font-bold hover:underline hover:text-blue-300 transition-colors" href="https://github.com/pinapelz">Github</a> - <br /> - <a href="https://github.com/pulls/search?q=is%3Apr+is%3Amerged+author%3Apinapelz+-repo%3Apinapelz%2Fupptime+-repo%3Apinapelz%2Fpinapelz+-user%3Apinapelz+-org%3AAntAvionics" class="mt-6 inline-block hover:underline text-center font-bold text-2xl animate-pulse hover:text-blue-300 transition-colors">Open Source Contributions</a> - </p> - </div> - <ul role="list" class="project-grid"> - <Card - href="https://github.com/pinapelz/Mirage" - title="Mirage" - body={`• Rhythm game score tracker that preserves scores across games - even niche ones -• No reliance on predefined seeds or chart metadata -• Import & track scores to keep a safe backup of your game progress -• Support for any rhythm game, even without official metadata -• Self-host for group tracking or use locally -• Multi-user system with customizable permissions -• Pre-loaded tracking for DANCERUSH, DANCE aROUND, Project DIVA, MUSIC DIVER, Nostalgia, and REFLEC BEAT`} - language="TypeScript" - languageColor="#3178c6" - year="2024" - image="/mirage.png" - imageAlt="Mirage score tracker interface screenshot" - tags={["React", "TypeScript", "Express", "Prisma ORM", "PostgreSQL"]} - /> - <Card - href="https://patchwork.moekyun.me" - title="Patchwork Archive" - body={`• Comprehensive archival system for YouTube videos at scale -• React-based frontend for managing requests and viewing archived content -• Python Flask backend API for authentication and database operations -• Distributed worker system for processing archival jobs -• Automatic metadata extraction and thumbnail preservation -• Efficient storage using S3-compatible backends -• Scalable architecture handling concurrent archival of thousands of videos`} - language="Javascript" - languageColor="#f1e05a" - year="2023" - image="/patchwork.png" - imageAlt="Screenshot of Patchwork Archive site" - tags={["React", "Python", "MySQL" , "S3 Storage", "TailwindCSS"]} - /> - <Card - href="https://github.com/pinapelz/tiny-time-tracker" - title="tiny-time-tracker" - body={`• Lightweight game time tracking application written in Rust -• Designed specifically for Windows systems -• Uses native process monitoring APIs instead of resource-intensive polling -• Event-based detection of when applications start and stop -• Clean web-based dashboard built with Askama templating -• Styled with Tailwind CSS for responsive design -• Detailed statistics about gaming habits and playtime`} - language="Rust" - languageColor="#dea584" - image="/ttt.png" - imagAlt="Tiny Time Tracker Web UI" - year="2025" - tags={["Rust", "Askama", "Tailwind", "Windows API", "SQLite"]} - /> - <Card - href="https://github.com/pinapelz/NijiTrack" - title="Nijitrack" - body={`• Data analytics platform for YouTube channel statistics -• Focus on VTuber and content creator metrics tracking -• Python backend for continuous data collection via YouTube API -• Tracks subscriber counts, view metrics, and other channel data -• Next.js frontend with interactive charts and historical analysis -• Responsive dashboard for comparing multiple channels -• Tools to identify growth trends and performance patterns`} - language="Python" - languageColor="#3572A5" - year="2023" - image="/phase-tracker.png" - tags={["Python", "Next.js", "Tailwind", "SSR", "Chart.js", "PostgreSQL", "YouTube API"]} - /> - <Card - href="https://github.com/pinapelz/brokenithm-evolved-ios-umi" - title="brokenithm-evolved-ios-umi" - body={`• Low-level bridge application for iOS rhythm game controllers -• Interfaces with the Brokenithm protocol for game input -• Direct integration with chuniio hardware input systems -• Translates UMIGURI LED lighting signals for iOS compatibility + <main> + <div class="header-container"> + <h1 class="text-4xl font-semibold text-center py-6">Projects</h1> + <p class="text-center mb-8"> + Here are some of my projects. I love tinkering with stuff so for + a more complete list, visit my <a + class="font-bold hover:underline hover:text-blue-300 transition-colors" + href="https://github.com/pinapelz">Github</a + > + <br /> + <a + href="https://github.com/pulls/search?q=is%3Apr+is%3Amerged+author%3Apinapelz+-repo%3Apinapelz%2Fupptime+-repo%3Apinapelz%2Fpinapelz+-user%3Apinapelz+-org%3AAntAvionics" + class="mt-6 inline-block hover:underline text-center font-bold text-2xl animate-pulse hover:text-blue-300 transition-colors" + >Open Source Contributions</a + > + </p> + </div> + + <ul role="list" class="project-grid"> + <!-- 2026 --> + <Card + href="https://github.com/pinapelz/auto-live-tl" + title="auto-live-tl" + body={`• Local real-time subtitle translation for a PCM audio input using faster-whisper ASR +• Optional LLM post-processing for cleanup and de-duplication +• Live transcription with server-sent-events streaming to clients +• Algorithmic filter for removing potential hallucinations and repetitions`} + language="Python" + languageColor="#3572A5" + image="/auto-livetl.png" + imagAlt="Auto Live TL subtitle demo using a YouTube Userscript client (French -> English)" + year="2026" + tags={[ + "Python", + "OpenAI-Whisper", + "ASR", + "LLM" + ]} + /> + <Card + href="https://github.com/pinapelz/lrc-karaoke-player" + title="lrc-karaoke-player" + body={`• Client-side web app for LRC-based karaoke playback and tooling +• LRC-Player: synchronized scrolling lyrics with karaoke-style highlighting, with dual audio mixing +• LRC-Type: typing game inspired by Typing-Tube using song lyrics as input +• Shareable, codes, Fully client-side, no user data stored`} + language="TypeScript" + languageColor="#3178C6" + image="/lrc.png" + imagAlt="LRC Karaoke Player interface" + year="2026" + tags={[ + "Next.js", + "TypeScript", + "LRC", + "Karaoke", + "Web Audio", + "Client-side" + ]} + /> + <!-- 2025 --> + <Card + href="https://github.com/pinapelz/tiny-time-tracker" + title="tiny-time-tracker" + body={`• Lightweight game time tracker for Windows +• Uses native APIs for event-based detection +• Web-based dashboard with responsive design +• Tracks detailed gaming statistics and playtime +• Built with Rust and styled with Tailwind CSS`} + language="Rust" + languageColor="#dea584" + image="/ttt.png" + imagAlt="Tiny Time Tracker Web UI" + year="2025" + tags={["Rust", "Askama", "Tailwind", "Windows API", "SQLite"]} + /> + + <Card + href="https://github.com/pinapelz/brokenithm-evolved-ios-umi" + title="brokenithm-evolved-ios-umi" + body={`• Bridge app for iOS rhythm game controllers • Real-time input translation with minimal latency -• Accurate transmission of touch inputs to the game system -• Custom protocol implementations for input capture and LED synchronization -• Enables arcade-style gaming experiences using mobile devices`} - language="Python" - image="/brokenithm.png" - imageAlt="Brokenithm-SwiftUI bridged via USB MUX into UMIGURI" - languageColor="#3572A5" - year="2025" - tags={["Python", "C#"]} - /> - <Card - href="https://github.com/pinapelz/JHolodex" - title="JHolodex" - body={`• Object-oriented Java wrapper library for the Holodex API -• Provides easy access to VTuber and content creator data -• Built with Retrofit2 for efficient HTTP operations -• Clean, intuitive interface for querying channel information -• Access to video metadata and live stream data -• Extensive documentation and comprehensive unit tests -• Published on Maven Central for easy integration -• Follows modern Java development practices and API design patterns`} - language="Java" - languageColor="#b07219" - image="/jholodex.png" - imageAlt="JHolodex Central Repository" - year="2023" - tags={["Java", "Retrofit2", "Maven Central", "REST API"]} - /> - <Card - href="https://github.com/pinapelz/573-updates" - title="573-UPDATES" - body={`• Modular web scraping and news aggregation system for arcade gaming -• Python-based scraper monitoring multiple news sources -• Tracks game updates, patch notes, and community announcements -• Automatic parsing and standardization to JSON format -• React single-page application built with TypeScript -• Intuitive, filterable interface for browsing aggregated news -• Extensible architecture for adding new data sources +• Interfaces with chuniio hardware and UMIGURI LEDs +• Translates LED signals for iOS compatibility +• Enables arcade-style gaming on mobile devices`} + language="Python" + languageColor="#3572A5" + image="/brokenithm.png" + imageAlt="Brokenithm-SwiftUI bridged via USB MUX into UMIGURI" + year="2025" + tags={["Python", "C#"]} + /> + + <Card + href="https://github.com/pinapelz/573-updates" + title="573-UPDATES" + body={`• Web scraper for arcade gaming news and updates +• Tracks patch notes, announcements, and community posts +• Python backend with React frontend for browsing +• Extensible architecture for adding new sources • Automated deployment pipelines for content updates`} - language="Typescript" - languageColor="#3178c6" - year="2025" - image="/573.png" - imageAlt="573-UPDATES site screenshot" - tags={["React", "TypeScript", "Python", "Web Scraping"]} - /> - <Card - href="https://github.com/pinapelz/ffxiv-chronowatcher" - title="ffxiv-chronowatcher" - body={`• Precision-engineered Rust library for FFXIV calculations -• Accurate implementation of Eorzean Time system -• Weather forecasting for all game zones with perfect accuracy -• Complex time conversion algorithms and weather generation systems + language="Typescript" + languageColor="#3178c6" + image="/573.png" + imageAlt="573-UPDATES site screenshot" + year="2025" + tags={["React", "TypeScript", "Python", "Web Scraping"]} + /> + + <!-- 2024 --> + <Card + href="https://github.com/pinapelz/Mirage" + title="Mirage" + body={`• Tracks rhythm game scores across multiple titles +• Supports niche games without official metadata +• Import and backup game progress from different games +• Multi-user system with customizable permissions +• Pre-loaded trackers for several popular rhythm games`} + language="TypeScript" + languageColor="#3178c6" + image="/mirage.png" + imageAlt="Mirage score tracker interface screenshot" + year="2024" + tags={[ + "React", + "TypeScript", + "Express", + "Prisma ORM", + "PostgreSQL", + ]} + /> + + <Card + href="https://github.com/pinapelz/ffxiv-chronowatcher" + title="ffxiv-chronowatcher" + body={`• Rust library for FFXIV time and weather calculations +• Implements Eorzean Time system with perfect accuracy +• Weather forecasting for all game zones • Comprehensive unit testing for reliability -• Extensive documentation with practical examples -• Published on crates.io for easy integration -• Valuable for developers building FFXIV-related tools`} - language="Rust" - languageColor="#dea584" - year="2024" - image="/chronowatcher.png" - imageAlt="Crates.io FFXIV-Chronowatcher" - tags={["Rust", "Crates.io"]} - /> - <Card - href="https://blog.pinapelz.com" - title="Personal Blog" - body={`• Modern, performance-focused personal blog built with Astro -• Platform for technical insights and project updates -• MDX integration combining Markdown with React components -• Rich content with embedded demos and interactive elements -• Clean, responsive design with accessibility compliance -• Excellent SEO optimization and fast loading times -• Content spanning from technical tutorials to industry observations -• Project breakdowns and software development experiences`} - language="Astro" - image="/blog.png" - imageAlt="Personal Blog Site Screenshot" - languageColor="#ff5a03" - year="2023" - tags={["Astro", "MDX", "React"]} - /> - <Card - href="https://github.com/pinapelz/ytmp3AutoTag" - title="ytID3AutoTag" - body={`• Java Swing desktop application for YouTube to MP3 conversion -• Automatic ID3 metadata tagging from video information -• Intelligent analysis of video titles and descriptions -• Auto-population of artist names, song titles, and album info -• User-friendly GUI designed for ease of use -• Batch processing capabilities for multiple downloads -• High audio quality preservation during conversion -• Streamlined workflow for building organized music libraries`} - language="Java" - languageColor="#b07219" - image="/yt.png" - imageAlt="ytId3AutoTag Swing Metadata Editing GUI" - year="2022" - tags={["Java", "Swing"]} - /> - <Card - href="https://github.com/pinapelz/yet-another-lavaplayer-bot" - title="Yet Another Lavaplayer Bot" - body={`• Feature-rich, self-hosted Discord music bot -• Built with Java Discord API (JDA) and Lavaplayer -• Multi-source playback: YouTube, SoundCloud, Bandcamp, etc. -• Advanced queue management and playlist support -• Audio filtering and sound customization features -• Comprehensive command handling system -• User permission controls and moderation features -• High-quality audio streaming for Discord servers`} - language="Java" - languageColor="#b07219" - image="/lavaplayer.png" - imageAlt="Example usage of Discord bot. Able to search + play songs on YouTube" - year="2022" - tags={["Java", "JDA", "Discord Bot", "Lavaplayer", "Audio Streaming", "Async Programming"]} - /> - <Card - href="https://github.com/pinapelz/moekyun-me-link-shortener" - title="Moekyun Link Shortener" - body={`• Self-hosted URL shortening service built with Flask -• Designed for easy deployment on serverless platforms -• Clean, minimalist interface with intuitive controls -• Custom short URL generation and management -• PostgreSQL for reliable data persistence -• Redis caching for frequently accessed URLs -• Fast response times even under heavy load -• 1-click deployment through Vercel for easy setup`} - language="Python" - languageColor="#3572A5" - year="2023" - image="/link.png" - imageAlt="Moekyun Link Shortener Screenshot" - tags={["Python", "Flask", "PostgreSQL", "Redis"]} - /> - <Card - href="https://github.com/pinapelz/ffxiv-malmstone" - title="Malmstone Calculator" - body={`• Specialized FFXIV Dalamud plugin for PvP progression tracking -• Real-time goal-setting functionality within the game interface -• Built with ImGui for seamless UI integration -• Hooks into game data to fetch necessary information -• Calculates matches needed to reach player-defined goals -• Customizable target tracking for different rewards -• Match history analysis and performance metrics -• Time-to-completion estimates based on win rate probabilities`} - language="C#" - languageColor="#178600" - year="2023" - image="/malmstone.png" - imageAlt="FFXIV Malmstone Plugin Screenshot" - tags={["C#", "ImGui"]} - /> - </ul> - <div class="text-center my-12"> - <a href="https://knowledge.pinapelz.com/personal/tools" class="inline-block hover:underline text-2xl animate-pulse transition-all hover:text-blue-300">and also a few smaller tools...</a> - </div> - </main> - <SocialNavbar /> +• Published on crates.io for easy integration`} + language="Rust" + languageColor="#dea584" + image="/chronowatcher.png" + imageAlt="Crates.io FFXIV-Chronowatcher" + year="2024" + tags={["Rust", "Crates.io"]} + /> - <!-- Image Modal --> - <div id="imageModal" class="modal"> - <span class="close-modal">×</span> - <img class="modal-content" id="modalImage"> - <div id="modalCaption"></div> - </div> - <style> - main { - margin: auto; - padding: 2rem 1.5rem; - max-width: 1300px; - color: white; - font-size: 18px; - line-height: 1.6; - } - a { - color: white; - transition: all 0.2s ease; - } - .header-container { - margin-bottom: 2.5rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - padding-bottom: 1rem; - } - .project-grid { - display: grid; - grid-template-columns: repeat(1, 1fr); - gap: 2.5rem; - padding: 0; - list-style: none; - margin-bottom: 2rem; - } + <!-- 2023 --> + <Card + href="https://patchwork.moekyun.me" + title="Patchwork Archive" + body={`• Scalable system for archiving YouTube videos +• React frontend for managing requests and archives +• Distributed workers for processing archival jobs +• Automatic metadata extraction and thumbnail storage +• Efficient storage using S3-compatible backends`} + language="Javascript" + languageColor="#f1e05a" + image="/patchwork.png" + imageAlt="Screenshot of Patchwork Archive site" + year="2023" + tags={["React", "Python", "MySQL", "S3 Storage", "TailwindCSS"]} + /> - /* Bullet point styling */ - .bullet-style { - position: relative; - padding-left: 1.25rem; - margin-bottom: 0.5rem; - line-height: 1.5; - } + <Card + href="https://github.com/pinapelz/NijiTrack" + title="Nijitrack" + body={`• Analytics platform for YouTube channel metrics +• Tracks subscribers, views, and historical trends +• Python backend with Next.js frontend and interactive charts +• Dashboard for comparing multiple channels +• Tools for growth analysis and performance insights`} + language="Python" + languageColor="#3572A5" + image="/phase-tracker.png" + year="2023" + tags={[ + "Python", + "Next.js", + "Tailwind", + "SSR", + "Chart.js", + "PostgreSQL", + "YouTube API", + ]} + /> - .bullet-style::before { - content: "•"; - position: absolute; - left: 0; - color: rgb(139, 92, 246); - font-weight: bold; - } + <Card + href="https://github.com/pinapelz/JHolodex" + title="JHolodex" + body={`• Java wrapper for the Holodex API +• Simplifies access to VTuber and creator data +• Built with Retrofit2 for efficient HTTP calls +• Exposes video metadata and live stream info +• Published on Maven Central with docs and tests`} + language="Java" + languageColor="#b07219" + image="/jholodex.png" + imageAlt="JHolodex Central Repository" + year="2023" + tags={["Java", "Retrofit2", "Maven Central", "REST API"]} + /> - @media (min-width: 768px) { - .project-grid { - grid-template-columns: repeat(2, 1fr); - gap: 2rem 2.5rem; - } - } + <Card + href="https://blog.pinapelz.com" + title="Personal Blog" + body={`• Performance-focused blog built with Astro +• Combines MDX with React components for richer posts +• Technical tutorials, project write-ups, and demos +• Optimized for SEO and fast loading times`} + language="Astro" + languageColor="#ff5a03" + image="/blog.png" + imageAlt="Personal Blog Site Screenshot" + year="2023" + tags={["Astro", "MDX", "React"]} + /> - @media (min-width: 1200px) { - .project-grid { - grid-template-columns: repeat(2, 1fr); - gap: 3rem; - } - } + <Card + href="https://github.com/pinapelz/ffxiv-malmstone" + title="Malmstone Calculator" + body={`• FFXIV Dalamud plugin for PvP progression tracking +• Calculates matches needed to reach player goals +• Real-time goal tracking and match history analysis +• Built with ImGui for in-game UI integration +• Provides time-to-completion estimates based on win rate`} + language="C#" + languageColor="#178600" + image="/malmstone.png" + imageAlt="FFXIV Malmstone Plugin Screenshot" + year="2023" + tags={["C#", "ImGui"]} + /> - /* Modal Styles */ - .modal { - display: none; - position: fixed; - z-index: 1500; - padding-top: 50px; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(0, 0, 0, 0.9); - opacity: 0; - transition: opacity 0.3s ease; - } + <!-- 2022 --> + <Card + href="https://github.com/pinapelz/ytmp3AutoTag" + title="ytID3AutoTag" + body={`• Java Swing app for YouTube-to-MP3 conversion +• Automatically tags ID3 metadata from video info +• Batch processing for many files at once +• Preserves audio quality during conversion +• GUI optimized for quick metadata edits`} + language="Java" + languageColor="#b07219" + image="/yt.png" + imageAlt="ytId3AutoTag Swing Metadata Editing GUI" + year="2022" + tags={["Java", "Swing"]} + /> - .modal-visible { - opacity: 1; - display: block; - } + <Card + href="https://github.com/pinapelz/yet-another-lavaplayer-bot" + title="Yet Another Lavaplayer Bot" + body={`• Self-hosted Discord music bot with multi-source playback +• Supports YouTube, SoundCloud, Bandcamp, and more +• Advanced queue management and audio filtering +• Built with JDA and Lavaplayer for reliable streaming +• Includes permission controls and moderation features`} + language="Java" + languageColor="#b07219" + image="/lavaplayer.png" + imageAlt="Example usage of Discord bot. Able to search + play songs on YouTube" + year="2022" + tags={[ + "Java", + "JDA", + "Discord Bot", + "Lavaplayer", + "Audio Streaming", + "Async Programming", + ]} + /> + </ul> - .modal-content { - margin: auto; - display: block; - max-width: 90%; - max-height: 80vh; - opacity: 0; - transition: opacity 0.3s ease; - } + <div class="text-center my-12"> + <a + href="https://knowledge.pinapelz.com/personal/tools" + class="inline-block hover:underline text-2xl animate-pulse transition-all hover:text-blue-300" + >and also a few smaller tools...</a + > + </div> + </main> - .modal-content-visible { - opacity: 1; - } + <SocialNavbar /> - .close-modal { - position: absolute; - top: 15px; - right: 35px; - color: #f1f1f1; - font-size: 40px; - font-weight: bold; - transition: 0.3s; - cursor: pointer; - z-index: 1001; - } + <!-- Image Modal --> + <div id="imageModal" class="modal"> + <span class="close-modal">×</span> + <img class="modal-content" id="modalImage" /> + <div id="modalCaption"></div> + </div> - .close-modal:hover, - .close-modal:focus { - color: #bbb; - text-decoration: none; - cursor: pointer; - } + <style> + main { + margin: auto; + padding: 2rem 1.5rem; + max-width: 1300px; + color: white; + font-size: 18px; + line-height: 1.6; + } + a { + color: white; + transition: all 0.2s ease; + } + .header-container { + margin-bottom: 2.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + padding-bottom: 1rem; + } + .project-grid { + display: grid; + grid-template-columns: repeat(1, 1fr); + gap: 2.5rem; + padding: 0; + list-style: none; + margin-bottom: 2rem; + } - #modalCaption { - margin: auto; - display: block; - width: 80%; - max-width: 700px; - text-align: center; - color: #ccc; - padding: 10px 0; - height: 150px; - } - </style> + /* Bullet point styling */ + .bullet-style { + position: relative; + padding-left: 1.25rem; + margin-bottom: 0.5rem; + line-height: 1.5; + } - <script> - document.addEventListener('DOMContentLoaded', function() { - // Get all project images - const projectImages = document.querySelectorAll('.image-container'); - const modal = document.getElementById('imageModal'); - const modalImg = document.getElementById('modalImage'); - const modalCaption = document.getElementById('modalCaption'); - const closeBtn = document.querySelector('.close-modal'); + .bullet-style::before { + content: "•"; + position: absolute; + left: 0; + color: rgb(139, 92, 246); + font-weight: bold; + } - // Add click event to each project image - projectImages.forEach(imgContainer => { - imgContainer.addEventListener('click', function(e) { - e.preventDefault(); - e.stopPropagation(); + @media (min-width: 768px) { + .project-grid { + grid-template-columns: repeat(2, 1fr); + gap: 2rem 2.5rem; + } + } - const img = this.querySelector('img'); - modal.style.display = "block"; - setTimeout(() => { - modal.classList.add('modal-visible'); - }, 10); + @media (min-width: 1200px) { + .project-grid { + grid-template-columns: repeat(2, 1fr); + gap: 3rem; + } + } - modalImg.src = img.src; - modalImg.alt = img.alt; - setTimeout(() => { - modalImg.classList.add('modal-content-visible'); - }, 50); + /* Modal Styles */ + .modal { + display: none; + position: fixed; + z-index: 1500; + padding-top: 50px; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.9); + opacity: 0; + transition: opacity 0.3s ease; + } - modalCaption.textContent = img.alt; + .modal-visible { + opacity: 1; + display: block; + } - return false; - }); - }); + .modal-content { + margin: auto; + display: block; + max-width: 90%; + max-height: 80vh; + opacity: 0; + transition: opacity 0.3s ease; + } - // Close modal when clicking the X - closeBtn.addEventListener('click', closeModal); + .modal-content-visible { + opacity: 1; + } - // Close modal when clicking outside the image - modal.addEventListener('click', function(event) { - if (event.target === modal) { - closeModal(); + .close-modal { + position: absolute; + top: 15px; + right: 35px; + color: #f1f1f1; + font-size: 40px; + font-weight: bold; + transition: 0.3s; + cursor: pointer; + z-index: 1001; } - }); - // Close modal with Escape key - document.addEventListener('keydown', function(event) { - if (event.key === 'Escape' && modal.style.display === 'block') { - closeModal(); + .close-modal:hover, + .close-modal:focus { + color: #bbb; + text-decoration: none; + cursor: pointer; } - }); - function closeModal() { - modal.classList.remove('modal-visible'); - modalImg.classList.remove('modal-content-visible'); - setTimeout(() => { - modal.style.display = "none"; - }, 300); - } - }); - </script> + #modalCaption { + margin: auto; + display: block; + width: 80%; + max-width: 700px; + text-align: center; + color: #ccc; + padding: 10px 0; + height: 150px; + } + </style> + + <script> + document.addEventListener("DOMContentLoaded", function () { + // Get all project images + const projectImages = document.querySelectorAll(".image-container"); + const modal = document.getElementById("imageModal"); + const modalImg = document.getElementById("modalImage"); + const modalCaption = document.getElementById("modalCaption"); + const closeBtn = document.querySelector(".close-modal"); + + // Add click event to each project image + projectImages.forEach((imgContainer) => { + imgContainer.addEventListener("click", function (e) { + e.preventDefault(); + e.stopPropagation(); + + const img = this.querySelector("img"); + modal.style.display = "block"; + setTimeout(() => { + modal.classList.add("modal-visible"); + }, 10); + + modalImg.src = img.src; + modalImg.alt = img.alt; + setTimeout(() => { + modalImg.classList.add("modal-content-visible"); + }, 50); + + modalCaption.textContent = img.alt; + + return false; + }); + }); + + // Close modal when clicking the X + closeBtn.addEventListener("click", closeModal); + + // Close modal when clicking outside the image + modal.addEventListener("click", function (event) { + if (event.target === modal) { + closeModal(); + } + }); + + // Close modal with Escape key + document.addEventListener("keydown", function (event) { + if (event.key === "Escape" && modal.style.display === "block") { + closeModal(); + } + }); + + function closeModal() { + modal.classList.remove("modal-visible"); + modalImg.classList.remove("modal-content-visible"); + setTimeout(() => { + modal.style.display = "none"; + }, 300); + } + }); + </script> </Layout> |
