utkarshpathak48 commited on
Commit
2d19ca9
·
1 Parent(s): b751679

fix: card hover cascade, nav/row tap targets, and t4 dark-mode contrast

Browse files

- Remove duplicate flatter .card:hover rule (was overriding the richer
translateY(-5px) scale(1.008) + elaborate shadow lift with a plain
translateY(-2px), no-scale version later in cascade order). Only one
.card:hover rule remains; @media (hover:none) touch override untouched.
- Raise nav .chip input/select min-height 20px -> 36px, add matching
min-height:36px to .state-filter-toggle, and add min-height:40px to
.row-summary (ranked-row click target), addressing sub-24px tap
targets flagged in the interaction/motion audit without disrupting
the compact row/chip layouts.
- Lighten dark-theme --t4 from #5b667e (3.46:1 on --bg, fails WCAG AA)
to #707c98 (4.77:1 on --bg, 4.59:1 on --bg2), verified via a
relative-luminance/contrast script. Affects USDT quote-suffix,
confidence /10 denominator, scan-step elapsed labels, and backtest
per-state trade counts.

Also verified (already fixed by prior uncommitted session, logic
confirmed sound on re-read, no changes needed):
- flipReorder() FLIP re-rank now keys the before/after position map by
el.dataset.sym instead of DOM element reference, so the transform
correctly applies across refreshScanQuietly()'s replaceWith() swaps.
- Scan-loader step 4 ("Ranking setups") now rotates reassurance copy
every 5s via startStep4Wait()/STEP4_WAIT_MESSAGES instead of sitting
static during slow 40-90s cold-start scans; timers cleared correctly
in both startLoaderSteps() reset and stopLoaderSteps() cleanup.

Validation: python brace/paren/bracket balance check on full file OK;
node --check (v20.20.2) on largest inline <script> OK; local uvicorn
smoke test on :8954 returned 200 for / and /api/status with no
Python errors, and served output confirmed all edits present.

Files changed (1) hide show
  1. static/index.html +52 -17
static/index.html CHANGED
@@ -44,7 +44,7 @@
44
  --t1: var(--tx);
45
  --t2: var(--tx2);
46
  --t3: var(--tx3);
47
- --t4: #5b667e;
48
  --muted: var(--tx3);
49
 
50
  /* ── Palette ── */
@@ -1531,7 +1531,7 @@ nav {
1531
  .state-filter-toggle {
1532
  display:inline-flex; align-items:center; gap:5px;
1533
  background:var(--glass2); border:1px solid var(--gb);
1534
- border-radius:8px; padding:4px 10px;
1535
  font-size:11px; font-weight:700; color:var(--t2); cursor:pointer;
1536
  transition:all 0.2s;
1537
  }
@@ -1584,7 +1584,7 @@ nav {
1584
 
1585
  .row-summary { display:none; }
1586
  .card.row-mode .row-summary {
1587
- display:grid; cursor:pointer;
1588
  grid-template-columns:26px 1.2fr 0.8fr 0.65fr 0.65fr 18px;
1589
  align-items:center; gap:10px;
1590
  }
@@ -1671,7 +1671,7 @@ nav {
1671
 
1672
  /* ── Touch targets — minimum 44px per WCAG 2.5.5 ── */
1673
  .chip { min-height:40px; }
1674
- .chip input, .chip select { min-height:20px; }
1675
  .bt-run-btn { min-height:36px; padding:6px 12px; }
1676
  .live-acc-resolve-btn { min-height:36px; padding:6px 10px; }
1677
  .signals-refresh-btn { width:36px !important; height:36px !important; }
@@ -1751,11 +1751,6 @@ nav {
1751
  backdrop-filter:blur(14px) saturate(160%);
1752
  -webkit-backdrop-filter:blur(14px) saturate(160%);
1753
  }
1754
- .card:hover {
1755
- transform:translateY(-2px);
1756
- border-color:var(--gb);
1757
- box-shadow:var(--shadow), inset 0 1px 0 var(--rim), 0 0 0 1px rgba(76,201,240,0.22);
1758
- }
1759
  #cycle-section { position:relative; }
1760
  #grid { position:relative; }
1761
 
@@ -2471,10 +2466,22 @@ function stopSymbolStream() { clearInterval(symStreamTimer); }
2471
  /* ═══ Loader step animator ═══ */
2472
  let stepTimer=null, elapsedTimer=null, countTimer=null, currentStep=0;
2473
  let scanStartTime=0;
 
 
 
 
 
 
 
 
 
 
 
2474
 
2475
  function startLoaderSteps() {
2476
  currentStep=0; scanStartTime=Date.now();
2477
- clearInterval(stepTimer); clearInterval(elapsedTimer); clearInterval(countTimer);
 
2478
 
2479
  // Reset all steps
2480
  for(let i=1;i<=4;i++){
@@ -2483,6 +2490,8 @@ function startLoaderSteps() {
2483
  const num=el.querySelector('.step-num'); if(num) num.textContent=i;
2484
  const t=$('st'+i); if(t) t.textContent='';
2485
  }
 
 
2486
  const sc=$('scan-count'); if(sc) sc.textContent='0';
2487
  const se=$('scan-elapsed'); if(se) se.textContent='0s';
2488
 
@@ -2490,12 +2499,18 @@ function startLoaderSteps() {
2490
  activateStep(1);
2491
  startSymbolStream();
2492
 
2493
- // Progress through steps: 1→2 at 3s, 2→3 at 7s, 3→4 at 11s
 
 
2494
  const timings=[3000,4000,4000];
2495
  let cumulative=0;
2496
  timings.forEach((delay,i)=>{
2497
  cumulative+=delay;
2498
- setTimeout(()=>{ markDone(i+1); activateStep(i+2); }, cumulative);
 
 
 
 
2499
  });
2500
 
2501
  // Elapsed counter
@@ -2504,7 +2519,8 @@ function startLoaderSteps() {
2504
  const se=$('scan-elapsed'); if(se) se.textContent=s+'s';
2505
  },500);
2506
 
2507
- // Fake count-up: 0→280 over ~12s (pace slows toward end)
 
2508
  let count=0;
2509
  countTimer=setInterval(()=>{
2510
  const target=280, elapsed=(Date.now()-scanStartTime)/1000;
@@ -2515,6 +2531,17 @@ function startLoaderSteps() {
2515
  },120);
2516
  }
2517
 
 
 
 
 
 
 
 
 
 
 
 
2518
  function activateStep(n) {
2519
  if(n>4) return;
2520
  const el=$('sd'+n); if(!el) return;
@@ -2530,7 +2557,9 @@ function markDone(n) {
2530
  }
2531
 
2532
  function stopLoaderSteps() {
2533
- clearInterval(stepTimer); clearInterval(elapsedTimer); clearInterval(countTimer);
 
 
2534
  for(let i=1;i<=4;i++) markDone(i);
2535
  const sc=$('scan-count'); if(sc) sc.textContent='280+';
2536
  stopSymbolStream();
@@ -2937,13 +2966,19 @@ function setLoading(on) {
2937
 
2938
  /* ═══ FLIP re-rank + never-blank auto-refresh ═══ */
2939
  function flipReorder(container, mutation) {
2940
- const before = new Map([...container.children].map(el => [el, el.getBoundingClientRect()]));
 
 
 
 
 
2941
  mutation();
2942
  const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
2943
  if (reduced) return;
2944
  [...container.children].forEach(el => {
2945
- const f = before.get(el);
2946
- if (!f) return; // brand-new element handled by its own reveal animation
 
2947
  const l = el.getBoundingClientRect();
2948
  const dx = f.left - l.left, dy = f.top - l.top;
2949
  if (dx || dy) {
 
44
  --t1: var(--tx);
45
  --t2: var(--tx2);
46
  --t3: var(--tx3);
47
+ --t4: #707c98;
48
  --muted: var(--tx3);
49
 
50
  /* ── Palette ── */
 
1531
  .state-filter-toggle {
1532
  display:inline-flex; align-items:center; gap:5px;
1533
  background:var(--glass2); border:1px solid var(--gb);
1534
+ border-radius:8px; padding:4px 10px; min-height:36px; box-sizing:border-box;
1535
  font-size:11px; font-weight:700; color:var(--t2); cursor:pointer;
1536
  transition:all 0.2s;
1537
  }
 
1584
 
1585
  .row-summary { display:none; }
1586
  .card.row-mode .row-summary {
1587
+ display:grid; cursor:pointer; min-height:40px;
1588
  grid-template-columns:26px 1.2fr 0.8fr 0.65fr 0.65fr 18px;
1589
  align-items:center; gap:10px;
1590
  }
 
1671
 
1672
  /* ── Touch targets — minimum 44px per WCAG 2.5.5 ── */
1673
  .chip { min-height:40px; }
1674
+ .chip input, .chip select { min-height:36px; }
1675
  .bt-run-btn { min-height:36px; padding:6px 12px; }
1676
  .live-acc-resolve-btn { min-height:36px; padding:6px 10px; }
1677
  .signals-refresh-btn { width:36px !important; height:36px !important; }
 
1751
  backdrop-filter:blur(14px) saturate(160%);
1752
  -webkit-backdrop-filter:blur(14px) saturate(160%);
1753
  }
 
 
 
 
 
1754
  #cycle-section { position:relative; }
1755
  #grid { position:relative; }
1756
 
 
2466
  /* ═══ Loader step animator ═══ */
2467
  let stepTimer=null, elapsedTimer=null, countTimer=null, currentStep=0;
2468
  let scanStartTime=0;
2469
+ let step4WaitTimer=null, step4MsgIdx=0;
2470
+ // Step 4 ("Ranking setups") is the last step and has no further server signal to advance on —
2471
+ // on a slow cold-start scan (40-90s) it would otherwise sit frozen on "running…" for a long
2472
+ // time. Rotate its sub-label through reassurance copy every ~5s instead of appearing stuck,
2473
+ // until stopLoaderSteps() is called when the real fetch resolves.
2474
+ const STEP4_WAIT_MESSAGES = [
2475
+ '35/20/15/15/15 scoring · sorting by confidence',
2476
+ 'Still ranking — large markets can take up to 90s',
2477
+ 'Cross-checking confidence bands…',
2478
+ 'Almost there — finalizing setup order…'
2479
+ ];
2480
 
2481
  function startLoaderSteps() {
2482
  currentStep=0; scanStartTime=Date.now();
2483
+ clearInterval(stepTimer); clearInterval(elapsedTimer); clearInterval(countTimer); clearInterval(step4WaitTimer);
2484
+ step4MsgIdx=0;
2485
 
2486
  // Reset all steps
2487
  for(let i=1;i<=4;i++){
 
2490
  const num=el.querySelector('.step-num'); if(num) num.textContent=i;
2491
  const t=$('st'+i); if(t) t.textContent='';
2492
  }
2493
+ const sd4sub=document.querySelector('#sd4 .step-sub');
2494
+ if(sd4sub) sd4sub.textContent=STEP4_WAIT_MESSAGES[0];
2495
  const sc=$('scan-count'); if(sc) sc.textContent='0';
2496
  const se=$('scan-elapsed'); if(se) se.textContent='0s';
2497
 
 
2499
  activateStep(1);
2500
  startSymbolStream();
2501
 
2502
+ // Progress through steps: 1→2 at 3s, 2→3 at 7s, 3→4 at 11s. These are illustrative pacing
2503
+ // for the first 3 steps only — step 4 has its own indefinite-wait handling below since the
2504
+ // real scan duration varies 40-90s and isn't streamed to the client.
2505
  const timings=[3000,4000,4000];
2506
  let cumulative=0;
2507
  timings.forEach((delay,i)=>{
2508
  cumulative+=delay;
2509
+ setTimeout(()=>{
2510
+ markDone(i+1);
2511
+ activateStep(i+2);
2512
+ if(i+2===4) startStep4Wait();
2513
+ }, cumulative);
2514
  });
2515
 
2516
  // Elapsed counter
 
2519
  const se=$('scan-elapsed'); if(se) se.textContent=s+'s';
2520
  },500);
2521
 
2522
+ // Fake count-up: 0→280 over ~12s (pace slows toward end), then holds at 280 gracefully
2523
+ // (rather than looking frozen mid-count) for the remainder of a slow scan.
2524
  let count=0;
2525
  countTimer=setInterval(()=>{
2526
  const target=280, elapsed=(Date.now()-scanStartTime)/1000;
 
2531
  },120);
2532
  }
2533
 
2534
+ function startStep4Wait() {
2535
+ clearInterval(step4WaitTimer);
2536
+ step4MsgIdx=0;
2537
+ step4WaitTimer=setInterval(()=>{
2538
+ step4MsgIdx=(step4MsgIdx+1)%STEP4_WAIT_MESSAGES.length;
2539
+ const sd4sub=document.querySelector('#sd4 .step-sub');
2540
+ if(sd4sub) sd4sub.textContent=STEP4_WAIT_MESSAGES[step4MsgIdx];
2541
+ const t=$('st4'); if(t) t.textContent=Math.floor((Date.now()-scanStartTime)/1000)+'s';
2542
+ },5000);
2543
+ }
2544
+
2545
  function activateStep(n) {
2546
  if(n>4) return;
2547
  const el=$('sd'+n); if(!el) return;
 
2557
  }
2558
 
2559
  function stopLoaderSteps() {
2560
+ clearInterval(stepTimer); clearInterval(elapsedTimer); clearInterval(countTimer); clearInterval(step4WaitTimer);
2561
+ const sd4sub=document.querySelector('#sd4 .step-sub');
2562
+ if(sd4sub) sd4sub.textContent=STEP4_WAIT_MESSAGES[0];
2563
  for(let i=1;i<=4;i++) markDone(i);
2564
  const sc=$('scan-count'); if(sc) sc.textContent='280+';
2565
  stopSymbolStream();
 
2966
 
2967
  /* ═══ FLIP re-rank + never-blank auto-refresh ═══ */
2968
  function flipReorder(container, mutation) {
2969
+ // Keyed by data-sym (stable identity) rather than DOM element reference: refreshScanQuietly
2970
+ // replaces unchanged cards with brand-new nodes via replaceWith(), so an element-reference
2971
+ // keyed map would never match post-mutation and the FLIP transform would always be skipped.
2972
+ const before = new Map([...container.children]
2973
+ .filter(el => el.dataset && el.dataset.sym)
2974
+ .map(el => [el.dataset.sym, el.getBoundingClientRect()]));
2975
  mutation();
2976
  const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
2977
  if (reduced) return;
2978
  [...container.children].forEach(el => {
2979
+ const key = el.dataset && el.dataset.sym;
2980
+ const f = key ? before.get(key) : null;
2981
+ if (!f) return; // brand-new/unkeyed element — handled by its own reveal animation
2982
  const l = el.getBoundingClientRect();
2983
  const dx = f.left - l.left, dy = f.top - l.top;
2984
  if (dx || dy) {