2525 font-family : 'Inter' , -apple-system, BlinkMacSystemFont, 'Segoe UI' , sans-serif;
2626 background : linear-gradient (180deg , var (--bg-top ) 0% , var (--bg-bottom ) 100% );
2727 min-height : 100vh ;
28- overflow-x : hidden;
28+ overflow : hidden;
2929 color : var (--text-primary );
3030 }
3131
32+ @media (max-width : 768px ) {
33+ body { overflow-y : auto; }
34+ }
35+
3236 /* ── Navigation ── */
3337 nav {
3438 position : fixed;
345349 }
346350
347351 .hero-visual {
348- min-height : 400px ;
352+ min-height : 350px ;
353+ max-height : 400px ;
349354 order : -1 ;
350355 }
351356
357+ # three-canvas {
358+ top : -15% ;
359+ left : -15% ;
360+ width : 130% ;
361+ height : 130% ;
362+ }
363+
352364 .hero-features {
353365 justify-content : center;
354366 }
364376 }
365377
366378 @media (max-width : 768px ) {
367- nav { padding : 0 1.25rem ; }
379+ nav {
380+ padding : 0 1.25rem ;
381+ }
368382
369- .nav-links { display : none; }
383+ .nav-links {
384+ display : flex;
385+ flex-direction : column;
386+ position : absolute;
387+ top : 72px ;
388+ left : 0 ;
389+ right : 0 ;
390+ background : var (--bg-top );
391+ gap : 0 ;
392+ padding : 0.5rem 1.25rem 1rem ;
393+ border-bottom : 1px solid var (--border );
394+ box-shadow : 0 8px 24px rgba (0 , 0 , 0 , 0.08 );
395+ opacity : 0 ;
396+ visibility : hidden;
397+ transform : translateY (-8px );
398+ transition : opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ;
399+ pointer-events : none;
400+ }
370401
371- .nav-toggle { display : block; }
402+ .nav-links .open {
403+ opacity : 1 ;
404+ visibility : visible;
405+ transform : translateY (0 );
406+ pointer-events : auto;
407+ }
372408
373- .hero-features {
374- flex-direction : column;
375- gap : 1rem ;
409+ .nav-links li {
410+ width : 100% ;
411+ }
412+
413+ .nav-links a {
414+ display : block;
415+ padding : 0.75rem 0 ;
416+ font-size : 1rem ;
417+ border-bottom : 1px solid var (--border );
418+ }
419+
420+ .nav-links li : last-child a {
421+ border-bottom : none;
422+ }
423+
424+ .nav-cta {
425+ display : inline-flex !important ;
376426 align-items : center;
427+ gap : 0.4rem ;
428+ width : fit-content;
429+ margin-top : 0.5rem ;
430+ padding : 0.75rem 1rem !important ;
431+ }
432+
433+ .nav-toggle .open span : nth-child (1 ) {
434+ transform : rotate (45deg ) translate (4px , 4px );
435+ }
436+ .nav-toggle .open span : nth-child (2 ) {
437+ opacity : 0 ;
438+ }
439+ .nav-toggle .open span : nth-child (3 ) {
440+ transform : rotate (-45deg ) translate (4px , -4px );
441+ }
442+
443+ .nav-toggle { display : block; }
444+
445+ .hero {
446+ padding-top : 60px ;
447+ padding-bottom : 2rem ;
377448 }
378449
379450 .hero-visual {
380- min-height : 320px ;
451+ min-height : 240px ;
452+ max-height : 280px ;
453+ aspect-ratio : auto;
454+ }
455+
456+ # three-canvas {
457+ top : -10% ;
458+ left : -10% ;
459+ width : 120% ;
460+ height : 120% ;
461+ }
462+
463+ .hero-badge { margin-bottom : 1rem ; }
464+
465+ .hero-code { display : none; }
466+
467+ .hero-title {
468+ font-size : 2.4rem ;
469+ margin-bottom : 1rem ;
470+ }
471+
472+ .hero-description {
473+ font-size : 0.95rem ;
474+ margin-bottom : 1.5rem ;
381475 }
382476
383477 .hero-actions {
384478 flex-direction : column;
385479 width : 100% ;
480+ margin-bottom : 2rem ;
386481 }
387482
388483 .btn { width : 100% ; justify-content : center; }
484+
485+ .hero-features {
486+ flex-direction : row;
487+ flex-wrap : wrap;
488+ gap : 1rem 2rem ;
489+ justify-content : center;
490+ margin-top : 1.5rem ;
491+ padding-top : 1rem ;
492+ }
493+
494+ .hero-feature { align-items : center; }
389495 }
390496 </ style >
391497</ head >
@@ -484,6 +590,16 @@ <h1 class="hero-title">
484590 }
485591 }
486592 </ script >
593+ < script >
594+ // ── Mobile nav toggle ──
595+ const navToggle = document . querySelector ( '.nav-toggle' ) ;
596+ const navLinks = document . querySelector ( '.nav-links' ) ;
597+ navToggle . addEventListener ( 'click' , ( ) => {
598+ navToggle . classList . toggle ( 'open' ) ;
599+ navLinks . classList . toggle ( 'open' ) ;
600+ } ) ;
601+ </ script >
602+
487603 < script type ="module ">
488604 import * as THREE from 'three' ;
489605
@@ -781,6 +897,39 @@ <h1 class="hero-title">
781897 isHovering = false ;
782898 } ) ;
783899
900+ // Touch support — tap to explode, tap again or wait to reassemble
901+ let touchActive = false ;
902+ renderer . domElement . addEventListener ( 'touchstart' , ( e ) => {
903+ const touch = e . touches [ 0 ] ;
904+ const rect = container . getBoundingClientRect ( ) ;
905+ mouse . x = ( ( touch . clientX - rect . left ) / rect . width ) * 2 - 1 ;
906+ mouse . y = - ( ( touch . clientY - rect . top ) / rect . height ) * 2 + 1 ;
907+
908+ // Check if touch hits the sphere
909+ raycaster . setFromCamera ( mouse , camera ) ;
910+ const hits = raycaster . intersectObject ( hitSphere ) ;
911+ if ( hits . length > 0 ) {
912+ touchActive = ! touchActive ;
913+ isHovering = touchActive ;
914+ e . preventDefault ( ) ;
915+ }
916+ } , { passive : false } ) ;
917+
918+ renderer . domElement . addEventListener ( 'touchmove' , ( e ) => {
919+ if ( ! touchActive ) return ;
920+ const touch = e . touches [ 0 ] ;
921+ const rect = container . getBoundingClientRect ( ) ;
922+ if ( prevMouseX !== null ) {
923+ mouseVelX = touch . clientX - prevMouseX ;
924+ }
925+ prevMouseX = touch . clientX ;
926+ e . preventDefault ( ) ;
927+ } , { passive : false } ) ;
928+
929+ renderer . domElement . addEventListener ( 'touchend' , ( ) => {
930+ prevMouseX = null ;
931+ } ) ;
932+
784933 function easeOutQuart ( t ) { return 1 - Math . pow ( 1 - t , 4 ) ; }
785934 function easeInOutCubic ( t ) {
786935 return t < 0.5 ? 4 * t * t * t : 1 - Math . pow ( - 2 * t + 2 , 3 ) / 2 ;
@@ -791,9 +940,12 @@ <h1 class="hero-title">
791940 const dt = clock . getDelta ( ) ;
792941 const elapsed = clock . getElapsedTime ( ) ;
793942
794- raycaster . setFromCamera ( mouse , camera ) ;
795- const hits = raycaster . intersectObject ( hitSphere ) ;
796- isHovering = hits . length > 0 ;
943+ // On touch devices, isHovering is toggled by tap; on desktop, by raycasting
944+ if ( ! touchActive ) {
945+ raycaster . setFromCamera ( mouse , camera ) ;
946+ const hits = raycaster . intersectObject ( hitSphere ) ;
947+ isHovering = hits . length > 0 ;
948+ }
797949
798950 if ( isHovering ) {
799951 displaceFactor = Math . min ( 1 , displaceFactor + dt * EXPAND_SPEED ) ;
0 commit comments