42b33234bee213325c2b6eede35edbb4fb2e566c
max
  Thu Sep 3 06:40:30 2026 -0700
hgBlat: spinner while a search runs, shorter example, left-justified result columns

Four items from Lou's note-37 on #38086.

The search is a plain form POST, so nothing on screen changed between Submit and
the arrival of the results - on an assembly served by a dynamic BLAT server that
is a ten second wait with no sign that anything is happening.  A busy overlay now
covers the form: a generic .gbBusyBg/.gbSpinner in gbModern.css (CSS-drawn, since
the page is already navigating away when it appears), put up by blatFormBusyWire()
350 ms after submit so a sub-second hg38 search does not flash a scrim, and
swapping its text after 8 s to say that an index is probably being loaded on
demand.  Skipped for Clear and for an empty query; a pageshow handler takes it
down again after a Back navigation.

The example sequence goes from 13.8 kb of the SOD1 locus to 2.5 kb of PTP4A3,
hg38 chr8:141,424,501-141,427,000.  That window is carried by both
chr8_KI270819v1_alt and chr8_MU273363v1_fix, so the example now demonstrates the
alt and fix rows and their FAQ icons instead of returning one plain hit, and it
covers two PTP4A3 exons so the result track is named after the gene.

Every column of the results table is left-justified; the numeric ones keep
tabular figures, and the score value gets a fixed width so its bars stay in a
straight line.  The alt/fix explanation icon is red rather than faint grey, and
is drawn as the browser's own info-icon SVG rather than the U+24D8 glyph, which
some system fonts do not have.

refs #38086

diff --git src/hg/htdocs/style/gbModern.css src/hg/htdocs/style/gbModern.css
index f11b1dcb5d7..648c047be5f 100644
--- src/hg/htdocs/style/gbModern.css
+++ src/hg/htdocs/style/gbModern.css
@@ -93,30 +93,49 @@
  * descendant of the .gbApp wrapper so the tokens inherit in. */
 .gbModalBg { position:fixed; top:0; right:0; bottom:0; left:0; z-index:1000;
     background:rgba(0,0,0,.4); display:flex; align-items:center; justify-content:center; }
 .gbModal { background:#fff; border:1px solid var(--line); border-radius:0;
     box-shadow:0 4px 18px rgba(0,0,0,.28); padding:18px 20px; min-width:340px; max-width:92vw; }
 .gbModalTitle { font-size:16px; font-weight:700; color:var(--ink); margin-bottom:12px; }
 .gbModalText { font-size:13px; color:var(--ink); line-height:1.5; max-width:430px; margin-bottom:14px; }
 .gbModalText.gbModalNote { margin-top:14px; }
 .gbModalText a { color:var(--accent); }
 .gbModalText a:hover { color:var(--accentHover); }
 .gbModalLabel { display:block; font-size:12px; font-weight:700; color:var(--muted); margin:10px 0 3px; }
 .gbModalInput { width:100%; box-sizing:border-box; border:1px solid var(--btnLine);
     border-radius:0; padding:6px 8px; font-size:14px; font-family:inherit; background:#fff; }
 .gbModalBtns { display:flex; gap:8px; justify-content:flex-end; margin-top:18px; }
 
+/* Busy overlay - a scrim over a spinner card, shown while a request that navigates the page away is
+ * in flight.  A plain form POST gives no feedback of its own, and a BLAT run against a dynamic
+ * server can sit there for many seconds, so without this the page looks dead after Submit.  Same
+ * placement rules as the modal: keep the markup inside the .gbApp wrapper.  Add class "on" to show
+ * it; there is no way to dismiss it, since the only thing that ends the wait is the new page. */
+.gbBusyBg { position:fixed; top:0; right:0; bottom:0; left:0; z-index:1100;
+    background:rgba(255,255,255,.75); display:none; align-items:center; justify-content:center; }
+.gbBusyBg.on { display:flex; }
+.gbBusyCard { background:#fff; border:1px solid var(--line); box-shadow:0 4px 18px rgba(0,0,0,.28);
+    padding:22px 26px; display:flex; align-items:center; gap:16px; max-width:92vw; }
+.gbBusyTitle { font-size:15px; font-weight:700; color:var(--ink); }
+.gbBusyText { font-size:13px; color:var(--muted); margin-top:4px; max-width:380px; line-height:1.4; }
+/* Drawn in CSS rather than loaded as images/loading.gif: the page is already navigating away when
+ * this appears, so a request for an image is exactly what we cannot count on finishing. */
+.gbSpinner { flex:none; width:28px; height:28px; border:3px solid var(--lineSoft);
+    border-top-color:var(--navy); border-radius:50%; animation:gbSpin .8s linear infinite; }
+@keyframes gbSpin { to { transform:rotate(360deg); } }
+@media (prefers-reduced-motion: reduce) { .gbSpinner { animation-duration:2.4s; } }
+
 /* Generic dense results table - steel-tint header, zebra rows, navy selection bar.  Apply class
  * "gbTable" to the <table>; the tool may add its own column classes. */
 .gbTable { font-size:13px; width:100%; border-collapse:collapse; }
 .gbTable thead th { background:var(--headrow); border-bottom:1px solid var(--headrowLine);
     font-size:12px; color:var(--navy); font-weight:700; padding:8px 12px; white-space:nowrap;
     text-align:left; }
 .gbTable tbody td { padding:8px 12px; border-bottom:1px solid var(--lineSoft); white-space:nowrap;
     vertical-align:middle; }
 .gbTable tbody tr:nth-child(even) { background:var(--stripe); }
 .gbTable tbody tr:hover { background:var(--hover); }
 .gbTable tbody tr.gbSel { background:var(--sel); box-shadow:inset 3px 0 0 var(--navy); }
 .gbTable td.num, .gbTable th.num { text-align:right; font-variant-numeric:tabular-nums; }
 .gbTable a { color:var(--accent); text-decoration:none; }
 .gbTable a:hover { color:var(--accentHover); text-decoration:underline; }