diff --git a/README.md b/README.md index 5d33e53..d7f7ffa 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Pure static front-end site served by nginx. No build step required. ``` public/ Site root (served by nginx) -├── index.html Single page: home, about, apps, games +├── index.html Single page: home, about, apps, hosted, games ├── favicon.ico └── assets/ ├── css/ douwco.css (branding), style.css (layout) diff --git a/public/assets/css/style.css b/public/assets/css/style.css index d67d35d..34c8e89 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -92,7 +92,7 @@ footer > div > p { font-weight: bold; color: var(--white-clr); text-decoration: none; - font-size: 36px; + font-size: 28px; border: none; border-radius: 20px; @@ -100,7 +100,7 @@ footer > div > p { display: flex; align-items: center; - margin: 10px; + margin: 8px; padding-right: 10px; } @@ -202,328 +202,156 @@ footer > div > p { float: left; } -/* New project card styling - rectangular with image on top */ -.project-card-new { - background-color: var(--background-accent-clr); - border-radius: var(--card-border-radius, 25px); - overflow: hidden; - margin: 10px; - max-width: 400px; - min-width: 300px; - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); - display: flex; - flex-direction: column; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - cursor: pointer; - position: relative; -} - -.project-card-new.expanded { - z-index: 10; - transform: scale(1.02); - box-shadow: 0 12px 35px rgba(0, 0, 0, 0.5); -} - -.project-card-new .toggle-icon { - position: absolute; - top: 10px; - right: 10px; - width: 30px; - height: 30px; - border-radius: 50%; - background-color: var(--green-clr); - color: var(--background-clr); - font-size: 20px; - font-weight: bold; - z-index: 20; - cursor: pointer; - transition: transform 0.2s ease; -} - -.project-card-new .toggle-icon:hover { - transform: scale(1.1); -} - -.project-card-image-container { - position: relative; - width: 100%; - height: 200px; - overflow: hidden; - transition: height 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.project-card-new.expanded .project-card-image-container { - height: 150px; -} - -.project-card-image { - width: 100%; - height: 100%; - object-fit: cover; - background-color: #888; /* Plain grey as fallback */ -} - -.project-card-title-container { - position: relative; - margin-top: -50px; /* Overlap with image */ - padding: 10px 20px; - z-index: 1; -} - -.project-card-title-background { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--background-accent-clr); - border-radius: var(--card-border-radius, 25px); - opacity: 0.95; -} - -.project-card-title { - position: relative; - font-weight: bold; - font-size: 20px; - color: var(--green-clr); - margin: 0; - z-index: 1; -} - -.project-card-content { - padding: 0 20px; - color: var(--text-color); - max-height: 0; - overflow: hidden; - transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), padding-top 0.4s cubic-bezier(0.4, 0, 0.2, 1), padding-bottom 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.project-card-new.expanded .project-card-content { - max-height: 1000px; - padding: 20px; -} - -.project-card-short-desc { - padding: 10px 20px 20px; - color: var(--text-color); - line-height: 1.6; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.project-card-new.expanded .project-card-short-desc { - display: none; -} - -.project-card-content p { - margin: 0; - line-height: 1.6; -} - /* About Me grid layout */ .about-me-projects-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; width: 100%; max-width: 1200px; margin: 20px auto; } -/* ===== Apps Section - Full Width Dashboard Carousel ===== */ +/* ===== Apps & Hosted Sections - vertical selector + detail ===== */ -.apps-container { +.service-section { width: 100%; max-width: 1600px; margin: 0 auto; - padding: 20px; - display: flex; - flex-direction: column; - gap: 30px; -} - -.carousel-container-full { - position: relative; - width: 100%; - padding: 40px 60px; + padding: 0 20px 20px; box-sizing: border-box; -} - -.carousel-track-full { display: flex; - justify-content: center; - align-items: center; - gap: 20px; - width: 100%; + gap: 30px; + align-items: stretch; } -.carousel-item-full { +.service-selector { display: flex; flex-direction: column; + gap: 12px; + width: 260px; + flex-shrink: 0; + max-height: 480px; + overflow-y: auto; + padding: 5px; +} + +.service-selector-item { + display: flex; align-items: center; + gap: 14px; + padding: 12px 16px; + border: 2px solid transparent; + border-radius: 18px; + background-color: var(--background-accent-clr); + color: var(--white-clr); + font-family: 'Montserrat', sans-serif; + font-size: 22px; + font-weight: bold; cursor: pointer; - transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 1; - min-width: 120px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + text-align: left; } -.carousel-item-full[data-selected="true"] { - z-index: 5; +.service-selector-item:hover { + transform: translateY(-2px); + background-color: #3a5c5c; } -.service-icon-large { - width: 100px; - height: 100px; +.service-selector-item.selected { + border-color: var(--green-clr); + background-color: var(--dark-teal, #2c5f5f); +} + +.service-selector-icon { + width: 48px; + height: 48px; + flex-shrink: 0; border-radius: 50%; background-color: var(--icon-bg, #888); display: flex; align-items: center; justify-content: center; - color: var(--white-clr); - font-weight: bold; - font-size: 40px; - transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); -} - -.carousel-item-full[data-selected="true"] .service-icon-large { - width: 150px; - height: 150px; - font-size: 60px; - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5); -} - -.carousel-item-full:nth-child(1):not([data-selected="true"]) .service-icon-large, -.carousel-item-full:nth-child(5):not([data-selected="true"]) .service-icon-large { - width: 80px; - height: 80px; - font-size: 32px; - opacity: 0.7; -} - -.carousel-item-full:nth-child(2):not([data-selected="true"]):not(:nth-child(4)) .service-icon-large, -.carousel-item-full:nth-child(4):not([data-selected="true"]):not(:nth-child(2)) .service-icon-large { - width: 90px; - height: 90px; - font-size: 36px; - opacity: 0.85; -} - -.service-label-large { - color: var(--white-clr); - font-size: 22px; - margin-top: 15px; - opacity: 0.8; - transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); - text-align: center; -} - -.carousel-item-full[data-selected="true"] .service-label-large { - opacity: 1; - font-size: 28px; - font-weight: bold; -} - -.carousel-item-full:nth-child(1):not([data-selected="true"]) .service-label-large, -.carousel-item-full:nth-child(5):not([data-selected="true"]) .service-label-large { - opacity: 0.5; -} - -.carousel-nav-full { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(255, 255, 255, 0.15); - border: none; - color: var(--white-clr); - font-size: 48px; - width: 50px; - height: 50px; - border-radius: 50%; - cursor: pointer; - transition: all 0.3s ease; - z-index: 10; -} - -.carousel-nav-full:hover { - background: rgba(255, 255, 255, 0.25); - transform: translateY(-50%) scale(1.1); -} - -.carousel-nav-full.prev { - left: 20px; -} - -.carousel-nav-full.next { - right: 20px; -} - -/* Service detail area - full width */ -.service-detail-full { - width: 100%; - background-color: var(--dark-green, #1a3d2e); - border-radius: var(--card-border-radius, 25px); - padding: 40px; - box-sizing: border-box; - display: flex; - flex-direction: column; - gap: 30px; -} - -.service-screenshot-full { - width: 100%; - height: 400px; - border-radius: 15px; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; - background-color: #2a2a2a; - transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); } -.service-screenshot-full img { +.service-selector-icon img { width: 100%; height: 100%; - object-fit: cover; - opacity: 0; - transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1); + object-fit: contain; + padding: 5px; + box-sizing: border-box; } -.service-screenshot-full img.active { +.service-selector-item.selected .service-selector-icon { + transform: scale(1.1); +} + +.service-selector-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.service-detail { + flex: 1; + background-color: var(--dark-green, #1a3d2e); + border-radius: var(--card-border-radius, 25px); + padding: 30px; + box-sizing: border-box; + display: flex; + gap: 30px; + align-items: center; + min-width: 0; +} + +.service-visual { + flex: 1 1 45%; + height: clamp(240px, 45vh, 420px); + border-radius: 15px; + overflow: hidden; + background: linear-gradient(145deg, #2c5f5f, #1a3d2e); + display: flex; + align-items: center; + justify-content: center; + min-width: 0; +} + +.service-visual-img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + padding: 24px; + box-sizing: border-box; opacity: 1; + transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1); } -.service-info-full { +.service-visual-img.fading { + opacity: 0; +} + +.service-info { + flex: 1 1 55%; display: flex; flex-direction: column; - align-items: center; - gap: 20px; - text-align: center; + align-items: flex-start; + gap: 16px; + min-width: 0; } .service-title { color: var(--green-clr); font-size: 42px; margin: 0; - opacity: 0; - transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.1s; } -.service-title.active { - opacity: 1; -} - -.service-description-full { +.service-description { color: var(--white-clr); font-size: 24px; line-height: 1.6; - max-width: 800px; - opacity: 0; - transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.2s; -} - -.service-description-full.active { - opacity: 1; -} - -.service-description-full p { margin: 0; } @@ -541,9 +369,7 @@ footer > div > p { cursor: pointer; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); - margin-top: 20px; - opacity: 0; - transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.3s; + margin-top: 10px; } .go-to-app-button:hover { @@ -551,10 +377,6 @@ footer > div > p { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); } -.go-to-app-button.active { - opacity: 1; -} - /* ===== Games Section ===== */ .games-container { @@ -566,7 +388,7 @@ footer > div > p { .games-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 30px; width: 100%; } @@ -587,18 +409,30 @@ footer > div > p { box-shadow: 0 12px 30px rgba(0, 0, 0, 0.4); } +.game-card.expanded { + grid-column: span 2; + z-index: 10; + box-shadow: 0 12px 35px rgba(0, 0, 0, 0.5); +} + .game-card-image { width: 100%; height: 200px; overflow: hidden; - background-color: #888; + background: linear-gradient(145deg, #2c5f5f, #1a3d2e); } .game-card-image img { width: 100%; height: 100%; object-fit: cover; - background-color: #888; +} + +/* Logo-style images (About Me cards): show the full logo instead of cropping */ +.game-card-image.contain img { + object-fit: contain; + padding: 24px; + box-sizing: border-box; } .game-card-content { @@ -606,6 +440,7 @@ footer > div > p { display: flex; flex-direction: column; gap: 10px; + flex: 1; } .game-card-title { @@ -615,6 +450,20 @@ footer > div > p { margin: 0; } +.game-card-meta { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.game-genre { + color: var(--white-clr); + font-size: 14px; + font-weight: bold; + opacity: 0.75; +} + .game-card-description { color: var(--white-clr); font-size: 18px; @@ -680,77 +529,106 @@ footer > div > p { box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); } +.game-card-content .play-button { + align-self: flex-start; + padding: 10px 24px; + font-size: 18px; + margin-top: auto; +} + .play-button:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); } -/* Responsive design for carousel */ +/* Responsive design for the service sections */ @media (max-width: 1024px) { - .apps-container { - padding: 15px; + .service-selector { + width: 220px; } - - .carousel-container-full { - padding: 30px 40px; - } - - .carousel-track-full { - gap: 15px; - } - - .service-icon-large { - width: 80px; - height: 80px; - font-size: 32px; - } - - .carousel-item-full[data-selected="true"] .service-icon-large { - width: 120px; - height: 120px; - font-size: 48px; - } - - .carousel-item-full:nth-child(1):not([data-selected="true"]) .service-icon-large, - .carousel-item-full:nth-child(5):not([data-selected="true"]) .service-icon-large { - width: 60px; - height: 60px; - font-size: 24px; - } - - .carousel-item-full:nth-child(2):not([data-selected="true"]):not(:nth-child(4)) .service-icon-large, - .carousel-item-full:nth-child(4):not([data-selected="true"]):not(:nth-child(2)) .service-icon-large { - width: 70px; - height: 70px; - font-size: 28px; - } - - .service-screenshot-full { - height: 300px; - } - - .service-title { - font-size: 36px; - } - - .service-description-full { - font-size: 20px; - } - - .service-label-large { + + .service-selector-item { font-size: 18px; } - - .carousel-item-full[data-selected="true"] .service-label-large { - font-size: 24px; + + .service-detail { + padding: 20px; + gap: 20px; } - + + .service-visual { + height: clamp(220px, 40vh, 340px); + } + + .service-title { + font-size: 34px; + } + + .service-description { + font-size: 20px; + } + .go-to-app-button { font-size: 24px; padding: 15px 35px; } } +@media (max-width: 900px) { + .service-section { + flex-direction: column; + padding: 0 10px 20px; + gap: 20px; + } + + .service-selector { + flex-direction: row; + width: 100%; + max-height: none; + overflow-x: auto; + overflow-y: hidden; + padding: 2px; + } + + .service-selector-item { + flex-shrink: 0; + font-size: 18px; + padding: 10px 14px; + } + + .service-selector-icon { + width: 36px; + height: 36px; + } + + .service-detail { + flex-direction: column; + align-items: stretch; + } + + .service-visual { + width: 100%; + height: clamp(200px, 35vh, 320px); + } + + .service-info { + align-items: center; + text-align: center; + } +} + +/* Below this width the card grids have a single column: expanding tiles + take the full width instead of spanning two columns. */ +@media (max-width: 740px) { + .game-card.expanded { + grid-column: span 1; + } + + .game-card-full-description { + max-width: 100%; + } +} + /* Currently working on and However in the past sections */ .about-me-section { width: 100%; @@ -778,11 +656,11 @@ footer > div > p { } .icon.small { - height: 45px; + height: 36px; } .icon.medium { - height: 100px; + height: 70px; } .icon.large { diff --git a/public/assets/img/apps/beszel_screenshot.png b/public/assets/img/apps/beszel_screenshot.png deleted file mode 100644 index e69de29..0000000 diff --git a/public/assets/img/apps/gitea_screenshot.png b/public/assets/img/apps/gitea_screenshot.png deleted file mode 100644 index e69de29..0000000 diff --git a/public/assets/img/apps/nextcloud_screenshot.png b/public/assets/img/apps/nextcloud_screenshot.png deleted file mode 100644 index e69de29..0000000 diff --git a/public/assets/img/apps/penpot_screenshot.png b/public/assets/img/apps/penpot_screenshot.png deleted file mode 100644 index e69de29..0000000 diff --git a/public/assets/img/apps/wattif_screenshot.png b/public/assets/img/apps/wattif_screenshot.png deleted file mode 100644 index e69de29..0000000 diff --git a/public/assets/img/findash_dashboard.svg b/public/assets/img/findash_dashboard.svg new file mode 100644 index 0000000..e972019 --- /dev/null +++ b/public/assets/img/findash_dashboard.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + € 1 284,50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/img/games/break-the-isolation.png b/public/assets/img/games/break-the-isolation.png new file mode 100644 index 0000000..4724c53 Binary files /dev/null and b/public/assets/img/games/break-the-isolation.png differ diff --git a/public/assets/img/games/cobor-goes-farming.jpg b/public/assets/img/games/cobor-goes-farming.jpg new file mode 100644 index 0000000..ffc00ee Binary files /dev/null and b/public/assets/img/games/cobor-goes-farming.jpg differ diff --git a/public/assets/img/games/coral-conquest.jpg b/public/assets/img/games/coral-conquest.jpg new file mode 100644 index 0000000..9b2713a Binary files /dev/null and b/public/assets/img/games/coral-conquest.jpg differ diff --git a/public/assets/img/games/cropcopter.jpg b/public/assets/img/games/cropcopter.jpg new file mode 100644 index 0000000..03eda88 Binary files /dev/null and b/public/assets/img/games/cropcopter.jpg differ diff --git a/public/assets/img/games/for-king-for-freedom-and-for-law.jpg b/public/assets/img/games/for-king-for-freedom-and-for-law.jpg new file mode 100644 index 0000000..f942978 Binary files /dev/null and b/public/assets/img/games/for-king-for-freedom-and-for-law.jpg differ diff --git a/public/assets/img/games/gerstenat.png b/public/assets/img/games/gerstenat.png new file mode 100644 index 0000000..17d7e38 Binary files /dev/null and b/public/assets/img/games/gerstenat.png differ diff --git a/public/assets/img/games/loopedy-island.jpg b/public/assets/img/games/loopedy-island.jpg new file mode 100644 index 0000000..f10e89d Binary files /dev/null and b/public/assets/img/games/loopedy-island.jpg differ diff --git a/public/assets/img/games/ludum47_placeholder.png b/public/assets/img/games/ludum47_placeholder.png deleted file mode 100644 index 669fade..0000000 Binary files a/public/assets/img/games/ludum47_placeholder.png and /dev/null differ diff --git a/public/assets/img/games/ludum48_placeholder.png b/public/assets/img/games/ludum48_placeholder.png deleted file mode 100644 index 7ddb957..0000000 Binary files a/public/assets/img/games/ludum48_placeholder.png and /dev/null differ diff --git a/public/assets/img/games/ludum49_placeholder.png b/public/assets/img/games/ludum49_placeholder.png deleted file mode 100644 index a0c33bb..0000000 Binary files a/public/assets/img/games/ludum49_placeholder.png and /dev/null differ diff --git a/public/assets/img/games/ludum50_placeholder.png b/public/assets/img/games/ludum50_placeholder.png deleted file mode 100644 index 281434e..0000000 Binary files a/public/assets/img/games/ludum50_placeholder.png and /dev/null differ diff --git a/public/assets/img/games/ludum51_placeholder.png b/public/assets/img/games/ludum51_placeholder.png deleted file mode 100644 index 480bdea..0000000 Binary files a/public/assets/img/games/ludum51_placeholder.png and /dev/null differ diff --git a/public/assets/img/games/pandaemic.jpg b/public/assets/img/games/pandaemic.jpg new file mode 100644 index 0000000..8e6f1f6 Binary files /dev/null and b/public/assets/img/games/pandaemic.jpg differ diff --git a/public/assets/img/games/romants.png b/public/assets/img/games/romants.png new file mode 100644 index 0000000..a978893 Binary files /dev/null and b/public/assets/img/games/romants.png differ diff --git a/public/assets/img/games/tanks-n-robots.jpg b/public/assets/img/games/tanks-n-robots.jpg new file mode 100644 index 0000000..e9e1a86 Binary files /dev/null and b/public/assets/img/games/tanks-n-robots.jpg differ diff --git a/public/assets/img/games/the-desert-of-nothingness.jpg b/public/assets/img/games/the-desert-of-nothingness.jpg new file mode 100644 index 0000000..a806e50 Binary files /dev/null and b/public/assets/img/games/the-desert-of-nothingness.jpg differ diff --git a/public/assets/img/games/windy-the-indie.jpg b/public/assets/img/games/windy-the-indie.jpg new file mode 100644 index 0000000..f38ee38 Binary files /dev/null and b/public/assets/img/games/windy-the-indie.jpg differ diff --git a/public/assets/img/games/windy_placeholder.png b/public/assets/img/games/windy_placeholder.png deleted file mode 100644 index 3760734..0000000 Binary files a/public/assets/img/games/windy_placeholder.png and /dev/null differ diff --git a/public/assets/img/icons/hosted_icon.svg b/public/assets/img/icons/hosted_icon.svg new file mode 100644 index 0000000..0b50882 --- /dev/null +++ b/public/assets/img/icons/hosted_icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/assets/js/index.js b/public/assets/js/index.js index b7c79da..235e65d 100644 --- a/public/assets/js/index.js +++ b/public/assets/js/index.js @@ -2,9 +2,8 @@ document.addEventListener('DOMContentLoaded', () => { setupNavigationHighlight(); setupNavigationClicks(); setupCopyright(); - setupProjectCards(); - setupAppsCarousel(); - setupGameCards(); + setupServiceSections(); + setupCards(); }); function setupNavigationHighlight() { @@ -47,212 +46,32 @@ function setupCopyright() { document.getElementById('copyright').textContent = `© ${new Date().getFullYear()} - douwco.be`; } -function setupProjectCards() { - const projectCards = document.querySelectorAll('.project-card-new'); - - projectCards.forEach(card => { - const shortDesc = card.querySelector('.project-card-short-desc'); - const fullContent = card.querySelector('.project-card-content'); - const toggleIcon = card.querySelector('.toggle-icon'); - - if (!shortDesc || !fullContent || !toggleIcon) return; - - const toggleCard = (event) => { - if (event.target.tagName === 'A') { +/* Unified expandable card system, shared by the About Me and My Games grids. + Clicking a card expands it (spanning two grid columns); clicking again or + picking another card in the same grid collapses it. */ +function setupCards() { + const cards = document.querySelectorAll('.game-card'); + + cards.forEach(card => { + card.addEventListener('click', (event) => { + if (event.target.closest('a')) { return; } - - const isExpanded = card.classList.contains('expanded'); - - if (isExpanded) { - card.classList.remove('expanded'); - toggleIcon.textContent = '+'; - } else { - card.classList.add('expanded'); - toggleIcon.textContent = '−'; - } - - event.stopPropagation(); - }; - - card.addEventListener('click', toggleCard); - toggleIcon.addEventListener('click', toggleCard); - }); -} -function setupAppsCarousel() { - const carousel = document.getElementById('apps-carousel'); - const carouselContainer = carousel ? carousel.closest('.carousel-container-full') : null; - const items = document.querySelectorAll('#apps-carousel .carousel-item-full'); - const prevBtn = carouselContainer ? carouselContainer.querySelector('.carousel-nav-full.prev') : null; - const nextBtn = carouselContainer ? carouselContainer.querySelector('.carousel-nav-full.next') : null; - const screenshotImg = document.getElementById('apps-screenshot-img'); - const description = document.getElementById('apps-description'); - const title = document.getElementById('apps-title'); - const goToAppBtn = document.getElementById('apps-link'); - - if (!carousel || items.length === 0) return; - - const services = [ - { - name: 'Gitea', - screenshot: '/assets/img/gitea_screenshot.png', - description: 'Self-hosted Git service with a simple and lightweight interface.', - url: 'https://gitea.douwco.be' - }, - { - name: 'Nextcloud', - screenshot: '/assets/img/nextcloud_screenshot.png', - description: 'Private file hosting and collaboration platform with powerful features.', - url: 'https://nextcloud.douwco.be' - }, - { - name: 'Penpot', - screenshot: '/assets/img/penpot_screenshot.png', - description: 'Open-source design and prototyping platform for teams.', - url: 'https://penpot.douwco.be' - }, - { - name: 'Beszel', - screenshot: '/assets/img/beszel_screenshot.png', - description: 'Custom service management and monitoring dashboard.', - url: 'https://beszel.douwco.be' - }, - { - name: 'Wattif', - screenshot: '/assets/img/wattif_screenshot.png', - description: 'Energy monitoring and analysis tool for smart homes.', - url: 'https://wattif.douwco.be' - } - ]; - - let currentIndex = 0; - let isAnimating = false; - - function updateCarousel() { - if (isAnimating) return; - isAnimating = true; - - items.forEach((item, index) => { - const isSelected = index === currentIndex; - item.setAttribute('data-selected', isSelected.toString()); - }); - - const service = services[currentIndex]; - - if (description) description.classList.remove('active'); - if (screenshotImg) screenshotImg.classList.remove('active'); - if (title) title.classList.remove('active'); - if (goToAppBtn) goToAppBtn.classList.remove('active'); - - setTimeout(() => { - if (screenshotImg) { - screenshotImg.src = service.screenshot; - } - if (title) { - title.textContent = service.name; - } - if (description) { - description.innerHTML = `

${service.description}

`; - } - if (goToAppBtn) { - goToAppBtn.href = service.url; - } - - setTimeout(() => { - if (description) description.classList.add('active'); - if (screenshotImg) screenshotImg.classList.add('active'); - if (title) title.classList.add('active'); - if (goToAppBtn) goToAppBtn.classList.add('active'); - isAnimating = false; - }, 50); - }, 300); - } - - function rotateCarousel(direction) { - if (isAnimating) return; - - currentIndex = (currentIndex + direction + items.length) % items.length; - updateCarousel(); - } - - if (prevBtn) { - prevBtn.addEventListener('click', () => rotateCarousel(-1)); - } - - if (nextBtn) { - nextBtn.addEventListener('click', () => rotateCarousel(1)); - } - - items.forEach((item, index) => { - item.addEventListener('click', () => { - if (isAnimating) return; - currentIndex = index; - updateCarousel(); - }); - }); - - let touchStartX = 0; - let touchEndX = 0; - - if (carousel) { - carousel.addEventListener('touchstart', (e) => { - touchStartX = e.changedTouches[0].screenX; - }, { passive: true }); - - carousel.addEventListener('touchend', (e) => { - touchEndX = e.changedTouches[0].screenX; - handleSwipe(); - }, { passive: true }); - } - - function handleSwipe() { - if (isAnimating) return; - - const swipeThreshold = 50; - const diff = touchStartX - touchEndX; - - if (Math.abs(diff) > swipeThreshold) { - if (diff > 0) { - rotateCarousel(1); - } else { - rotateCarousel(-1); - } - } - } - - if (carousel) { - carousel.addEventListener('wheel', (e) => { - if (isAnimating) return; - e.preventDefault(); - if (e.deltaY > 0) { - rotateCarousel(1); - } else { - rotateCarousel(-1); - } - }, { passive: false }); - } - - updateCarousel(); -} - -function setupGameCards() { - const gameCards = document.querySelectorAll('.game-card'); - - gameCards.forEach(card => { - card.addEventListener('click', (event) => { + const grid = card.closest('.games-grid, .about-me-projects-grid'); const isExpanded = card.classList.contains('expanded'); - - if (isExpanded) { - card.classList.remove('expanded'); - } else { - gameCards.forEach(c => c.classList.remove('expanded')); + + if (grid) { + grid.querySelectorAll('.game-card').forEach(c => c.classList.remove('expanded')); + } + + if (!isExpanded) { card.classList.add('expanded'); } - + event.stopPropagation(); }); - + const playButton = card.querySelector('.play-button'); if (playButton) { playButton.addEventListener('click', (event) => { @@ -261,3 +80,47 @@ function setupGameCards() { } }); } + +function setupServiceSections() { + document.querySelectorAll('.service-section').forEach(section => { + const items = section.querySelectorAll('.service-selector-item'); + const visualImg = section.querySelector('.service-visual-img'); + const title = section.querySelector('.service-title'); + const description = section.querySelector('.service-description'); + const link = section.querySelector('.go-to-app-button'); + + if (items.length === 0 || !visualImg || !title || !description || !link) return; + + let isAnimating = false; + + const applyItem = (item) => { + visualImg.src = item.dataset.image; + visualImg.alt = item.dataset.name; + title.textContent = item.dataset.name; + description.textContent = item.dataset.description; + link.href = item.dataset.url; + }; + + const selectItem = (item) => { + if (isAnimating || item.classList.contains('selected')) return; + isAnimating = true; + + items.forEach(i => i.classList.toggle('selected', i === item)); + + visualImg.classList.add('fading'); + setTimeout(() => { + applyItem(item); + requestAnimationFrame(() => { + visualImg.classList.remove('fading'); + setTimeout(() => { + isAnimating = false; + }, 300); + }); + }, 300); + }; + + items.forEach(item => { + item.addEventListener('click', () => selectItem(item)); + }); + }); +} diff --git a/public/index.html b/public/index.html index 181dfce..dcd49ad 100644 --- a/public/index.html +++ b/public/index.html @@ -13,9 +13,9 @@