7676 align-items : center;
7777 justify-content : center;
7878 padding : 20px ;
79+ position : relative;
80+ overflow : hidden;
81+ background : # 0f172a ;
82+ }
83+ [data-theme = "dark" ] .login-wrap { background : # 0f172a ; }
84+
85+ /* ── SPARKLES CANVAS ── */
86+ # sparkles-canvas {
87+ position : absolute;
88+ inset : 0 ;
89+ width : 100% ;
90+ height : 100% ;
91+ z-index : 0 ;
92+ }
93+
94+ /* ── GRADIENT LINE UNDER TITLE ── */
95+ .sparkle-line {
96+ position : relative;
97+ width : 280px ;
98+ height : 40px ;
99+ margin : 16px auto 0 ;
100+ }
101+ .sparkle-line ::before {
102+ content : '' ;
103+ position : absolute;
104+ top : 0 ;
105+ left : 10% ;
106+ right : 10% ;
107+ height : 2px ;
108+ background : linear-gradient (90deg , transparent, # 6366f1, transparent);
109+ }
110+ .sparkle-line ::after {
111+ content : '' ;
112+ position : absolute;
113+ top : 0 ;
114+ left : 25% ;
115+ right : 25% ;
116+ height : 4px ;
117+ background : linear-gradient (90deg , transparent, # 38bdf8, transparent);
118+ filter : blur (2px );
79119 }
80120 .login-container {
81121 width : 100% ;
82122 max-width : 440px ;
83123 animation : fadeInUp 0.6s ease-out;
124+ position : relative;
125+ z-index : 1 ;
84126 }
85127
86128 .login-header {
350392 <!-- Login form (hidden until needed) -->
351393 < div id ="staticrypt_content " class ="hidden ">
352394 < div class ="login-wrap ">
395+ < canvas id ="sparkles-canvas "> </ canvas >
353396 < div class ="login-container ">
354397 < div class ="login-header ">
355398 < div class ="icon ">
@@ -365,6 +408,7 @@ <h1>/*[|template_title|]*/0</h1>
365408 < span class ="dot "> </ span >
366409 jmacot.github.io
367410 </ div >
411+ < div class ="sparkle-line "> </ div >
368412 </ div >
369413 < div class ="login-body ">
370414 < form id ="staticrypt-form " action ="# " method ="post ">
@@ -488,6 +532,59 @@ <h1>/*[|template_title|]*/0</h1>
488532 }
489533 } ) ;
490534
535+ // ─────────────────────────────────────────────
536+ // SPARKLES CANVAS
537+ // ─────────────────────────────────────────────
538+ ( function ( ) {
539+ var canvas = document . getElementById ( 'sparkles-canvas' ) ;
540+ if ( ! canvas ) return ;
541+ var ctx = canvas . getContext ( '2d' ) ;
542+ var particles = [ ] ;
543+ var count = 120 ;
544+ var prefersReduced = window . matchMedia ( '(prefers-reduced-motion: reduce)' ) . matches ;
545+ if ( prefersReduced ) return ;
546+
547+ function resize ( ) {
548+ canvas . width = canvas . parentElement . offsetWidth ;
549+ canvas . height = canvas . parentElement . offsetHeight ;
550+ }
551+ resize ( ) ;
552+ window . addEventListener ( 'resize' , resize ) ;
553+
554+ for ( var i = 0 ; i < count ; i ++ ) {
555+ particles . push ( {
556+ x : Math . random ( ) * canvas . width ,
557+ y : Math . random ( ) * canvas . height ,
558+ r : Math . random ( ) * 1.5 + 0.5 ,
559+ dx : ( Math . random ( ) - 0.5 ) * 0.4 ,
560+ dy : ( Math . random ( ) - 0.5 ) * 0.4 ,
561+ opacity : Math . random ( ) ,
562+ opacityDir : ( Math . random ( ) > 0.5 ? 1 : - 1 ) * ( Math . random ( ) * 0.02 + 0.005 )
563+ } ) ;
564+ }
565+
566+ function draw ( ) {
567+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
568+ for ( var i = 0 ; i < particles . length ; i ++ ) {
569+ var p = particles [ i ] ;
570+ p . x += p . dx ;
571+ p . y += p . dy ;
572+ p . opacity += p . opacityDir ;
573+ if ( p . opacity <= 0.1 || p . opacity >= 1 ) p . opacityDir *= - 1 ;
574+ if ( p . x < 0 ) p . x = canvas . width ;
575+ if ( p . x > canvas . width ) p . x = 0 ;
576+ if ( p . y < 0 ) p . y = canvas . height ;
577+ if ( p . y > canvas . height ) p . y = 0 ;
578+ ctx . beginPath ( ) ;
579+ ctx . arc ( p . x , p . y , p . r , 0 , Math . PI * 2 ) ;
580+ ctx . fillStyle = 'rgba(255,255,255,' + p . opacity . toFixed ( 2 ) + ')' ;
581+ ctx . fill ( ) ;
582+ }
583+ requestAnimationFrame ( draw ) ;
584+ }
585+ draw ( ) ;
586+ } ) ( ) ;
587+
491588 // ─────────────────────────────────────────────
492589 // DARK MODE (auto-detect)
493590 // ─────────────────────────────────────────────
0 commit comments