33< head >
44< meta charset ="utf-8 ">
55< meta name ="viewport " content ="width=device-width, initial-scale=1 ">
6+ < meta name ="color-scheme " content ="light ">
67< title > DemoApp</ title >
78< style >
89 : root {
9- --bg : # 0b0f1a ;
10- --card : rgba (255 , 255 , 255 , 0.04 );
11- --border : rgba (255 , 255 , 255 , 0.09 );
12- --text : # f2f5fb ;
13- --muted : # 8b95ad ;
14- --accent : # 5b8cff ;
15- --accent-2 : # b06bff ;
16- --accent-3 : # 2ee6a8 ;
10+ --bg : # eef1f7 ;
11+ --surface : # ffffff ;
12+ --line : # e2e7f1 ;
13+ --line-strong : # cdd5e4 ;
14+ --text : # 101423 ;
15+ --muted : # 5c667d ;
16+ --faint : # 8b95a9 ;
17+ --accent : # 2544d8 ;
18+ --accent-2 : # 6d28d9 ;
19+ --accent-3 : # 0d8f6b ;
20+ --danger : # c62a25 ;
21+ --grid : rgba (16 , 20 , 35 , 0.045 );
1722 }
1823
1924 * { box-sizing : border-box; margin : 0 ; padding : 0 ; }
3237 overflow-x : hidden;
3338 }
3439
40+ /* Deux couches d'ambiance : teintes diffuses + trame technique, pensées pour le fond clair. */
3541 body ::before {
3642 content : "" ;
3743 position : fixed;
3844 inset : -30% ;
3945 background :
40- radial-gradient (45rem 45rem at 20% 15% , rgba (91 , 140 , 255 , 0.20 ), transparent 60% ),
41- radial-gradient (40rem 40rem at 80% 85% , rgba (176 , 107 , 255 , 0.18 ), transparent 60% ),
42- radial-gradient (35rem 35rem at 60% 30% , rgba (46 , 230 , 168 , 0.10 ), transparent 60% );
43- filter : blur (20px );
46+ radial-gradient (45rem 45rem at 18% 12% , rgba (37 , 68 , 216 , 0.10 ), transparent 62% ),
47+ radial-gradient (40rem 40rem at 82% 88% , rgba (109 , 40 , 217 , 0.09 ), transparent 62% ),
48+ radial-gradient (35rem 35rem at 62% 28% , rgba (13 , 143 , 107 , 0.07 ), transparent 62% );
49+ z-index : -2 ;
50+ animation : drift 32s ease-in-out infinite alternate;
51+ }
52+
53+ body ::after {
54+ content : "" ;
55+ position : fixed;
56+ inset : 0 ;
57+ background-image :
58+ linear-gradient (var (--grid ) 1px , transparent 1px ),
59+ linear-gradient (90deg , var (--grid ) 1px , transparent 1px );
60+ background-size : 4rem 4rem ;
61+ mask-image : radial-gradient (70% 60% at 50% 40% , # 000, transparent 100% );
62+ -webkit-mask-image : radial-gradient (70% 60% at 50% 40% , # 000, transparent 100% );
4463 z-index : -1 ;
45- animation : drift 24s ease-in-out infinite alternate;
4664 }
4765
4866 @keyframes drift {
8098
8199 .card {
82100 position : relative;
101+ overflow : hidden;
83102 padding : 2.25rem 2rem ;
84- border : 1px solid var (--border );
103+ border : 1px solid var (--line );
85104 border-radius : 1.25rem ;
86- background : var (--card );
87- backdrop-filter : blur ( 16 px );
88- -webkit-backdrop-filter : blur ( 16 px );
89- box-shadow : 0 1.5rem 3 rem rgba (0 , 0 , 0 , 0.35 );
105+ background : var (--surface );
106+ box-shadow :
107+ 0 1 px 2 px rgba ( 16 , 24 , 40 , 0.05 ) ,
108+ 0 1.25 rem 2. 5rem -1 rem rgba (16 , 24 , 40 , 0.14 );
90109 text-align : center;
91- transition : transform 0.25s ease, border-color 0.25s ease;
110+ transition : transform 0.25s ease, box-shadow 0.25 s ease , border-color 0.25s ease;
92111 }
93112
94- .card : hover { transform : translateY (-4px ); border-color : rgba (255 , 255 , 255 , 0.2 ); }
113+ .card : hover {
114+ transform : translateY (-4px );
115+ border-color : var (--line-strong );
116+ box-shadow :
117+ 0 1px 2px rgba (16 , 24 , 40 , 0.05 ),
118+ 0 2rem 3.5rem -1rem rgba (16 , 24 , 40 , 0.18 );
119+ }
95120
121+ /* Sur fond clair la profondeur vient du liseré, pas d'un halo lumineux. */
96122 .card ::after {
97123 content : "" ;
98124 position : absolute;
99- inset-inline : 15 % ;
100- top : -1 px ;
101- height : 1 px ;
125+ inset-inline : 0 ;
126+ top : 0 ;
127+ height : 2 px ;
102128 background : linear-gradient (90deg , transparent, var (--glow , var (--accent )), transparent);
103129 }
104130
135161 word-break : break-all;
136162 }
137163
164+ /* Progression de la minute en cours : la seule pièce animée par les données. */
165+ .minute {
166+ margin : 1.1rem auto 0 ;
167+ width : 68% ;
168+ height : 3px ;
169+ border-radius : 999px ;
170+ background : var (--line );
171+ overflow : hidden;
172+ }
173+
174+ .minute i {
175+ display : block;
176+ height : 100% ;
177+ width : 0 ;
178+ border-radius : inherit;
179+ background : linear-gradient (90deg , var (--accent ), var (--accent-2 ));
180+ transition : width 0.6s linear;
181+ }
182+
138183 .commit {
139184 margin-top : 0.45rem ;
140185 font-size : 0.68rem ;
141186 letter-spacing : 0.04em ;
142- color : # 6b748c ;
187+ color : var ( --faint ) ;
143188 font-family : ui-monospace, SFMono-Regular, "SF Mono" , Menlo, monospace;
144189 }
145190
146- .commit span { color : # 97a1b8 ; }
191+ .commit span { color : var ( --text ) ; }
147192
148193 .badge {
149- display : inline-block;
194+ display : inline-flex;
195+ align-items : center;
196+ gap : 0.4rem ;
150197 margin-top : 0.9rem ;
151198 padding : 0.3rem 0.75rem ;
152- border : 1px solid var (--border );
199+ border : 1px solid var (--line );
153200 border-radius : 999px ;
201+ background : # f7f9fc ;
154202 font-size : 0.75rem ;
155203 color : var (--muted );
156204 }
157205
206+ .dot {
207+ width : 0.45rem ;
208+ height : 0.45rem ;
209+ border-radius : 50% ;
210+ background : var (--accent-3 );
211+ box-shadow : 0 0 0 3px rgba (13 , 143 , 107 , 0.15 );
212+ animation : beat 2.4s ease-in-out infinite;
213+ }
214+
215+ .dot .ko {
216+ background : var (--danger );
217+ box-shadow : 0 0 0 3px rgba (198 , 42 , 37 , 0.15 );
218+ }
219+
220+ @keyframes beat {
221+ 0% , 100% { opacity : 1 ; }
222+ 50% { opacity : 0.35 ; }
223+ }
224+
158225 footer { color : var (--muted ); font-size : 0.78rem ; letter-spacing : 0.05em ; }
159226
160- .offline { color : # ff7b7b ; }
227+ .offline { color : var (--danger ); }
228+
229+ : focus-visible { outline : 2px solid var (--accent ); outline-offset : 3px ; }
230+
231+ @media (prefers-reduced-motion : reduce) {
232+ body ::before , .dot { animation : none; }
233+ .card , .minute i { transition : none; }
234+ }
161235</ style >
162236</ head >
163237< body >
@@ -171,6 +245,7 @@ <h1>DemoApp</h1>
171245 < div class ="label "> Heure locale</ div >
172246 < div class ="value " id ="time "> {{ time }}</ div >
173247 < div class ="sub " id ="date "> {{ date }}</ div >
248+ < div class ="minute " aria-hidden ="true "> < i id ="minute-fill "> </ i > </ div >
174249 < span class ="badge " id ="tz "> {{ timezone }} (UTC{{ offset[:3] }}:{{ offset[3:] }})</ span >
175250 </ section >
176251
@@ -188,7 +263,9 @@ <h1>DemoApp</h1>
188263 {% if views is none %}< span class ="offline "> redis KO</ span > {% else %}{{ views }}{% endif %}
189264 </ div >
190265 < div class ="sub "> visites enregistrées</ div >
191- < span class ="badge "> Backend Redis</ span >
266+ < span class ="badge ">
267+ < span class ="dot{% if views is none %} ko{% endif %} "> </ span > Backend Redis
268+ </ span >
192269 </ section >
193270 </ main >
194271
@@ -202,6 +279,7 @@ <h1>DemoApp</h1>
202279 function tick ( ) {
203280 const el = document . getElementById ( "time" ) ;
204281 const dateEl = document . getElementById ( "date" ) ;
282+ const fill = document . getElementById ( "minute-fill" ) ;
205283 const now = new Date ( ) ;
206284 try {
207285 el . textContent = now . toLocaleTimeString ( "fr-FR" , { timeZone : tzName , hour12 : false } ) ;
@@ -211,6 +289,8 @@ <h1>DemoApp</h1>
211289 } catch ( e ) {
212290 // timezone inconnue du navigateur : on laisse la valeur serveur
213291 }
292+ // Le décalage horaire ne change pas le rang de la seconde dans la minute.
293+ fill . style . width = ( ( now . getSeconds ( ) + 1 ) / 60 * 100 ) . toFixed ( 2 ) + "%" ;
214294 }
215295
216296 setInterval ( tick , 1000 ) ;
0 commit comments