feat(ui): documents open in an almost-fullscreen modal instead of a new page — same-page overlay on chat + Sources, /document.html kept as the no-JS/direct-link fallback
This commit is contained in:
@@ -1204,6 +1204,163 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
}
|
||||
.doc-link:hover, .doc-link:focus-visible { text-decoration: underline; }
|
||||
|
||||
/* ---------- Document modal (phase 26) ----------
|
||||
"New documents should open in an almost-fullscreen modal, not in a new
|
||||
page" (TODO.md L4). The overlay reuses the viewer page's .doc-meta
|
||||
badge classes, the .doc-md ≤46rem reading column, and the .doc-raw
|
||||
pre — this block only adds the chrome (backdrop, panel, header,
|
||||
actions, scroll container). Phase-08 tokens only; NO blur (the
|
||||
phase-08 no-blur perf anchor); no new assets; system fonts.
|
||||
|
||||
Stacking: z-index 1000 puts the overlay above the sticky header (20)
|
||||
and the skip-link (100); the z-index:-1 background layers stay below
|
||||
everything. The panel is 96vw × 92vh, centered ("almost-fullscreen"). */
|
||||
.doc-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1000;
|
||||
display: flex; /* the panel is the only in-flow child — margin: auto centers it */
|
||||
}
|
||||
/* Explicit (the global [hidden] rule already wins — this one is the
|
||||
documented, testable contract for the skeleton). */
|
||||
.doc-modal[hidden] { display: none; }
|
||||
|
||||
.doc-modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
/* --bg at 82% — no backdrop-filter (phase-08 no-blur perf anchor). */
|
||||
background: rgba(10, 14, 23, 0.82);
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.doc-modal-panel {
|
||||
/* position:relative lifts the panel above the fixed backdrop (positioned
|
||||
elements paint over in-flow siblings otherwise). */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: min(1100px, 96vw);
|
||||
height: 92vh;
|
||||
margin: auto;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 24px 80px rgb(0 0 0 / 0.55);
|
||||
}
|
||||
|
||||
/* Sticky top with the SAME height as the page bars — the phase-12 pins
|
||||
(--header-h: 64px desktop / 58px ≤640px) so the bar never reads
|
||||
differently here. The title is the designated squeeze target (ellipsis),
|
||||
so the bar can never grow its height. */
|
||||
.doc-modal-header {
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.9rem;
|
||||
height: var(--header-h);
|
||||
padding-inline: 1.25rem;
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.doc-modal-title {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.3;
|
||||
color: var(--ink);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.doc-modal-actions {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* "Full page" escape hatch: the same ghost pill as New chat / Sign in
|
||||
(ink-soft on surface ≈6.9:1; hover pair brand-ink/brand-soft ≈6.9:1).
|
||||
Icon-only below 640px — the aria-label keeps the accessible name. */
|
||||
.doc-modal-open {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem;
|
||||
min-height: 44px;
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: transparent;
|
||||
color: var(--ink-soft);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
.doc-modal-open:hover { background: var(--brand-soft); color: var(--brand-ink); }
|
||||
.doc-modal-open svg { width: 15px; height: 15px; display: none; }
|
||||
|
||||
/* Icon-only close (aria-label in the markup). ink-soft on surface ≈6.9:1;
|
||||
hover = the err pair ≈9.1:1, like the steering-note delete. */
|
||||
.doc-modal-close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
padding: 0;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--line);
|
||||
background: transparent;
|
||||
color: var(--ink-soft);
|
||||
cursor: pointer;
|
||||
}
|
||||
.doc-modal-close:hover { background: var(--err-bg); color: var(--err-ink); border-color: var(--err-line); }
|
||||
.doc-modal-close:focus-visible {
|
||||
outline: 3px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.doc-modal-close svg { width: 18px; height: 18px; display: block; }
|
||||
|
||||
/* Meta row: the SAME badge classes as the viewer's .doc-meta (source
|
||||
badge · format badge · mono path · indexed · chunks — no duplicate
|
||||
badge styling here); unlike the fixed-height header bar it may WRAP,
|
||||
so nothing clips. aria-live="polite" on the element announces the
|
||||
load → meta swap (phase-10 a11y contract, modal variant). */
|
||||
.doc-modal-meta {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.6rem 1.25rem 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
/* The scroll container: vertical scroll lives HERE, never the viewport.
|
||||
.doc-md keeps its ≤46rem centered reading column inside; .doc-raw keeps
|
||||
its own overflow-x. tabindex="-1" in the markup is the JS focus target. */
|
||||
.doc-modal-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 1rem 1.25rem 1.5rem;
|
||||
}
|
||||
.doc-modal-loading { margin: 1.5rem auto; text-align: center; color: var(--ink-soft); }
|
||||
|
||||
/* No motion under reduced motion (same pattern as the phase-25 layers). */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.doc-modal-backdrop { transition: none; }
|
||||
}
|
||||
|
||||
/* ---------- Footer ---------- */
|
||||
.app-footer {
|
||||
border-top: 1px solid var(--line);
|
||||
@@ -1270,6 +1427,16 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
.doc-path { max-width: 16rem; }
|
||||
.doc-md { padding: 1.1rem 1rem; }
|
||||
.doc-raw { padding: 1rem; font-size: 0.8rem; }
|
||||
/* Phase 26: the modal bar squeezes like the other bars — the Full page
|
||||
pill goes icon-only (aria-label keeps the name), the title clips;
|
||||
the panel stays 96vw × 92vh, so no horizontal overflow at 360px. */
|
||||
.doc-modal-header { gap: 0.5rem; padding-inline: 0.9rem; }
|
||||
.doc-modal-title { font-size: 1.1rem; }
|
||||
.doc-modal-open { padding: 0.4rem 0.55rem; }
|
||||
.doc-modal-open svg { display: block; }
|
||||
.doc-modal-open span { display: none; }
|
||||
.doc-modal-meta { padding-inline: 0.9rem; }
|
||||
.doc-modal-content { padding: 0.75rem 0.9rem 1.25rem; }
|
||||
.composer { padding: 0.5rem; }
|
||||
.footer-inner { flex-direction: column; gap: 0.2rem; text-align: center; }
|
||||
main { padding-bottom: env(safe-area-inset-bottom, 0); }
|
||||
|
||||
Reference in New Issue
Block a user