@@ -164,13 +164,13 @@ function setupContactForm(){
164164/* =========================================================
165165 RENDER: PROJECTS
166166========================================================= */
167- function renderProjects ( ) {
168- const grid = document . getElementById ( "projects-grid" ) ;
169- if ( ! grid || ! STATE . dict ?. projects ) return ;
167+ let PROJECT_INDEX = 0 ;
170168
171- const proj = STATE . dict . projects ;
169+ function getProjectItems ( ) {
170+ const proj = STATE . dict ?. projects ;
171+ if ( ! proj ) return [ ] ;
172172
173- const items = [
173+ return [
174174 {
175175 title : proj . p1_title ,
176176 desc : proj . p1_desc ,
@@ -196,22 +196,107 @@ function renderProjects(){
196196 emoji : "🔍"
197197 }
198198 ] ;
199+ }
199200
200- grid . innerHTML = items . map ( p => `
201+ function clampIndex ( i , len ) {
202+ if ( len <= 0 ) return 0 ;
203+ return ( i + len ) % len ; // wrap circular
204+ }
205+
206+ function renderProjects ( ) {
207+ const stage = document . getElementById ( "project-stage" ) ;
208+ const dots = document . getElementById ( "project-dots" ) ;
209+ if ( ! stage || ! dots ) return ;
210+
211+ const items = getProjectItems ( ) ;
212+ if ( items . length === 0 ) {
213+ stage . innerHTML = "" ;
214+ dots . innerHTML = "" ;
215+ return ;
216+ }
217+
218+ PROJECT_INDEX = clampIndex ( PROJECT_INDEX , items . length ) ;
219+ const p = items [ PROJECT_INDEX ] ;
220+
221+ stage . innerHTML = `
201222 <div class="card project-card">
202- <div class="project-thumb"><span>${ p . emoji } </span></div>
223+ <div class="project-thumb">
224+ <span>${ p . emoji } </span>
225+ </div>
226+
203227 <h3>${ p . title } </h3>
204228 <p>${ p . desc } </p>
205- <div class="tech">${ p . tech . map ( t => `<span>${ t } </span>` ) . join ( "" ) } </div>
229+
230+ <div class="tech">
231+ ${ p . tech . map ( t => `<span>${ t } </span>` ) . join ( "" ) }
232+ </div>
233+
206234 <details>
207- <summary>${ STATE . lang === "es" ? " + info" : "+ info" } </summary>
235+ <summary>+ info</summary>
208236 <p>${ p . why } </p>
209237 </details>
210- <a href="${ p . link } " class="btn btn--primary" target="_blank" rel="noopener noreferrer">GitHub</a>
238+
239+ <a href="${ p . link } " class="btn" target="_blank" rel="noopener noreferrer">GitHub</a>
211240 </div>
241+ ` ;
242+
243+ dots . innerHTML = items . map ( ( _ , idx ) => `
244+ <button
245+ class="carousel-dot ${ idx === PROJECT_INDEX ? "active" : "" } "
246+ aria-label="Ir al proyecto ${ idx + 1 } "
247+ data-idx="${ idx } ">
248+ </button>
212249 ` ) . join ( "" ) ;
250+
251+ // dots click
252+ dots . querySelectorAll ( ".carousel-dot" ) . forEach ( b => {
253+ b . addEventListener ( "click" , ( ) => {
254+ PROJECT_INDEX = Number ( b . getAttribute ( "data-idx" ) ) ;
255+ renderProjects ( ) ;
256+ } ) ;
257+ } ) ;
213258}
214259
260+ // Botones prev/next + swipe
261+ function setupProjectsCarousel ( ) {
262+ const prev = document . getElementById ( "proj-prev" ) ;
263+ const next = document . getElementById ( "proj-next" ) ;
264+ const stage = document . getElementById ( "project-stage" ) ;
265+ if ( ! prev || ! next || ! stage ) return ;
266+
267+ prev . addEventListener ( "click" , ( ) => {
268+ const items = getProjectItems ( ) ;
269+ PROJECT_INDEX = clampIndex ( PROJECT_INDEX - 1 , items . length ) ;
270+ renderProjects ( ) ;
271+ } ) ;
272+
273+ next . addEventListener ( "click" , ( ) => {
274+ const items = getProjectItems ( ) ;
275+ PROJECT_INDEX = clampIndex ( PROJECT_INDEX + 1 , items . length ) ;
276+ renderProjects ( ) ;
277+ } ) ;
278+
279+ // Swipe simple (móvil)
280+ let x0 = null ;
281+ stage . addEventListener ( "touchstart" , ( e ) => {
282+ x0 = e . touches ?. [ 0 ] ?. clientX ?? null ;
283+ } , { passive :true } ) ;
284+
285+ stage . addEventListener ( "touchend" , ( e ) => {
286+ const x1 = e . changedTouches ?. [ 0 ] ?. clientX ?? null ;
287+ if ( x0 == null || x1 == null ) return ;
288+
289+ const dx = x1 - x0 ;
290+ if ( Math . abs ( dx ) < 60 ) return ;
291+
292+ const items = getProjectItems ( ) ;
293+ PROJECT_INDEX = clampIndex ( PROJECT_INDEX + ( dx < 0 ? 1 : - 1 ) , items . length ) ;
294+ renderProjects ( ) ;
295+ x0 = null ;
296+ } , { passive :true } ) ;
297+ }
298+
299+
215300/* =========================================================
216301 RENDER: SKILLS
217302========================================================= */
@@ -285,6 +370,7 @@ document.addEventListener("DOMContentLoaded", () => {
285370 setupSearch ( ) ;
286371 setupContactForm ( ) ;
287372 setupSectionObserver ( ) ;
373+ setupProjectsCarousel ( ) ;
288374
289375 loadDict ( STATE . lang ) ;
290376} ) ;
0 commit comments