From 24d74df2ab9c9e595c4ea07e9ad6a45f38c353d2 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:31:52 +0100 Subject: [PATCH] feat(layout): stick the title row too, and chain the sticky offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "All items · N items" scrolled away like the pager did. It now sticks at the scrollport top (padding-top/negative margin cancel .content's padding so nothing shows through the gap), and the three things below it offset by --hdr-h: batch bar, pagination, and the table header (--hdr-h + --pag-h). Both vars are measured on .content in renderEntries. Co-Authored-By: Claude Opus 4.8 --- css/style.css | 17 +++++++++++++---- js/app.js | 9 ++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/css/style.css b/css/style.css index 02a52e9..a6977d8 100644 --- a/css/style.css +++ b/css/style.css @@ -800,7 +800,15 @@ input[type="range"]::-webkit-slider-thumb { .dropdown-item svg { color: var(--text-dim); } .content { flex: 1; padding: 24px 32px; overflow-y: auto; } -.content-header { display: flex; align-items: baseline; gap: 12px; margin-bottom: 20px; } +/* Sticky title row. The negative top margin cancels .content's padding-top so + nothing scrolls through the gap above it. Everything else that sticks + (batch bar, pagination, table header) offsets by --hdr-h. */ +.content-header { + display: flex; align-items: baseline; gap: 12px; margin-bottom: 20px; + position: sticky; top: 0; z-index: 12; + background: var(--bg); + padding-top: 24px; margin-top: -24px; +} .content-header h2 { font-size: 22px; font-weight: 600; margin: 0; } .content-meta { color: var(--text-faint); font-size: 12px; } @@ -1000,7 +1008,7 @@ input[type="range"]::-webkit-slider-thumb { /* Batch action bar (appears when selection is non-empty) */ .batch-bar { position: sticky; - top: -1px; + top: var(--hdr-h, 0px); z-index: 10; display: flex; align-items: center; gap: 8px; padding: 10px 14px; @@ -2581,6 +2589,7 @@ body[data-editor-position="center"]:has(#settingsPanel.is-open)::before { .nav-item { justify-content: center; padding: 8px; } .topbar { padding: 12px 16px; } .content { padding: 16px; } + .content-header { padding-top: 16px; margin-top: -16px; } .slideover { width: 100%; } /* On a narrow viewport the slideover fills the screen — no point pushing the shell, the table isn't visible anyway. */ @@ -2676,7 +2685,7 @@ body[data-editor-position="center"]:has(#settingsPanel.is-open)::before { border-bottom: 1px solid var(--border); background: var(--bg-elev); user-select: none; - position: sticky; top: var(--pag-h, 0px); + position: sticky; top: calc(var(--hdr-h, 0px) + var(--pag-h, 0px)); z-index: 1; } .entry-table th.is-sortable { cursor: pointer; } @@ -2968,7 +2977,7 @@ input[type="password"]::-ms-clear { is pushed down by --pag-h (measured in renderEntries). */ .pagination { position: sticky; - top: 0; + top: var(--hdr-h, 0px); z-index: 2; background: var(--bg); display: flex; align-items: center; gap: 6px; diff --git a/js/app.js b/js/app.js index f1a9e2c..55ca7fd 100644 --- a/js/app.js +++ b/js/app.js @@ -2296,13 +2296,16 @@ function renderGrid() { const start = (state.currentPage - 1) * state.pageSize; const pageList = list.slice(start, start + state.pageSize); + // Sticky offsets: the pagination bar parks under the title row, and the + // table header under both. Measured, so a wrapped pager still lines up. + const content = $('.content'); + content.style.setProperty('--hdr-h', $('.content-header').offsetHeight + 'px'); if (total > 10) { const pag = renderPagination(total, totalPages); grid.appendChild(pag); - // The table header sticks below the sticky pagination bar, not under it. - grid.style.setProperty('--pag-h', pag.offsetHeight + 'px'); + content.style.setProperty('--pag-h', pag.offsetHeight + 'px'); } else { - grid.style.setProperty('--pag-h', '0px'); + content.style.setProperty('--pag-h', '0px'); } if (state.viewMode === 'table') {