coderuday21 commited on
Commit
bff40f8
·
1 Parent(s): a4d589b

Sticky nav, avatar dropdown menu, mobile responsive layout

Browse files
Files changed (3) hide show
  1. static/css/style.css +103 -13
  2. static/js/app.js +13 -0
  3. templates/index.html +11 -5
static/css/style.css CHANGED
@@ -339,6 +339,9 @@ input:focus, select:focus, textarea:focus {
339
 
340
  /* ---- Nav ---- */
341
  .nav {
 
 
 
342
  display: flex;
343
  align-items: center;
344
  justify-content: space-between;
@@ -362,25 +365,75 @@ input:focus, select:focus, textarea:focus {
362
  }
363
  .nav-brand svg { stroke: #fff !important; }
364
  .nav-user {
 
 
 
 
 
 
 
 
 
 
 
365
  display: flex;
366
  align-items: center;
367
- gap: 0.75rem;
 
 
368
  }
369
- .user-badge {
370
- font-size: 0.8rem;
371
- color: rgba(255,255,255,0.9);
372
- background: rgba(255,255,255,0.15);
373
- padding: 0.3rem 0.7rem;
374
- border-radius: 20px;
375
- border: 1px solid rgba(255,255,255,0.25);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  }
377
- .nav .btn-ghost {
378
- color: rgba(255,255,255,0.8);
 
 
 
379
  }
380
- .nav .btn-ghost:hover {
381
- color: #fff;
382
- background: rgba(255,255,255,0.1);
 
383
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
  /* ---- Page header ---- */
386
  .page-header { margin-bottom: 1.75rem; }
@@ -778,3 +831,40 @@ input:focus, select:focus, textarea:focus {
778
  .mt-1 { margin-top: 0.5rem; }
779
  .mt-2 { margin-top: 1.25rem; }
780
  .mb-2 { margin-bottom: 1rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
  /* ---- Nav ---- */
341
  .nav {
342
+ position: sticky;
343
+ top: 0;
344
+ z-index: 100;
345
  display: flex;
346
  align-items: center;
347
  justify-content: space-between;
 
365
  }
366
  .nav-brand svg { stroke: #fff !important; }
367
  .nav-user {
368
+ position: relative;
369
+ }
370
+
371
+ /* Avatar button */
372
+ .nav-avatar {
373
+ width: 36px;
374
+ height: 36px;
375
+ border-radius: 50%;
376
+ border: 2px solid rgba(255,255,255,0.35);
377
+ background: rgba(255,255,255,0.15);
378
+ color: #fff;
379
  display: flex;
380
  align-items: center;
381
+ justify-content: center;
382
+ cursor: pointer;
383
+ transition: all var(--transition);
384
  }
385
+ .nav-avatar:hover {
386
+ background: rgba(255,255,255,0.25);
387
+ border-color: rgba(255,255,255,0.5);
388
+ }
389
+
390
+ /* Dropdown */
391
+ .nav-dropdown {
392
+ position: absolute;
393
+ top: calc(100% + 0.5rem);
394
+ right: 0;
395
+ min-width: 220px;
396
+ background: var(--bg-card);
397
+ border: 1px solid var(--border);
398
+ border-radius: var(--radius-sm);
399
+ box-shadow: 0 8px 32px rgba(0,0,0,0.12);
400
+ padding: 0.5rem 0;
401
+ animation: dropIn 0.15s ease;
402
+ z-index: 200;
403
+ }
404
+ @keyframes dropIn {
405
+ from { opacity: 0; transform: translateY(-6px); }
406
+ to { opacity: 1; transform: translateY(0); }
407
  }
408
+ .nav-dropdown-email {
409
+ padding: 0.6rem 1rem;
410
+ font-size: 0.82rem;
411
+ color: var(--text-muted);
412
+ word-break: break-all;
413
  }
414
+ .nav-dropdown-divider {
415
+ height: 1px;
416
+ background: var(--border);
417
+ margin: 0.25rem 0;
418
  }
419
+ .nav-dropdown-item {
420
+ display: flex;
421
+ align-items: center;
422
+ gap: 0.5rem;
423
+ width: 100%;
424
+ padding: 0.55rem 1rem;
425
+ border: none;
426
+ background: none;
427
+ color: var(--text);
428
+ font-family: var(--font-sans);
429
+ font-size: 0.85rem;
430
+ cursor: pointer;
431
+ transition: background var(--transition);
432
+ }
433
+ .nav-dropdown-item:hover {
434
+ background: var(--bg-elevated);
435
+ }
436
+ .nav-dropdown-item svg { color: var(--text-dim); }
437
 
438
  /* ---- Page header ---- */
439
  .page-header { margin-bottom: 1.75rem; }
 
831
  .mt-1 { margin-top: 0.5rem; }
832
  .mt-2 { margin-top: 1.25rem; }
833
  .mb-2 { margin-bottom: 1rem; }
834
+
835
+ /* ---- Mobile responsive ---- */
836
+ @media (max-width: 768px) {
837
+ .app { padding: 1rem; }
838
+
839
+ .nav {
840
+ margin-top: -1rem;
841
+ padding: 0.75rem 1rem;
842
+ }
843
+
844
+ .page-header h1 { font-size: 1.3rem; }
845
+ .page-header p { font-size: 0.82rem; }
846
+
847
+ .auth-container { margin: 2rem auto; }
848
+ .auth-container .card { padding: 1.5rem; }
849
+
850
+ .options-row { flex-direction: column; }
851
+ .options-row .form-group { min-width: 100%; }
852
+
853
+ .result-stats { grid-template-columns: repeat(2, 1fr); }
854
+
855
+ .history-item { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
856
+ .history-actions { align-self: flex-end; }
857
+
858
+ .app-footer {
859
+ margin-bottom: -1rem;
860
+ padding: 1rem;
861
+ }
862
+
863
+ .card { padding: 1.15rem; }
864
+ .card-header { margin-bottom: 1rem; }
865
+
866
+ .regions-table { font-size: 0.78rem; }
867
+ .regions-table th, .regions-table td { padding: 0.45rem 0.5rem; }
868
+
869
+ .nav-dropdown { right: -0.5rem; min-width: 200px; }
870
+ }
static/js/app.js CHANGED
@@ -114,9 +114,22 @@ document.querySelectorAll('[data-view]').forEach((a) => {
114
  document.getElementById('btn-logout')?.addEventListener('click', async () => {
115
  try { await fetch(API_BASE + '/api/auth/logout', { method: 'POST', credentials: 'include' }); } catch (_) {}
116
  setToken(null);
 
117
  showView('login');
118
  });
119
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  async function init() {
121
  const token = getToken();
122
  if (!token) { showView('login'); return; }
 
114
  document.getElementById('btn-logout')?.addEventListener('click', async () => {
115
  try { await fetch(API_BASE + '/api/auth/logout', { method: 'POST', credentials: 'include' }); } catch (_) {}
116
  setToken(null);
117
+ document.getElementById('nav-dropdown')?.classList.add('hidden');
118
  showView('login');
119
  });
120
 
121
+ // ---- Avatar dropdown toggle ----
122
+ document.getElementById('btn-avatar')?.addEventListener('click', (e) => {
123
+ e.stopPropagation();
124
+ document.getElementById('nav-dropdown')?.classList.toggle('hidden');
125
+ });
126
+ document.addEventListener('click', (e) => {
127
+ const dd = document.getElementById('nav-dropdown');
128
+ if (dd && !dd.classList.contains('hidden') && !e.target.closest('.nav-user')) {
129
+ dd.classList.add('hidden');
130
+ }
131
+ });
132
+
133
  async function init() {
134
  const token = getToken();
135
  if (!token) { showView('login'); return; }
templates/index.html CHANGED
@@ -138,11 +138,17 @@
138
  <span>SatDetect</span>
139
  </div>
140
  <div class="nav-user">
141
- <span class="user-badge" id="user-email"></span>
142
- <button type="button" class="btn btn-ghost btn-sm" id="btn-logout">
143
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
144
- Log out
145
  </button>
 
 
 
 
 
 
 
 
146
  </div>
147
  </nav>
148
 
@@ -282,6 +288,6 @@
282
  </div>
283
  </div>
284
 
285
- <script src="/static/js/app.js?v=9"></script>
286
  </body>
287
  </html>
 
138
  <span>SatDetect</span>
139
  </div>
140
  <div class="nav-user">
141
+ <button type="button" class="nav-avatar" id="btn-avatar" aria-label="Account menu">
142
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
 
 
143
  </button>
144
+ <div class="nav-dropdown hidden" id="nav-dropdown">
145
+ <div class="nav-dropdown-email" id="user-email"></div>
146
+ <div class="nav-dropdown-divider"></div>
147
+ <button type="button" class="nav-dropdown-item" id="btn-logout">
148
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
149
+ Log out
150
+ </button>
151
+ </div>
152
  </div>
153
  </nav>
154
 
 
288
  </div>
289
  </div>
290
 
291
+ <script src="/static/js/app.js?v=10"></script>
292
  </body>
293
  </html>