Skip to content

Commit 122e92c

Browse files
committed
feat(discover): added slideshow carousel to hero section with navigation controls
1 parent 1b2383d commit 122e92c

File tree

1 file changed

+274
-34
lines changed

1 file changed

+274
-34
lines changed

web/templates/discover.html

Lines changed: 274 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@
8484
margin-bottom: 40px;
8585
}
8686

87+
.hero-slide {
88+
position: absolute;
89+
top: 0;
90+
left: 0;
91+
width: 100%;
92+
height: 100%;
93+
opacity: 0;
94+
transition: opacity 0.8s ease-in-out;
95+
pointer-events: none;
96+
}
97+
98+
.hero-slide.active {
99+
opacity: 1;
100+
pointer-events: auto;
101+
}
102+
87103
.hero-bg {
88104
position: absolute;
89105
top: 0;
@@ -96,7 +112,7 @@
96112
transition: transform 8s ease-out;
97113
}
98114

99-
.hero:hover .hero-bg {
115+
.hero-slide.active .hero-bg {
100116
transform: scale(1.1);
101117
}
102118

@@ -118,6 +134,83 @@
118134
z-index: 10;
119135
}
120136

137+
/* Slideshow Navigation */
138+
.hero-nav {
139+
position: absolute;
140+
bottom: 20px;
141+
left: 50%;
142+
transform: translateX(-50%);
143+
display: flex;
144+
gap: 10px;
145+
z-index: 20;
146+
}
147+
148+
.hero-dot {
149+
width: 10px;
150+
height: 10px;
151+
border-radius: 50%;
152+
background: rgba(255, 255, 255, 0.3);
153+
cursor: pointer;
154+
transition: all 0.3s ease;
155+
border: none;
156+
padding: 0;
157+
}
158+
159+
.hero-dot:hover {
160+
background: rgba(255, 255, 255, 0.6);
161+
}
162+
163+
.hero-dot.active {
164+
background: #667eea;
165+
transform: scale(1.2);
166+
}
167+
168+
.hero-arrow {
169+
position: absolute;
170+
top: 50%;
171+
transform: translateY(-50%);
172+
width: 50px;
173+
height: 50px;
174+
background: rgba(0, 0, 0, 0.5);
175+
border: none;
176+
border-radius: 50%;
177+
color: white;
178+
font-size: 1.5rem;
179+
cursor: pointer;
180+
z-index: 20;
181+
transition: all 0.3s ease;
182+
display: flex;
183+
align-items: center;
184+
justify-content: center;
185+
opacity: 0;
186+
}
187+
188+
.hero:hover .hero-arrow {
189+
opacity: 1;
190+
}
191+
192+
.hero-arrow:hover {
193+
background: rgba(102, 126, 234, 0.8);
194+
}
195+
196+
.hero-arrow-left {
197+
left: 20px;
198+
}
199+
200+
.hero-arrow-right {
201+
right: 20px;
202+
}
203+
204+
.hero-progress {
205+
position: absolute;
206+
bottom: 0;
207+
left: 0;
208+
height: 3px;
209+
background: linear-gradient(90deg, #667eea, #764ba2);
210+
z-index: 20;
211+
transition: width 0.1s linear;
212+
}
213+
121214
.hero-badge {
122215
display: inline-block;
123216
padding: 6px 16px;
@@ -830,12 +923,37 @@
830923

831924
.hero-content {
832925
padding: 0 20px;
926+
bottom: 50px;
833927
}
834928

835929
.hero-title {
836930
font-size: 2rem;
837931
}
838932

933+
.hero-arrow {
934+
width: 40px;
935+
height: 40px;
936+
font-size: 1.2rem;
937+
opacity: 1;
938+
}
939+
940+
.hero-arrow-left {
941+
left: 10px;
942+
}
943+
944+
.hero-arrow-right {
945+
right: 10px;
946+
}
947+
948+
.hero-nav {
949+
bottom: 15px;
950+
}
951+
952+
.hero-dot {
953+
width: 8px;
954+
height: 8px;
955+
}
956+
839957
.container {
840958
padding: 0 20px;
841959
}
@@ -885,44 +1003,64 @@
8851003
</div>
8861004
</nav>
8871005

888-
{% if featured_games %}
889-
{% set hero_game = featured_games[0] %}
890-
{% set hero_screenshots = parse_json(hero_game.igdb_screenshots) %}
891-
{% set hero_bg = hero_screenshots[0] if hero_screenshots else (hero_game.igdb_cover_url or hero_game.cover_image) %}
892-
{% set hero_genres = parse_json(hero_game.genres) %}
893-
894-
<section class="hero">
895-
{% if hero_bg %}
896-
<img src="{{ hero_bg.replace('t_screenshot_big', 't_1080p') if 't_screenshot' in hero_bg else hero_bg }}" alt="" class="hero-bg">
897-
{% endif %}
898-
<div class="hero-gradient"></div>
899-
<div class="hero-content">
900-
<span class="hero-badge">Featured in Your Library</span>
901-
<h1 class="hero-title">{{ hero_game.name }}</h1>
902-
<div class="hero-meta">
903-
{% if hero_game.total_rating %}
904-
<div class="hero-rating">
905-
<span class="hero-rating-score">{{ hero_game.total_rating|round|int }}</span>
906-
<span class="hero-rating-label">IGDB Score</span>
1006+
{% set slideshow_games = featured_games[1:20] if featured_games|length > 1 else [] %}
1007+
{% if slideshow_games|length >= 5 %}
1008+
<section class="hero" id="hero-slideshow">
1009+
{% for game in slideshow_games %}
1010+
{% set screenshots = parse_json(game.igdb_screenshots) %}
1011+
{% set bg = screenshots[0] if screenshots else (game.igdb_cover_url or game.cover_image) %}
1012+
{% set genres = parse_json(game.genres) %}
1013+
<div class="hero-slide{% if loop.first %} active{% endif %}" data-slide-index="{{ loop.index0 }}">
1014+
{% if bg %}
1015+
<img src="{{ bg.replace('t_screenshot_big', 't_1080p') if 't_screenshot' in bg else bg }}" alt="" class="hero-bg">
1016+
{% endif %}
1017+
<div class="hero-gradient"></div>
1018+
<div class="hero-content">
1019+
<span class="hero-badge">
1020+
{% if popularity_source == 'igdb_popularity' %}
1021+
Trending in Your Library
1022+
{% else %}
1023+
Featured in Your Library
1024+
{% endif %}
1025+
</span>
1026+
<h1 class="hero-title">{{ game.name }}</h1>
1027+
<div class="hero-meta">
1028+
{% if game.total_rating %}
1029+
<div class="hero-rating">
1030+
<span class="hero-rating-score">{{ game.total_rating|round|int }}</span>
1031+
<span class="hero-rating-label">IGDB Score</span>
1032+
</div>
1033+
{% endif %}
1034+
{% if genres %}
1035+
<div class="hero-genres">
1036+
{% for genre in genres[:3] %}
1037+
<span class="hero-genre">{{ genre }}</span>
1038+
{% endfor %}
1039+
</div>
1040+
{% endif %}
9071041
</div>
1042+
{% set desc = game.igdb_summary or game.description %}
1043+
{% if desc %}
1044+
<p class="hero-description">{{ desc }}</p>
9081045
{% endif %}
909-
{% if hero_genres %}
910-
<div class="hero-genres">
911-
{% for genre in hero_genres[:3] %}
912-
<span class="hero-genre">{{ genre }}</span>
913-
{% endfor %}
1046+
<div class="hero-actions">
1047+
<a href="/game/{{ game.id }}" class="btn btn-primary">View Details</a>
1048+
<button class="btn btn-secondary" onclick="openExpandedCard({{ game|tojson|e }})">Quick Look</button>
9141049
</div>
915-
{% endif %}
916-
</div>
917-
{% set hero_desc = hero_game.igdb_summary or hero_game.description %}
918-
{% if hero_desc %}
919-
<p class="hero-description">{{ hero_desc }}</p>
920-
{% endif %}
921-
<div class="hero-actions">
922-
<a href="/game/{{ hero_game.id }}" class="btn btn-primary">View Details</a>
923-
<button class="btn btn-secondary" onclick="openExpandedCard({{ hero_game|tojson|e }})">Quick Look</button>
9241050
</div>
9251051
</div>
1052+
{% endfor %}
1053+
1054+
<button class="hero-arrow hero-arrow-left" onclick="prevSlide()">&lsaquo;</button>
1055+
<button class="hero-arrow hero-arrow-right" onclick="nextSlide()">&rsaquo;</button>
1056+
1057+
<div class="hero-nav">
1058+
{% for game in slideshow_games %}
1059+
<button class="hero-dot{% if loop.first %} active{% endif %}" data-slide="{{ loop.index0 }}" onclick="goToSlide({{ loop.index0 }})"></button>
1060+
{% endfor %}
1061+
</div>
1062+
1063+
<div class="hero-progress" id="hero-progress"></div>
9261064
</section>
9271065
{% endif %}
9281066

@@ -1927,8 +2065,110 @@ <h3 class="expanded-card-section-title">Screenshots</h3>
19272065
const row = container.querySelector('.game-row');
19282066
row.addEventListener('scroll', () => updateScrollButtons(container));
19292067
});
2068+
2069+
// Initialize hero slideshow
2070+
initHeroSlideshow();
19302071
});
19312072

2073+
// Hero Slideshow Functions
2074+
let currentSlide = 0;
2075+
let slideCount = 0;
2076+
let slideshowInterval = null;
2077+
let progressInterval = null;
2078+
const SLIDE_DURATION = 7000; // 7 seconds per slide
2079+
const PROGRESS_UPDATE_INTERVAL = 50;
2080+
2081+
function initHeroSlideshow() {
2082+
const hero = document.getElementById('hero-slideshow');
2083+
if (!hero) return;
2084+
2085+
const slides = hero.querySelectorAll('.hero-slide');
2086+
slideCount = slides.length;
2087+
2088+
if (slideCount > 0) {
2089+
startSlideshow();
2090+
2091+
// Pause on hover
2092+
hero.addEventListener('mouseenter', pauseSlideshow);
2093+
hero.addEventListener('mouseleave', startSlideshow);
2094+
}
2095+
}
2096+
2097+
function startSlideshow() {
2098+
if (slideCount <= 1) return;
2099+
2100+
let progressTime = 0;
2101+
const progressBar = document.getElementById('hero-progress');
2102+
2103+
// Clear any existing intervals
2104+
pauseSlideshow();
2105+
2106+
// Progress bar animation
2107+
progressInterval = setInterval(() => {
2108+
progressTime += PROGRESS_UPDATE_INTERVAL;
2109+
const percent = (progressTime / SLIDE_DURATION) * 100;
2110+
if (progressBar) {
2111+
progressBar.style.width = percent + '%';
2112+
}
2113+
}, PROGRESS_UPDATE_INTERVAL);
2114+
2115+
// Auto-advance slides
2116+
slideshowInterval = setInterval(() => {
2117+
nextSlide();
2118+
progressTime = 0;
2119+
}, SLIDE_DURATION);
2120+
}
2121+
2122+
function pauseSlideshow() {
2123+
if (slideshowInterval) {
2124+
clearInterval(slideshowInterval);
2125+
slideshowInterval = null;
2126+
}
2127+
if (progressInterval) {
2128+
clearInterval(progressInterval);
2129+
progressInterval = null;
2130+
}
2131+
}
2132+
2133+
function goToSlide(index) {
2134+
if (index === currentSlide) return;
2135+
2136+
const hero = document.getElementById('hero-slideshow');
2137+
if (!hero) return;
2138+
2139+
const slides = hero.querySelectorAll('.hero-slide');
2140+
const dots = hero.querySelectorAll('.hero-dot');
2141+
const progressBar = document.getElementById('hero-progress');
2142+
2143+
// Update slides
2144+
slides[currentSlide].classList.remove('active');
2145+
slides[index].classList.add('active');
2146+
2147+
// Update dots
2148+
dots[currentSlide].classList.remove('active');
2149+
dots[index].classList.add('active');
2150+
2151+
currentSlide = index;
2152+
2153+
// Reset progress bar
2154+
if (progressBar) {
2155+
progressBar.style.width = '0%';
2156+
}
2157+
2158+
// Restart slideshow timer
2159+
startSlideshow();
2160+
}
2161+
2162+
function nextSlide() {
2163+
const nextIndex = (currentSlide + 1) % slideCount;
2164+
goToSlide(nextIndex);
2165+
}
2166+
2167+
function prevSlide() {
2168+
const prevIndex = (currentSlide - 1 + slideCount) % slideCount;
2169+
goToSlide(prevIndex);
2170+
}
2171+
19322172
// Expanded Card Functions
19332173
let currentGame = null;
19342174

0 commit comments

Comments
 (0)