4242 display : flex;
4343 align-items : center;
4444 gap : 0.1rem ;
45- z-index : 100 ;
45+ z-index : 300 ;
4646 }
4747
4848 .theme-btn {
7272 text-decoration : none;
7373 letter-spacing : 0.06em ;
7474 transition : color 0.2s ease;
75- z-index : 100 ;
75+ z-index : 300 ;
7676 }
7777
7878 .back-link : hover { color : var (--muted ); }
8484 background : var (--bg );
8585 color : var (--text );
8686 line-height : 1.6 ;
87+ transition : background 0.2s ease, color 0.2s ease;
8788 }
8889
8990 /* ── Layout ── */
161162 line-height : 1.75 ;
162163 }
163164
164- .about-text + .about-text {
165- margin-top : 1rem ;
166- }
165+ .about-text + .about-text { margin-top : 1rem ; }
167166
168167 /* ── Skills ── */
169168 .skills-grid {
201200 flex-direction : column;
202201 gap : 0.5rem ;
203202 transition : background 0.2s ease;
204- cursor : pointer;
205203 }
206204
207205 .project-card : hover { background : var (--bg2 ); }
251249 letter-spacing : 0.04em ;
252250 }
253251
254- /* Screenshot placeholder */
252+ /* ── Project image ── */
255253 .project-img {
256254 width : 100% ;
257255 aspect-ratio : 16 / 9 ;
258256 background : var (--tag-bg );
259- border : 1px dashed var (--line );
257+ border : 1px solid var (--line );
260258 display : flex;
261259 align-items : center;
262260 justify-content : center;
263- margin-top : 0.5 rem ;
261+ margin-top : 0.75 rem ;
264262 overflow : hidden;
265263 position : relative;
264+ border-radius : 2px ;
265+ cursor : zoom-in;
266266 }
267267
268268 .project-img img {
269269 width : 100% ;
270270 height : 100% ;
271271 object-fit : cover;
272+ object-position : top;
272273 display : block;
274+ transition : transform 0.4s ease;
273275 }
274276
277+ .project-card : hover .project-img img { transform : scale (1.02 ); }
278+
275279 .project-img-label {
276280 font-family : 'DM Mono' , monospace;
277281 font-size : 0.65rem ;
283287
284288 .project-img img ~ .project-img-label { display : none; }
285289
290+ /* ── Lightbox ── */
291+ .lightbox {
292+ position : fixed;
293+ inset : 0 ;
294+ z-index : 500 ;
295+ background : rgba (0 , 0 , 0 , 0.88 );
296+ display : none;
297+ flex-direction : column;
298+ align-items : center;
299+ justify-content : center;
300+ padding : 2rem ;
301+ }
302+
303+ .lightbox .open { display : flex; }
304+
305+ .lightbox-inner {
306+ position : relative;
307+ max-width : min (900px , 92vw );
308+ width : 100% ;
309+ display : flex;
310+ flex-direction : column;
311+ align-items : center;
312+ gap : 1.25rem ;
313+ }
314+
315+ .lightbox-img-wrap {
316+ width : 100% ;
317+ position : relative;
318+ }
319+
320+ .lightbox-img-wrap img {
321+ width : 100% ;
322+ max-height : 70vh ;
323+ object-fit : contain;
324+ display : block;
325+ border-radius : 2px ;
326+ }
327+
328+ /* nav arrows */
329+ .lightbox-nav {
330+ display : flex;
331+ align-items : center;
332+ justify-content : space-between;
333+ width : 100% ;
334+ }
335+
336+ .lb-arrow {
337+ font-family : 'DM Mono' , monospace;
338+ font-size : 0.75rem ;
339+ color : rgba (255 , 255 , 255 , 0.35 );
340+ background : none;
341+ border : none;
342+ cursor : pointer;
343+ letter-spacing : 0.08em ;
344+ padding : 0.5rem 0 ;
345+ transition : color 0.2s ease;
346+ user-select : none;
347+ }
348+
349+ .lb-arrow : hover { color : rgba (255 , 255 , 255 , 0.85 ); }
350+ .lb-arrow : disabled { opacity : 0 ; pointer-events : none; }
351+
352+ .lb-counter {
353+ font-family : 'DM Mono' , monospace;
354+ font-size : 0.65rem ;
355+ color : rgba (255 , 255 , 255 , 0.3 );
356+ letter-spacing : 0.1em ;
357+ }
358+
359+ .lb-caption {
360+ font-family : 'DM Mono' , monospace;
361+ font-size : 0.65rem ;
362+ color : rgba (255 , 255 , 255 , 0.4 );
363+ letter-spacing : 0.08em ;
364+ text-transform : uppercase;
365+ text-align : center;
366+ }
367+
368+ .lb-close {
369+ position : absolute;
370+ top : -0.25rem ;
371+ right : 0 ;
372+ font-family : 'DM Mono' , monospace;
373+ font-size : 0.7rem ;
374+ color : rgba (255 , 255 , 255 , 0.3 );
375+ background : none;
376+ border : none;
377+ cursor : pointer;
378+ letter-spacing : 0.08em ;
379+ transition : color 0.2s ease;
380+ padding : 0.25rem 0 ;
381+ }
382+
383+ .lb-close : hover { color : rgba (255 , 255 , 255 , 0.8 ); }
384+
286385 /* ── Contact ── */
287386 .contact-list {
288387 display : flex;
358457 <!-- Back link -->
359458 < a class ="back-link " href ="/index.html "> ← index</ a >
360459
460+ <!-- Lightbox -->
461+ < div class ="lightbox " id ="lightbox " onclick ="handleLbClick(event) ">
462+ < div class ="lightbox-inner ">
463+ < button class ="lb-close " onclick ="closeLightbox() "> close ×</ button >
464+ < div class ="lightbox-img-wrap ">
465+ < img id ="lb-img " src ="" alt ="">
466+ </ div >
467+ < div class ="lightbox-nav ">
468+ < button class ="lb-arrow " id ="lb-prev " onclick ="lbNav(-1) "> ← prev</ button >
469+ < span class ="lb-counter " id ="lb-counter "> </ span >
470+ < button class ="lb-arrow " id ="lb-next " onclick ="lbNav(1) "> next →</ button >
471+ </ div >
472+ < p class ="lb-caption " id ="lb-caption "> </ p >
473+ </ div >
474+ </ div >
475+
361476 < div class ="page ">
362477
363478 <!-- Header -->
364479 < header >
365480 < p class ="header-label "> Portfolio</ p >
366481 < h1 > Yoshiro</ h1 >
367- < p class ="tagline "> Homelab dev & computer designer. High schooler building things that actually run .</ p >
482+ < p class ="tagline "> Homelab dev & computer designer. High schooler building things that i actually use .</ p >
368483 </ header >
369484
370485 <!-- About -->
@@ -411,7 +526,7 @@ <h1>Yoshiro</h1>
411526 < span class ="spec "> 1.21.1</ span >
412527 < span class ="spec "> modded</ span >
413528 </ div >
414- < div class ="project-img ">
529+ < div class ="project-img " onclick =" openLightbox('images/mc-screenshot.png', 'Modded Minecraft Server') " >
415530 < img src ="images/mc-screenshot.png " alt ="Minecraft server ">
416531 < span class ="project-img-label "> add screenshot</ span >
417532 </ div >
@@ -429,7 +544,7 @@ <h1>Yoshiro</h1>
429544 < span class ="spec "> 2×16TB HDD</ span >
430545 < span class ="spec "> 1TB SSD</ span >
431546 </ div >
432- < div class ="project-img ">
547+ < div class ="project-img " onclick =" openLightbox('images/homelab.png', 'DIY Homelab') " >
433548 < img src ="images/homelab.png " alt ="Homelab build ">
434549 < span class ="project-img-label "> add photo</ span >
435550 </ div >
@@ -446,7 +561,7 @@ <h1>Yoshiro</h1>
446561 < span class ="spec "> DietPi</ span >
447562 < span class ="spec "> cluster</ span >
448563 </ div >
449- < div class ="project-img ">
564+ < div class ="project-img " onclick =" openLightbox('images/pi-cluster.jpg', 'Raspberry Pi Cluster') " >
450565 < img src ="images/pi-cluster.jpg " alt ="Pi cluster ">
451566 < span class ="project-img-label "> add photo</ span >
452567 </ div >
@@ -459,14 +574,14 @@ <h1>Yoshiro</h1>
459574 < section id ="contact ">
460575 < p class ="section-label "> Get in touch</ p >
461576 < div class ="contact-list ">
462- < a class ="contact-item " href ="https://github.com/yoshiro " target ="_blank " rel ="noopener ">
577+ < a class ="contact-item " href ="https://github.com/YoshiroMaximus " target ="_blank " rel ="noopener ">
463578 < span class ="contact-platform "> GitHub</ span >
464- < span class ="contact-value "> github.com/yoshiro </ span >
579+ < span class ="contact-value "> github.com/YoshiroMaximus </ span >
465580 < span class ="contact-arrow "> →</ span >
466581 </ a >
467- < a class ="contact-item " href ="/cdn-cgi/l/email-protection#dda4b2a89db8b0bcb4b1f3beb2b0 ">
582+ < a class ="contact-item " href ="mailto:nico@sendan.com ">
468583 < span class ="contact-platform "> Email</ span >
469- < span class ="contact-value "> < span class =" __cf_email__ " data-cfemail =" 9fe6f0eadffaf2fef6f3b1fcf0f2 " > [email protected] </ span > </ span >
584+ < span class ="contact-value "> nico@sendan.com </ span >
470585 < span class ="contact-arrow "> →</ span >
471586 </ a >
472587 </ div >
@@ -480,17 +595,17 @@ <h1>Yoshiro</h1>
480595 </ div >
481596
482597 < script >
598+ // ── Theme ──
483599 function setTheme ( theme ) {
484600 document . body . classList . toggle ( 'dark' , theme === 'dark' ) ;
485601 document . getElementById ( 'btn-light' ) . classList . toggle ( 'active' , theme === 'light' ) ;
486602 document . getElementById ( 'btn-dark' ) . classList . toggle ( 'active' , theme === 'dark' ) ;
487603 localStorage . setItem ( 'theme' , theme ) ;
488604 }
489-
490- // Load saved theme or default to light
491605 const saved = localStorage . getItem ( 'theme' ) || 'light' ;
492606 setTheme ( saved ) ;
493607
608+ // ── Scroll observer ──
494609 const observer = new IntersectionObserver ( ( entries ) => {
495610 entries . forEach ( entry => {
496611 if ( entry . isIntersecting ) {
@@ -499,8 +614,61 @@ <h1>Yoshiro</h1>
499614 }
500615 } ) ;
501616 } , { threshold : 0.1 } ) ;
502-
503617 document . querySelectorAll ( 'section' ) . forEach ( el => observer . observe ( el ) ) ;
618+
619+ // ── Lightbox ──
620+ // Collect all images from project cards
621+ const gallery = [
622+ { src : 'images/mc-screenshot.png' , caption : 'Modded Minecraft Server' } ,
623+ { src : 'images/homelab.png' , caption : 'DIY Homelab' } ,
624+ { src : 'images/pi-cluster.jpg' , caption : 'Raspberry Pi Cluster' } ,
625+ ] ;
626+
627+ let lbIndex = 0 ;
628+
629+ function openLightbox ( src , caption ) {
630+ lbIndex = gallery . findIndex ( g => g . src === src ) ;
631+ if ( lbIndex === - 1 ) lbIndex = 0 ;
632+ renderLb ( ) ;
633+ const lb = document . getElementById ( 'lightbox' ) ;
634+ lb . style . display = 'flex' ;
635+ requestAnimationFrame ( ( ) => lb . classList . add ( 'open' ) ) ;
636+ document . body . style . overflow = 'hidden' ;
637+ }
638+
639+ function closeLightbox ( ) {
640+ const lb = document . getElementById ( 'lightbox' ) ;
641+ lb . classList . remove ( 'open' ) ;
642+ lb . style . display = 'none' ;
643+ document . body . style . overflow = '' ;
644+ }
645+
646+ function lbNav ( dir ) {
647+ lbIndex = Math . max ( 0 , Math . min ( gallery . length - 1 , lbIndex + dir ) ) ;
648+ renderLb ( ) ;
649+ }
650+
651+ function renderLb ( ) {
652+ const item = gallery [ lbIndex ] ;
653+ document . getElementById ( 'lb-img' ) . src = item . src ;
654+ document . getElementById ( 'lb-img' ) . alt = item . caption ;
655+ document . getElementById ( 'lb-caption' ) . textContent = item . caption ;
656+ document . getElementById ( 'lb-counter' ) . textContent = ( lbIndex + 1 ) + ' / ' + gallery . length ;
657+ document . getElementById ( 'lb-prev' ) . disabled = lbIndex === 0 ;
658+ document . getElementById ( 'lb-next' ) . disabled = lbIndex === gallery . length - 1 ;
659+ }
660+
661+ function handleLbClick ( e ) {
662+ if ( e . target === document . getElementById ( 'lightbox' ) ) closeLightbox ( ) ;
663+ }
664+
665+ document . addEventListener ( 'keydown' , e => {
666+ const lb = document . getElementById ( 'lightbox' ) ;
667+ if ( ! lb . classList . contains ( 'open' ) ) return ;
668+ if ( e . key === 'Escape' ) closeLightbox ( ) ;
669+ if ( e . key === 'ArrowLeft' ) lbNav ( - 1 ) ;
670+ if ( e . key === 'ArrowRight' ) lbNav ( 1 ) ;
671+ } ) ;
504672 </ script >
505673</ body >
506674</ html >
0 commit comments