feat(ui): one consistent navbar on every page (TODO.md L3)
This commit is contained in:
+109
-40
@@ -203,7 +203,10 @@ html::after {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* 2px brand→cyan gradient hairline under the sticky header (phase 08;
|
||||
shared by the app header and the document-viewer header, phase 10). */
|
||||
shared by the app header and the document-viewer header, phase 10).
|
||||
In the viewer's two-row header (phase 34) this lands at the BOTTOM
|
||||
edge of the whole header — row 1's own copy is suppressed there (see
|
||||
the .doc-header rules below). */
|
||||
.app-header::after,
|
||||
.doc-header::after {
|
||||
content: "";
|
||||
@@ -235,12 +238,23 @@ html::after {
|
||||
font-size: 1.125rem;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
/* Phase 34 task 04 (visual pass): the bar must fit the FULL admin
|
||||
control set at every width — the wordmark is the designated
|
||||
squeeze target (min-width:0 lets flex shrink it; overflow:hidden
|
||||
clips it clean instead of letting it overlap the nav). */
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Mono wordmark with letter-spacing — the "technical" touch (phase 08). */
|
||||
/* Mono wordmark with letter-spacing — the "technical" touch (phase 08);
|
||||
ellipsizes as the squeeze target (phase 34 task 04). */
|
||||
.brand-text {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.08em;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.brand-mark { width: 22px; height: 22px; flex: 0 0 auto; display: block; }
|
||||
.brand-text strong { color: var(--brand-ink); font-weight: 700; }
|
||||
@@ -339,6 +353,18 @@ html::after {
|
||||
}
|
||||
.sync-btn:hover { background: var(--brand-soft); color: var(--brand-ink); }
|
||||
.sync-btn:disabled { opacity: 0.6; cursor: wait; }
|
||||
/* Phase 34: the failed-sync look — the button now lives on EVERY page
|
||||
(the non-Sources pages have no error banner, so the button itself is
|
||||
the visible failure state; the sanitized error text rides in title /
|
||||
aria-label, set by the header module). Phase-08 error pair: --err-ink
|
||||
on --err-bg ≈9.1:1, --err-line border (the amber --accent-line is
|
||||
deflection-only — never on errors). */
|
||||
.sync-btn.is-error {
|
||||
background: var(--err-bg);
|
||||
color: var(--err-ink);
|
||||
border-color: var(--err-line);
|
||||
}
|
||||
.sync-btn.is-error:hover { background: var(--err-bg); color: var(--err-ink); }
|
||||
.sync-icon { width: 16px; height: 16px; display: block; flex: 0 0 auto; }
|
||||
/* Running state: the refresh icon spins (reuses the shared spin
|
||||
keyframes) — the visible half of "Syncing…" while the 2 s poll waits. */
|
||||
@@ -755,6 +781,16 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.steering-delete:hover:not(:disabled) { background: var(--err-bg); color: var(--err-ink); border-color: var(--err-line); }
|
||||
.steering-delete:disabled { opacity: 0.5; cursor: wait; }
|
||||
.steering-empty { margin: 0.65rem 0 0; color: var(--ink-soft); font-size: 0.88rem; }
|
||||
/* Phase 34 task 04 (visual pass): on the non-chat pages the panel is a
|
||||
direct child of <main> (not inside a .container) — pin it to the same
|
||||
72rem content column as everything else so it never renders as a
|
||||
full-viewport band. The chat page's panel sits inside .chat-shell's
|
||||
container, so this selector only hits the direct-child placement. */
|
||||
.app-main > .steering-panel {
|
||||
width: calc(100% - 2.5rem);
|
||||
max-width: 69.5rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* ---------- Global tuning page (phase 27) ---------- */
|
||||
/* /tuning.html: create / edit / delete steering notes without a chat
|
||||
@@ -1239,26 +1275,30 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.docs-table tbody tr:hover { background: var(--bg); }
|
||||
.docs-table tbody tr:last-child td { border-bottom: 0; }
|
||||
|
||||
/* ---------- Document viewer (phase 10) ---------- */
|
||||
/* Phase 12: the same fixed-height bar as .app-header (--header-h, 64px /
|
||||
58px mobile) — the header must never change size between chat,
|
||||
sources, and the document viewer (owner report 2026-08-22). */
|
||||
/* ---------- Document viewer (phase 10; two-row header since phase 34) ---------- */
|
||||
/* Phase 34 (owner confirmation 2026-08-26): the viewer header is TWO
|
||||
rows in one sticky <header> — row 1 reuses the standard .app-header /
|
||||
.header-inner rules VERBATIM (row 1 IS the standard bar, so the
|
||||
phase-12/19 pinned heights 64px / 58px apply), row 2 is the
|
||||
.doc-titlebar (back link + title + meta). The header element itself
|
||||
is content-sized (row 1 + row 2) and sticky, so BOTH rows stay
|
||||
pinned while the document scrolls. */
|
||||
.doc-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
background: var(--surface);
|
||||
height: var(--header-h);
|
||||
/* Phase 12: same guard as .app-header — the bar never shrinks. */
|
||||
/* Phase 34: no fixed height — the header sizes to row 1 (--header-h)
|
||||
+ the .doc-titlebar; the phase-12 shrink guard stays. */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.doc-header-inner {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
/* Back link: pill with an SVG arrow + "Sources" (>=44px touch target). */
|
||||
/* Row 1's own gradient hairline would land BETWEEN the rows; the
|
||||
separator there is the quiet --line border-top on the titlebar, and
|
||||
the signature hairline belongs at the bottom edge of the whole
|
||||
header (the .doc-header::after in the shared rule above). */
|
||||
.doc-header > .app-header::after { content: none; }
|
||||
/* Back link: pill with an SVG arrow + "Sources" (>=44px touch target;
|
||||
:focus-visible via the global 3px outline rule, phase 08). */
|
||||
.doc-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1276,31 +1316,37 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
}
|
||||
.doc-back:hover { background: #2a345f; }
|
||||
.doc-back svg { width: 16px; height: 16px; display: block; }
|
||||
.doc-title-block { min-width: 0; }
|
||||
/* Phase 19: the shared header controls reach the viewer bar (New chat
|
||||
+ Sign in / Sign out) — margin-left:auto pushes them to the right;
|
||||
the title block keeps clipping (min-width: 0 above) so the two pills
|
||||
fit while the bar still measures exactly --header-h. The reused
|
||||
.new-chat-btn / .auth-link classes already carry the ≤640px icon-only
|
||||
rules, so at 360px the bar is back pill + clipping title + two icon
|
||||
pills (no overflow — test_responsive_polish pins scrollWidth). */
|
||||
.doc-header-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
/* Row 2: the titlebar — a .container-width row with the back link +
|
||||
the title block, its own content-sized height (title line + meta
|
||||
line), the same surface as row 1, separated from it by the quiet
|
||||
hairline (var(--line) — the existing 1px border color). */
|
||||
.doc-titlebar {
|
||||
border-top: 1px solid var(--line);
|
||||
padding-block: 0.6rem;
|
||||
}
|
||||
.doc-titlebar .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
/* The title block keeps clipping (min-width: 0) so #doc-title /
|
||||
.doc-meta ellipsize inside the flex row instead of overflowing. */
|
||||
.doc-title-block { min-width: 0; }
|
||||
#doc-title {
|
||||
margin: 0;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.3;
|
||||
/* Phase 34: ellipsis + the `title` attribute (set by document.js) —
|
||||
the row has no pills to fit, so the old clipping-for-pills rule is
|
||||
gone; the ellipsis is the only fit the title needs. */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
/* Meta row: source badge · format badge · mono path · indexed · chunks.
|
||||
Phase 12: it may clip, but it must NEVER wrap — a wrapped meta row
|
||||
would grow the header past the shared --header-h bar. */
|
||||
Phase 12/34: it may clip, but it must NEVER wrap — the meta stays on
|
||||
one line in the titlebar row (the row is content-sized, so a wrap
|
||||
would grow it). */
|
||||
.doc-meta {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
@@ -1606,6 +1652,20 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* ---------- Responsive (tablet squeeze — phase 34 task 04 visual pass) ----------
|
||||
The full admin bar (brand + nav [Chat, Sources, Tuning] + Tuning
|
||||
toggle + Sync sources + New chat + Sign out) outgrows a 768px bar in
|
||||
the desktop styles. Squeeze the pills/gaps moderately — the 44px
|
||||
touch floor is held by min-height and the 64px height by --header-h
|
||||
— and let the brand wordmark (base ellipsis) absorb any remainder.
|
||||
The ≤640 block below stays tighter and wins at phone widths. */
|
||||
@media (max-width: 900px) {
|
||||
.header-inner { gap: 0.65rem; }
|
||||
.nav-link { padding: 0.4rem 0.6rem; font-size: 0.9rem; }
|
||||
.app-nav { gap: 0.2rem; }
|
||||
.new-chat-btn, .auth-link, .sync-btn, .steering-toggle { padding: 0.45rem 0.6rem; }
|
||||
}
|
||||
|
||||
/* ---------- Responsive (mobile-first adjustments) ---------- */
|
||||
@media (max-width: 640px) {
|
||||
:root { --header-h: 58px; }
|
||||
@@ -1619,9 +1679,17 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
the nav pills + gaps instead: tighter pill padding/font and a
|
||||
tighter inner gap. The 44px touch floor is held by min-height, the
|
||||
58px bar height is pinned by --header-h, and the bar now fits both
|
||||
auth states at 375px (and 360px) without horizontal overflow. */
|
||||
.header-inner { gap: 0.5rem; }
|
||||
.brand { min-width: 0; }
|
||||
auth states at 375px (and 360px) without horizontal overflow.
|
||||
Phase 34 task 04 (visual pass): the full admin bar — nav [Chat,
|
||||
Sources, Tuning] + Tuning toggle + Sync + New chat + Sign out —
|
||||
now ships on EVERY page, so the squeeze tightens once more
|
||||
(0.3rem inner gap, 0.78rem nav pills, 0.35rem pill padding,
|
||||
18px brand mark) to hold 375px with the brand mark intact and
|
||||
360px with the brand clipped clean (overflow:hidden — the mark
|
||||
never overlaps the nav). */
|
||||
.header-inner { gap: 0.3rem; }
|
||||
.brand { min-width: 0; overflow: hidden; }
|
||||
.brand-mark { width: 18px; height: 18px; }
|
||||
.brand-text {
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
@@ -1630,20 +1698,20 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.nav-link { padding: 0.4rem 0.4rem; font-size: 0.84rem; }
|
||||
.app-nav { gap: 0.15rem; }
|
||||
.new-chat-btn { padding: 0.4rem 0.55rem; }
|
||||
.nav-link { padding: 0.35rem 0.3rem; font-size: 0.78rem; }
|
||||
.app-nav { gap: 0.1rem; }
|
||||
.new-chat-btn { padding: 0.4rem 0.35rem; }
|
||||
.new-chat-label { display: none; }
|
||||
.new-chat-btn svg { display: block; }
|
||||
/* Phase 16: the auth pill goes icon-only like New chat — brand text
|
||||
ellipsizes as the designated squeeze target, no bar overflow. */
|
||||
.auth-link { padding: 0.4rem 0.55rem; }
|
||||
.auth-link { padding: 0.4rem 0.35rem; }
|
||||
.auth-label { display: none; }
|
||||
.auth-link svg { display: block; }
|
||||
/* Phase 32: the sync pill goes icon-only like the other pills (the
|
||||
aria-label keeps the accessible name); the spinning icon is the
|
||||
visible running state on a touch screen. */
|
||||
.sync-btn { padding: 0.4rem 0.55rem; }
|
||||
.sync-btn { padding: 0.4rem 0.35rem; }
|
||||
.sync-label { display: none; }
|
||||
/* The last-result counts stay ANNOUNCED (aria-live is untouched) but
|
||||
go visually hidden — the 58px bar has no room for the text; the
|
||||
@@ -1657,7 +1725,7 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
.steering-toggle { padding: 0.4rem 0.55rem; }
|
||||
.steering-toggle { padding: 0.4rem 0.35rem; }
|
||||
/* Visually hidden, NOT display:none — the accessible name keeps the
|
||||
word "Tuning" next to the count badge. */
|
||||
.steering-label {
|
||||
@@ -1670,6 +1738,7 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
border: 0;
|
||||
}
|
||||
.steering-note { padding: 0.3rem 0.3rem 0.3rem 0.7rem; }
|
||||
.app-main > .steering-panel { width: calc(100% - 1.8rem); }
|
||||
.tune-btn { min-height: 44px; }
|
||||
/* Phase 27: the tuning page squeezes like the other cards — the row
|
||||
padding tightens; the icon+label actions keep their 44px floor and
|
||||
@@ -1682,7 +1751,7 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.suggestions { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start;
|
||||
padding-bottom: 0.4rem; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
|
||||
.suggestion-chip { flex: 0 0 auto; }
|
||||
.doc-header-inner { flex-wrap: nowrap; gap: 0.5rem; } /* phase 12: fixed-height bar — no wrap, no extra padding */
|
||||
.doc-titlebar .container { gap: 0.5rem; } /* phase 34: row 2 squeezes like row 1 at mobile widths */
|
||||
#doc-title { font-size: 1.1rem; }
|
||||
.doc-path { max-width: 16rem; }
|
||||
.doc-md { padding: 1.1rem 1rem; }
|
||||
|
||||
Reference in New Issue
Block a user