9f8d33c8b2b6bc61f6d02d781c4e02836f7099f9
max
  Fri Aug 21 02:05:42 2026 -0700
hgSession: new opt-in JavaScript "My Sessions" page; share gbModern.css with hgBlat. refs #38157

Applies the hgBlat facelift strategy (#37996) to hgSession: an opt-in,
client-rendered "My Sessions" page gated by the sessionNewPage /
sessionNewPageBanner hg.conf flags (mirroring blatNewForm / blatNewFormBanner),
with a banner linking between the classic and new pages so neither is a one-way
door. sessionNewPage also flips the site default.

hgSession.c stays the data/action backend: it emits the session list and page
config as an inline JSON global (hgSessionData) into an empty #sessionApp
container, and the new hgSession.js builds the UI - a save-current-view card
(name + optional description + "only I can load it"; empty name saves under a
random share_ name), a "most recently saved session" one-click Update, a
searchable/sortable/paged DataTable of sessions (assembly + position, created
with last-used on hover, views, a lock icon on private sessions), inline Share
(copy link / email / gallery), Edit (rename + description + private), Overwrite
and Delete, and a bulk Select -> Delete-all-selected mode.  The mutating actions
POST to new JSON endpoints (hgS_doDeleteJson / doShareJson / doGalleryJson /
doOverwriteJson / doDescribeJson) that run the same SQL as the classic full-page
handlers and return JSON, so the table updates in place; loads, file up/downloads
and custom-track backup stay as ordinary form submits/links.  The Advanced panel
keeps feature parity with the classic page (load another user's session, load
from URL/file, save to file, back up custom tracks, reset), minus the login/
change-password links that now live in the top menu.

Shared UCSC house-style components (design tokens, .gbPill, .gbCard, .gbStrip,
.gbSection, .gbShareBox, .gbBanner, the .gbModal* dialog and a .gbTable) are
factored into a new gbModern.css.  hgBlat is migrated onto it: its generic
.blat* classes are renamed to the shared .gb* names in hgBlat.css / hgBlat.js
and the #blatResults / #blatFormBox containers get class="gbApp"; verified
pixel-clean against the previous search form and results pages, including the
rename modal.  hgSession.css holds only session-specific layout.

diff --git src/hg/htdocs/style/hgBlat.css src/hg/htdocs/style/hgBlat.css
index b3033e1f327..7a5511a2662 100644
--- src/hg/htdocs/style/hgBlat.css
+++ src/hg/htdocs/style/hgBlat.css
@@ -1,210 +1,139 @@
-/* hgBlat.css - styling for the modern BLAT search form and results pages (hgBlat.js).
- * UCSC house style (per the project's UCSC UI Style Guide): steel-blue section header, tan/white
- * content, navy links with maroon hover, #dbe4ee table header, zebra rows, navy selection bar.
- * Loaded on the new pages via webIncludeResourceFile("hgBlat.css"); the classic C-rendered pages
- * emit their own small .blatBanner rule in hgBlat.c instead. */
+/* hgBlat.css - BLAT-specific styling for the modern search form and results pages (hgBlat.js).
+ * The shared UCSC house-style components (design tokens on .gbApp, plus .gbPill, .gbCard, .gbStrip,
+ * .gbStat, .gbSection, .gbShareBox, .gbBanner and the .gbModal* dialog) live in gbModern.css, which
+ * is loaded alongside this file via webIncludeResourceFile.  This file holds only the pieces unique
+ * to BLAT: the results table, score/coverage bars, the detail panel, and the search-form layout.
+ * (The classic C-rendered pages emit their own small .blatBanner rule in hgBlat.c instead, since
+ * they do not load these stylesheets.) */
 
-/* Plain white page background, matching the track-settings model (gb.css).  The framework body
- * is cream (#FFF9D2) and the hgInside content table carries a BGCOLOR=#FFFEE8 attribute; override
- * both to white so the results sit on one uniform white background. */
-body.cgi { background:#fff; }
-table.hgInside { background:#fff; }
-/* Main page header, styled like gb.css .gbTrackTitleBanner (hgGtexTrackSettings model): black
- * title on the house gold.  The framework's #sectTtl already holds "<assembly> BLAT Results". */
-.subheadingBar { background:#eaca92; padding:9px 16px; margin:0; border:0; box-sizing:border-box; }
-.subheadingBar #sectTtl { color:#000; font-weight:700; font-size:18px;
-    display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; }
-#blatResults, #blatFormBox { --accent:#003a72; --accentHover:#8b1a1a; --navy:#0a2b6b; --ink:#1e2833;
-    --muted:#5b6572; --faint:#93a0ad; --line:#d0d0d0; --lineSoft:#ececec; --card:#ffffff;
-    --panel:#ffffff; --section:#4c759c; --headrow:#ededed; --headrowLine:#cccccc;
-    --sel:#cfe0f5; --hover:#f0f0f0; --stripe:#f7f7f7;
-    --title:#eaca92; --titleLine:#d9bd82; --titleSub:#5a4a24;
-    --btn:#ffffff; --btnLine:#999999; --btnText:#003a72; --btnHover:#eef2f7;
-    --btnDark:#0a2b6b; --btnDarkHover:#0a2350;
-    font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color:var(--ink); font-size:14px; }
-.blatCard { background:var(--card); border:1px solid var(--line); border-radius:0; overflow:hidden;
-    box-shadow:0 1px 2px rgba(20,40,70,.10); margin:12px 0 24px; }
 /* Page actions sit in the gold main header (#sectTtl); no separate toolbar. */
 .blatHeadActions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
-.blatStrip { display:flex; align-items:center; gap:24px; flex-wrap:wrap;
-    padding:11px 18px; border-bottom:1px solid var(--line); background:var(--panel); }
-.blatStat { display:flex; flex-direction:column; gap:1px; }
-.blatStat .k { font-size:12px; color:var(--muted); font-weight:700; }
-.blatStat .v { font-size:14px; font-weight:700; color:var(--ink); }
-.blatDiv { width:1px; height:28px; background:var(--line); }
-.blatStripActions { margin-left:auto; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
-/* Buttons live both inside the card (#blatResults) and in the gold header (#sectTtl); scope to
- * both ids so the framework's link styles (which would add an underline) can't beat them. */
-#blatResults .blatPill, #sectTtl .blatPill, #blatFormBox .blatPill,
-#blatResults .blatStripActions input[type=submit],
-#blatResults .blatStripActions input[type=button] {
-    font-family:inherit; font-size:13px; font-weight:700; color:var(--btnText); background:var(--btn);
-    border:1px solid var(--btnLine); border-radius:0; padding:5px 12px; cursor:pointer;
-    text-decoration:none; display:inline-block; line-height:1.5; }
-#blatResults .blatPill:hover, #sectTtl .blatPill:hover, #blatFormBox .blatPill:hover,
-#blatResults .blatStripActions input[type=submit]:hover,
-#blatResults .blatStripActions input[type=button]:hover { background:var(--btnHover); text-decoration:none; }
-#blatResults .blatPill.primary, #sectTtl .blatPill.primary,
-#blatFormBox .blatPill.primary { background:var(--btnDark); color:#fff; border-color:var(--btnDark); }
-#blatResults .blatPill.primary:hover, #sectTtl .blatPill.primary:hover,
-#blatFormBox .blatPill.primary:hover { background:var(--btnDarkHover); }
-/* In the gold header (#sectTtl) the framework's link styles would otherwise beat .blatPill, so
- * re-assert the button look with id-level specificity. */
-#sectTtl .blatPill { color:#003a72; background:#fff; border:1px solid #999; text-decoration:none; }
-#sectTtl .blatPill:hover { background:#eef2f7; text-decoration:none; }
-#sectTtl .blatPill.primary { background:#0a2b6b; color:#fff; border-color:#0a2b6b; }
-#sectTtl .blatPill.primary:hover { background:#0a2350; }
 .blatShareIcon { vertical-align:-2px; margin-right:6px; }
-.blatShareBox { display:flex; align-items:center; gap:10px; flex-wrap:wrap;
-    padding:11px 18px; border-bottom:1px solid var(--line); background:var(--panel); }
-.blatShareMsg { font-size:14px; color:var(--muted); }
-.blatShareInput { flex:1; min-width:260px; font-size:13px; padding:5px 8px;
-    border:1px solid var(--btnLine); border-radius:0; background:#fff; }
-.blatBanner { background:#fbf3e2; border:1px solid var(--titleLine); padding:10px 14px;
-    margin:12px 0 0; font-size:14px; color:var(--ink); }
-.blatBanner a { color:var(--accent); }
-.blatBanner a:hover { color:var(--accentHover); }
 .blatSeqBar { flex:1 1 100%; display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
 .blatSeqText { flex:1 1 100%; min-height:150px; margin-top:10px; padding:8px; white-space:pre;
     font-family:'Roboto Mono','Courier New',monospace; font-size:13px; color:var(--ink);
     border:1px solid var(--btnLine); background:#fff; overflow:auto; }
 #blatTable { font-size:13px; width:100%; border-collapse:collapse; }
 /* Let the long text columns wrap so the table shrinks to fit smaller screens, while the fixed
  * bar columns keep a min-width so they never compress into an overlap; when even that won't fit,
  * #blatTable_wrapper scrolls horizontally. */
 #blatTable td.blatPos, #blatTable td.queryCol { white-space:normal; word-break:break-word; }
 #blatTable td.scoreCol { min-width:118px; }
 #blatTable td.covCol { min-width:120px; }
 #blatTable 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; }
 #blatTable tbody td { padding:8px 12px; border-bottom:1px solid var(--lineSoft); white-space:nowrap;
     vertical-align:middle; }
 #blatTable tbody tr { cursor:pointer; }
 #blatTable tbody tr:nth-child(even) { background:var(--stripe); }
 #blatTable tbody tr:hover { background:var(--hover); }
 #blatTable tbody tr.blatSel { background:var(--sel); box-shadow:inset 3px 0 0 var(--navy); }
 #blatTable td.num, #blatTable th.num { text-align:right; font-variant-numeric:tabular-nums; }
 #blatTable td.rankCol { color:var(--faint); }
 #blatTable td.strandCol { text-align:center; color:var(--muted); }
 #blatTable td.actionsCol a { color:var(--accent); font-weight:700; }
 #blatTable td.actionsCol .blatActSep { color:var(--line); margin:0 10px; }
 /* "New tab" shown as the usual box-with-arrow icon (via a CSS background so it isn't repeated in
  * every row's markup), sitting right after the position link with just a space between them. */
 #blatTable a.blatNewTab { display:inline-block; width:11px; height:11px;
     margin-left:4px; vertical-align:-1px; background-repeat:no-repeat; background-position:center;
     background-size:contain; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%23003a72' d='M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z'/%3E%3C/svg%3E"); }
 #blatTable a.blatNewTab:hover { opacity:.65; }
 #blatTable a { color:var(--accent); text-decoration:none; }
 #blatTable a:hover { color:var(--accentHover); text-decoration:underline; }
 .blatRowHint { padding:11px 18px 2px; font-size:13px; color:var(--muted); }
 .blatLocus { max-width:280px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
 .chrNote { color:var(--faint); text-decoration:none; margin-left:4px; }
 .chrNote:hover { color:var(--accent); }
 .blatScoreWrap { display:flex; align-items:center; gap:9px; justify-content:flex-end; }
 .blatScoreVal { font-size:13px; font-weight:700; text-align:right; font-variant-numeric:tabular-nums; }
 .blatScoreBar { flex:0 0 54px; height:8px; background:var(--headrow); border:1px solid var(--headrowLine); overflow:hidden; }
 .blatScoreBar > i { display:block; height:100%; background:var(--accent); }
 .blatIdPct { font-size:13px; font-weight:700; font-variant-numeric:tabular-nums; }
 .blatCov { position:relative; display:block; width:150px; max-width:100%; height:10px; background:var(--headrow); border:1px solid var(--headrowLine); }
 .blatCov > i { position:absolute; top:0; bottom:0; background:var(--accent); }
 #blatTable_filter { float:left; margin:0 0 10px; }
 #blatTable_filter input { width:300px; max-width:55vw; border:1px solid var(--btnLine);
     border-radius:0; padding:5px 9px; font-size:14px; }
 #blatTable_wrapper { padding:6px 0 4px; overflow-x:auto; }
 .blatDetail { border-bottom:1px solid var(--line); background:var(--panel); padding:14px 18px 16px; }
 .blatDetail .dhead { display:flex; align-items:baseline; gap:10px; margin-bottom:12px; flex-wrap:wrap; }
 .blatDetail .dhead .lab { font-size:13px; color:var(--muted); }
 .blatSelectHint { font-size:14px; color:var(--ink); }
 .blatSelectHint a { color:var(--accent); }
 .blatSelectHint a:hover { color:var(--accentHover); }
 .blatDetail .dhead .loc { font-size:14px; font-weight:700; color:var(--ink); }
 .blatDetailCard { display:flex; gap:26px; flex-wrap:wrap; padding:14px 16px; background:var(--card);
     border:1px solid var(--line); border-radius:0; }
 .blatTiles { display:grid; grid-template-columns:repeat(4,auto); gap:14px 26px; }
 .blatTile .k { font-size:13px; color:var(--muted); cursor:help; }
 .blatTile .v { font-size:14px; font-weight:700; color:var(--ink); }
 .blatDetailActions { display:flex; gap:8px; margin-top:14px; flex-wrap:wrap; }
 /* Detail panel layout (was inline styles in blatDetailSkeleton). */
 .blatDetailCol { display:flex; flex-direction:column; gap:16px; min-width:250px; }
 .blatAlignBox { flex:1; min-width:320px; border-left:1px solid var(--line); padding-left:24px;
     display:flex; flex-direction:column; justify-content:center; }
-.blatAlignBox .blatPill { align-self:flex-start; }
+.blatAlignBox .gbPill { align-self:flex-start; }
 .blatAlignText { font-size:14px; color:var(--muted); line-height:1.55; margin:8px 0 14px; max-width:360px; }
-/* Full-width message row inside the flex-wrap share box. */
-.blatShareFull { flex:1 1 100%; }
 /* Rename modal.  On this page the old C-emitted inline rename form (#renameFormItem / #renameForm)
- * is replaced by the JS button + modal below, so keep the C markup hidden even though hgBlat.c
+ * is replaced by the JS button + .gbModal dialog, so keep the C markup hidden even though hgBlat.c
  * flips its display to block after buildBigPsl (our !important beats that inline style). */
 #renameFormItem, #renameForm { display:none !important; }
-.blatModalBg { 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; }
-.blatModal { 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; }
-.blatModalTitle { font-size:16px; font-weight:700; color:var(--ink); margin-bottom:12px; }
-.blatModalText { font-size:13px; color:var(--ink); line-height:1.5; max-width:430px; margin-bottom:14px; }
-.blatModalText.blatModalNote { margin-top:14px; }
-.blatModalText a { color:var(--accent); }
-.blatModalText a:hover { color:var(--accentHover); }
-.blatModalLabel { display:block; font-size:12px; font-weight:700; color:var(--muted);
-    margin:10px 0 3px; }
-.blatModalInput { 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; }
-.blatModalBtns { display:flex; gap:8px; justify-content:flex-end; margin-top:18px; }
 
 /* ---- search form (blatFormBuild) -------------------------------------
- * Shares the tokens, .blatPill buttons, .blatBanner and the gold .subheadingBar page header
- * above with the results page, so the two pages read as one design. */
+ * Shares the design tokens (.gbApp on #blatFormBox), .gbPill buttons, .gbSection bars, .gbBanner
+ * and the gold .subheadingBar header from gbModern.css with the results page, so the two pages
+ * read as one design. */
 #blatFormBox { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; color:var(--ink); font-size:14px; }
 #blatFormBox .blatFormGrid { display:grid; grid-template-columns:minmax(0,1fr) 300px; gap:28px;
     align-items:start; margin:12px 0 24px; }
 @media (max-width:900px) { #blatFormBox .blatFormGrid { grid-template-columns:minmax(0,1fr); } }
-#blatFormBox .blatSection { background:var(--section); color:#fff; padding:6px 13px; font-size:15px;
-    font-weight:700; margin:0 0 14px; }
 /* flex-start, not flex-end: the genome field carries a "Current genome:" line under its input,
  * so bottom-aligning the row would push Query type down by the height of that line. */
 #blatFormBox .blatRow { display:flex; align-items:flex-start; gap:16px; flex-wrap:wrap; margin-bottom:14px; }
 #blatFormBox .blatField { display:flex; flex-direction:column; gap:5px; }
 #blatFormBox .blatField > span { font-size:13px; font-weight:700; color:var(--muted); }
 #blatFormBox .blatField select { padding:6px 8px; font-size:14px; border:1px solid var(--btnLine);
     border-radius:0; background:#fff; color:var(--ink); font-family:inherit; }
 #blatFormBox .blatGenomeSlot { flex:1 1 650px; min-width:260px; max-width:650px; }
 #blatFormBox .blatChecks { display:flex; align-items:center; gap:22px; flex-wrap:wrap; margin-bottom:16px;
     font-size:14px; }
 #blatFormBox .blatCheck { display:flex; align-items:center; gap:6px; }
 #blatFormBox .blatCheck label { display:flex; align-items:center; gap:6px; cursor:pointer; }
 #blatFormBox .blatCheck input { margin:0; }
 #blatFormBox .blatInfo { display:inline-flex; align-items:center; cursor:help; }
 #blatFormBox .blatTabs { display:flex; border-bottom:1px solid var(--line); margin-bottom:12px; }
 #blatFormBox .blatTab { padding:8px 16px; font-size:14px; font-weight:700; cursor:pointer; background:none;
     border:none; border-bottom:3px solid transparent; color:var(--accent); font-family:inherit; }
 #blatFormBox .blatTab.on { border-bottom-color:var(--title); color:var(--ink); }
 #blatFormBox .blatPaneHint { display:flex; justify-content:space-between; align-items:baseline; gap:16px;
     flex-wrap:wrap; font-size:13px; color:var(--muted); margin-bottom:6px; }
 #blatFormBox textarea.blatSeq { width:100%; height:200px; resize:vertical; padding:9px 11px; box-sizing:border-box;
     font-family:'Roboto Mono','Courier New',monospace; font-size:13px; line-height:1.6;
     border:1px solid var(--btnLine); border-radius:0; color:var(--ink); background:#fff; }
 #blatFormBox .blatCount { display:flex; justify-content:space-between; align-items:center; gap:16px;
     padding:6px 1px 0; font-size:13px; color:var(--muted); }
 #blatFormBox .blatCount .over { color:#b1301f; font-weight:700; }
 #blatFormBox .blatDrop { border:2px dashed var(--headrowLine); background:#fafafa; padding:30px 20px;
     text-align:center; }
 #blatFormBox .blatDrop.hot { background:var(--hover); border-color:var(--accent); }
 #blatFormBox .blatDropTitle { font-size:15px; font-weight:700; color:var(--accent); margin-bottom:4px; }
 #blatFormBox .blatDropSub { font-size:13px; color:var(--muted); margin-bottom:14px; }
 #blatFormBox .blatFileName { margin-top:12px; font-size:13px; color:var(--ink); font-weight:700; }
 #blatFormBox .blatActions { display:flex; align-items:center; gap:10px; flex-wrap:wrap;
     border-top:1px solid var(--line); padding-top:16px; margin-top:16px; }
-#blatFormBox .blatCard { border:1px solid var(--line); padding:14px 16px; background:var(--card); }
-#blatFormBox .blatCard + .blatCard { margin-top:16px; }
-#blatFormBox .blatCard h3 { margin:0 0 9px; font-size:14px; font-weight:700; color:var(--accent); }
-#blatFormBox .blatCard div { font-size:13px; line-height:1.5; color:var(--ink); }
-#blatFormBox .blatCard div + div { margin-top:9px; }
-#blatFormBox .blatCard a { color:var(--accent); }
-#blatFormBox .blatCard a:hover { color:var(--accentHover); }
+/* Sidebar cards on the form add padding to the shared .gbCard (which supplies the border/shadow). */
+#blatFormBox .gbCard { border:1px solid var(--line); padding:14px 16px; background:var(--card); }
+#blatFormBox .gbCard + .gbCard { margin-top:16px; }
+#blatFormBox .gbCard h3 { margin:0 0 9px; font-size:14px; font-weight:700; color:var(--accent); }
+#blatFormBox .gbCard div { font-size:13px; line-height:1.5; color:var(--ink); }
+#blatFormBox .gbCard div + div { margin-top:9px; }
+#blatFormBox .gbCard a { color:var(--accent); }
+#blatFormBox .gbCard a:hover { color:var(--accentHover); }
 #blatFormBox .blatLimitRow { display:flex; justify-content:space-between; gap:12px; padding-bottom:7px;
     border-bottom:1px solid var(--lineSoft); font-size:13px; }
 /* The genome search bar is emitted by hgBlat.c (printGenomeSearchBar) and relocated into the
  * layout by JS, so it keeps its real autocomplete over every assembly. */
 /* The genome picker is setupGenomeSelector (utils.js), which builds its own combobox; only
  * its width is a page concern.  HGStyle.css does not style .gsWrap, so nothing to override. */
 #blatFormBox .gsWrap { max-width:650px; }