ZDDC/browse/css/tree.css
2026-06-11 13:32:31 -05:00

1069 lines
30 KiB
CSS

/* ── Layout ──────────────────────────────────────────────────────────────── */
html, body {
margin: 0;
padding: 0;
height: 100%;
font-family: var(--font);
color: var(--text);
background-color: var(--bg);
}
/* Body is a flex column so the header (which may wrap to a second
row at narrow viewports), #appMain, and the status bar each get
their natural height — no more fixed-pixel calc() that breaks
when the header reflows. Horizontal overflow scrolls on the body
as a final fallback when content can't shrink any further. */
body {
display: flex;
flex-direction: column;
height: 100vh;
overflow-x: auto;
overflow-y: hidden;
/* Hard floor for the body. Below this, the html-level scrollbar
picks up and the user can pan horizontally rather than seeing
the right edge clipped. */
min-width: 320px;
}
#appMain {
position: relative;
flex: 1 1 auto;
min-height: 0;
height: auto; /* override the old calc(100vh - 2.65rem) */
display: flex;
flex-direction: column;
overflow: hidden;
}
.browse-root {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
overflow: hidden;
background: var(--bg);
}
/* ── Toolbar ─────────────────────────────────────────────────────────────── */
.browse-toolbar {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.4rem 1rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.view-mode-toggle {
display: inline-flex;
gap: 0;
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.view-mode-toggle .btn {
border-radius: 0;
border: none;
border-right: 1px solid var(--border);
}
.view-mode-toggle .btn:last-child {
border-right: none;
}
.view-mode-toggle .btn[aria-selected="true"] {
background: var(--primary);
color: var(--text-light);
}
/* Breadcrumbs */
.breadcrumbs {
flex: 1;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.15rem 0.4rem;
font-size: 0.85rem;
color: var(--text-muted);
min-width: 0;
}
.breadcrumbs a,
.breadcrumbs button {
color: var(--text-muted);
background: none;
border: 0;
padding: 0.1rem 0.3rem;
border-radius: var(--radius);
cursor: pointer;
text-decoration: none;
font: inherit;
}
.breadcrumbs a:hover,
.breadcrumbs button:hover {
color: var(--text);
background: var(--bg-hover);
}
.breadcrumbs .bc-sep {
color: var(--text-muted);
user-select: none;
}
.breadcrumbs .bc-current {
color: var(--text);
font-weight: 600;
padding: 0.1rem 0.3rem;
}
.bc-home-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
}
/* ── Two-pane browse view ────────────────────────────────────────────────── */
.browse-view {
display: flex;
flex: 1;
overflow: hidden;
min-height: 0;
}
.pane {
overflow: hidden;
background: var(--bg);
display: flex;
flex-direction: column;
}
.tree-pane {
width: 360px;
min-width: 200px;
max-width: 60%;
border-right: 1px solid var(--border);
flex-shrink: 0;
}
.tree-pane__toolbar {
padding: 0.4rem 0.5rem;
border-bottom: 1px solid var(--border);
background: var(--bg-secondary);
flex-shrink: 0;
}
/* Single-input autofilter — same grammar as the archive app's column
filters (terms, quotes, !negation, multi-word AND). type=search so
the browser ships the native clear-X for free; the .filter-active
class amber-highlights the input while a query is set, matching
the archive `.column-filter.filter-active` cue. */
.tree-filter {
width: 100%;
box-sizing: border-box;
padding: 0.3rem 0.5rem;
font-family: var(--font);
font-size: 0.85rem;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
outline: none;
transition: border-color 0.12s, background 0.12s;
}
.tree-filter:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px var(--primary-light);
}
.tree-filter.filter-active {
background: rgba(234, 179, 8, 0.18);
border-color: rgba(234, 179, 8, 0.7);
}
.tree-pane__body {
flex: 1;
overflow: auto;
padding: 0.25rem 0;
font-size: 0.875rem;
}
/* Pane resizer — 4px grab handle between tree and preview */
.pane-resizer {
width: 4px;
background: transparent;
cursor: col-resize;
flex-shrink: 0;
position: relative;
z-index: 1;
}
.pane-resizer:hover,
.pane-resizer.is-dragging {
background: var(--primary);
}
.preview-pane {
flex: 1;
min-width: 0;
}
.preview-pane__header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.75rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
min-height: 2.1rem;
}
.preview-pane__title {
flex: 1;
font-size: 0.9rem;
font-weight: 500;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.preview-pane__meta {
font-size: 0.8rem;
color: var(--text-muted);
white-space: nowrap;
}
.preview-pane__body {
flex: 1;
min-height: 0; /* critical: lets the flex child shrink to fit
the viewport instead of growing to its
content's natural size (which clips the
YAML editor's bottom when there are many
lines, even with the editor's own scroll) */
min-width: 0;
overflow: auto;
display: flex;
flex-direction: column;
background: var(--bg);
}
/* The body's children fill the available space. Plugins inject
different content here — img, iframe, pre, custom markdown editor.
min-width:0 is load-bearing: a flex item defaults to min-width:auto
(its min-content width), so the markdown editor's wide internal
min-content would push the whole pane past the viewport's right edge
instead of shrinking. With min-width:0 the editor shrinks and its own
(and the grid's minmax(0)) scrolling takes over. */
.preview-pane__body > * {
flex: 1;
min-height: 0;
min-width: 0;
}
.preview-empty {
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 0.95rem;
padding: 2rem;
text-align: center;
}
.preview-pane__body img.preview-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
margin: auto;
display: block;
flex: none; /* avoid flex sizing interfering with object-fit */
}
.preview-pane__body iframe.preview-iframe {
width: 100%;
height: 100%;
border: none;
}
.preview-pane__body pre.preview-text {
padding: 1rem;
font-family: var(--font-mono);
font-size: 0.85rem;
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
overflow: auto;
background: var(--bg);
color: var(--text);
}
/* ── Tree (vertical, file-explorer style) ───────────────────────────────── */
.tree-row {
display: flex;
/* Top-aligned so the chevron + icon anchor to the title line on
two-line ZDDC rows. Single-line rows are unaffected because the
icon, chevron, and label all share a top edge. */
align-items: flex-start;
gap: 0.25rem;
padding: 0.2rem 0.5rem;
cursor: pointer;
user-select: none;
border-radius: 0;
color: var(--text);
}
.tree-row:hover {
background: var(--bg-hover);
}
.tree-row.is-selected {
background: var(--bg-selected);
color: var(--text);
}
/* Per-row "⋯" actions button — the visible affordance that a row has a
context menu. Hidden until the row is hovered/selected or the button
itself is keyboard-focused, so it stays out of the way during reading
but is discoverable without knowing to right-click. Pushed to the right
edge; never part of the tab order (rows use roving tabindex). */
.tree-row__kebab {
margin-left: auto;
align-self: flex-start;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.4rem;
height: 1.4rem;
padding: 0;
border: none;
background: transparent;
color: var(--text-muted, #888);
border-radius: var(--radius);
cursor: pointer;
opacity: 0;
transition: opacity 0.1s, background 0.1s, color 0.1s;
}
.tree-row__kebab svg { width: 1em; height: 1em; }
.tree-row:hover .tree-row__kebab,
.tree-row.is-selected .tree-row__kebab,
.tree-row__kebab:focus-visible {
opacity: 1;
}
.tree-row__kebab:hover,
.tree-row__kebab:focus-visible {
background: var(--bg-hover);
color: var(--text);
}
/* Tree-pane toolbar controls row (New folder/file, Sort, Show hidden),
sitting under the filter input. */
.tree-pane__controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.4rem;
}
.tree-pane__controls .tp-control {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.8rem;
color: var(--text-muted, #888);
}
.tree-pane__controls .tp-control--check { cursor: pointer; }
.tree-pane__controls select {
font-family: var(--font);
font-size: 0.8rem;
color: var(--text);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.15rem 0.3rem;
}
/* Per-row drop target highlight: applied while a file/folder drag is
hovering this row. The dashed outline reads as "drop here" without
shifting layout. */
.tree-row.is-droptarget {
background: var(--primary-light);
outline: 2px dashed var(--primary);
outline-offset: -2px;
}
.tree-row.is-selected .tree-name__label {
color: var(--text);
}
.tree-name__chevron {
/* Fixed-width slot so leaf rows (empty chevron) still align with
expandable rows. The SVG inside is sized via the rule below.
Top-anchored to the title-line baseline by the row's flex-start
alignment + this small top offset. */
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1.2em;
flex-shrink: 0;
color: var(--text-muted);
}
.tree-name__chevron svg {
width: 0.85em;
height: 0.85em;
transition: transform 0.12s ease;
}
/* Expanded state — rotate the same chevron 90° rather than swapping
to a second glyph. Smooth, single-sprite, and consistent with the
way most modern file trees indicate expand state. */
.tree-row.expanded .tree-name__chevron svg {
transform: rotate(90deg);
}
.tree-name__icon {
flex-shrink: 0;
/* Stacked column — glyph on top, extension chip below for files.
Wider min-width than the 1em glyph itself so common extensions
(pdf/docx/xlsx/json) don't push the label sideways. Height
grows with content; flex-start anchors to the title-line. */
min-width: 2.2em;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
color: var(--text-muted);
gap: 1px;
}
.tree-name__icon svg {
width: 1em;
height: 1em;
display: block;
}
.tree-name__ext {
font-size: 0.58rem;
line-height: 1;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 600;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Folder rows get the primary accent so directories stand out from
files at a glance — same convention as macOS Finder / GNOME Files. */
.tree-row[data-isdir="true"] .tree-name__icon,
.tree-row[data-iszip="true"] .tree-name__icon {
color: var(--primary);
}
/* Selected rows tint icon to match the label color (the bg-selected
token already differentiates the row background). */
.tree-row.is-selected .tree-name__icon {
color: var(--text);
}
.tree-name__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text);
min-width: 0;
}
/* Two-line ZDDC variant. Top line is monospace + small + muted so the
trackingNumber / revision / status fields line up vertically across
adjacent rows (every field has a fixed width by convention). Bottom
line is the human-readable title at normal weight. */
.tree-name__label--zddc {
display: flex;
flex-direction: column;
line-height: 1.15;
/* Tight gap between meta and title; tweak by 1-2 px if the rows
feel crowded on dense lists. */
gap: 0.05rem;
}
.tree-name__meta {
font-family: var(--font-mono);
font-size: 0.7rem;
/* Explicit weight: the folder-row rule below bolds .tree-name__label,
which would otherwise inherit through to the meta span. We want
the meta to stay light + muted on every row. */
font-weight: 400;
color: var(--text-muted);
/* Belt-and-braces: monospace already gives column-alignment, but
tabular-nums hardens it on the rare proportional fallback. */
font-variant-numeric: tabular-nums;
letter-spacing: 0.01em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-name__title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text);
}
.tree-row.is-selected .tree-name__title {
color: var(--text);
}
.tree-row[data-isdir="true"] .tree-name__label,
.tree-row[data-iszip="true"] .tree-name__label {
font-weight: 500;
}
/* ── Drag-drop upload overlay ─────────────────────────────────────────────── */
/* Shown only while a drag is active over the page AND the current scope
accepts uploads. Pointer-events:none below dragover so the underlying
drop event still reaches the document handlers. */
.upload-overlay {
position: fixed;
inset: 0;
z-index: 50;
pointer-events: none;
background: rgba(42, 90, 138, 0.18);
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.12s ease;
}
.upload-overlay.is-active {
opacity: 1;
}
.upload-overlay__panel {
background: var(--bg);
border: 2px dashed var(--primary);
border-radius: var(--radius);
padding: 1.5rem 2.25rem;
text-align: center;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
pointer-events: none;
color: var(--text);
max-width: 80vw;
}
.upload-overlay__icon {
font-size: 2.5rem;
line-height: 1;
color: var(--primary);
}
.upload-overlay__title {
font-family: var(--font-display);
font-size: 1.15rem;
font-weight: 600;
margin-top: 0.5rem;
}
.upload-overlay__path {
margin-top: 0.35rem;
font-family: var(--font-mono);
font-size: 0.82rem;
color: var(--text-muted);
word-break: break-all;
}
/* Virtual rows: synthesized for folders/files declared by the
cascade but absent from disk. The visual language reads as
"expected, not yet materialized" — italic label, muted accent
color, dashed left rail, and an outlined icon. Hover/select
chrome still applies on top; the dashed rail sits inside the row
so it doesn't fight padding-left indentation. */
.tree-row--virtual {
box-shadow: inset 2px 0 0 0 transparent;
position: relative;
}
.tree-row--virtual::before {
content: '';
position: absolute;
top: 4px;
bottom: 4px;
left: 2px;
border-left: 2px dashed var(--accent-muted, #8aa4cc);
pointer-events: none;
}
.tree-row--virtual .tree-name__label {
font-style: italic;
color: var(--text-muted, #6b7280);
}
.tree-row--virtual .tree-name__icon {
/* Hollow out the filled Lucide glyph: reduce fill opacity so
the icon reads as an outline-only sketch — the conventional
"placeholder, not actual" cue across UI systems. */
opacity: 0.5;
}
.tree-row--virtual .tree-name__icon svg {
fill: none;
stroke: currentColor;
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
}
.tree-row--virtual.is-selected::before {
/* Selected virtual row: rail brightens to selection accent so the
row reads as both selected and placeholder. */
border-left-color: var(--accent, #2868c8);
}
.tree-name__hint {
margin-left: 0.5rem;
font-size: 0.78rem;
color: var(--accent-muted, #8aa4cc);
font-style: italic;
}
/* ── Grid view (Phase C) ─────────────────────────────────────────────────── */
.grid-view {
flex: 1;
overflow: auto;
background: var(--bg);
padding: 0;
}
.grid-empty {
padding: 3rem;
text-align: center;
color: var(--text-muted);
}
/* ── Markdown plugin (right-pane internals when a .md is selected) ──────── */
/* CSS-Grid shell mirroring mdedit's layout: sidebar on the LEFT
(front matter top + TOC bottom), content on the RIGHT (informational
header above the Toast UI editor). The grid gives every cell a
definite size, which Toast UI needs to compute its scroll regions
correctly. */
.md-shell {
display: grid;
grid-template-rows: 1fr;
/* minmax(0, …) on BOTH tracks is load-bearing: a bare `1fr` is
`minmax(auto, 1fr)`, whose `auto` floor is the editor's min-content
width (Toast UI's toolbar). That floor stops the content track from
shrinking, so the whole shell overflows #previewBody as the window
narrows instead of the editor getting narrower. minmax(0, 1fr) drops
the floor so the editor reflows down to nothing. JS overrides the
column widths on drag — it preserves the minmax(0, …) form. */
grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
grid-template-areas: "sidebar content";
height: 100%;
min-height: 0;
background: var(--bg);
overflow: hidden;
}
/* Sidebar (col 1): three stacked items — Front matter (fixed height,
drag-resizable), the horizontal resizer (between FM and TOC), then
the TOC section taking the remaining height. Flexbox keeps the
resizer position unambiguous; the previous grid-overlay approach
was hard to read and prone to misplacement. */
.md-shell__sidebar {
grid-area: sidebar;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--border);
background: var(--bg);
position: relative;
}
/* Vertical sidebar/content resizer. Sits absolutely on the column
boundary so it doesn't occupy a grid track. */
.md-shell__resizer {
grid-area: sidebar;
align-self: stretch;
justify-self: end;
width: 6px;
margin-right: -3px;
cursor: col-resize;
background: transparent;
z-index: 2;
transition: background 0.12s;
}
.md-shell__resizer:hover,
.md-shell__resizer.is-dragging,
.md-shell__resizer:focus-visible {
background: var(--primary);
outline: none;
}
/* Horizontal resizer — a real flex item between FM and TOC. Drag
it up/down to change the front-matter pane's height; the JS
handler updates fmSection.style.height directly. */
.md-shell__fmresizer {
flex: 0 0 6px;
height: 6px;
cursor: row-resize;
background: var(--border);
transition: background 0.12s;
/* Subtle "grab" affordance — a slightly darker bar appears on
hover so users see this is the drag handle. */
}
.md-shell__fmresizer:hover,
.md-shell__fmresizer.is-dragging,
.md-shell__fmresizer:focus-visible {
background: var(--primary);
outline: none;
}
/* Content (col 2): informational header above the Toast UI editor. */
.md-shell__content {
grid-area: content;
display: grid;
grid-template-rows: auto 1fr;
min-width: 0;
min-height: 0;
overflow: hidden;
}
/* Informational header above the editor: file name on the left, then
dirty marker, status, source hint, save button. Reads as a header
for the content panel — file metadata at a glance. */
.md-shell__infohdr {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.4rem 0.75rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-size: 0.85rem;
}
.md-shell__title {
flex: 1;
font-family: var(--font-display);
font-size: 1rem;
font-weight: 600;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.md-shell__dirty {
color: var(--text-muted);
font-size: 0.85rem;
min-width: 5.5rem;
text-align: right;
}
.md-shell__status {
color: var(--text-muted);
font-size: 0.85rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 14rem;
}
.md-shell__source {
color: var(--text-muted);
font-size: 0.75rem;
font-style: italic;
padding: 0.15rem 0.4rem;
border-radius: var(--radius);
background: var(--bg);
border: 1px solid var(--border);
}
.md-shell__download {
/* Slightly tighter than the Save button so a row of three doesn't
crowd the title. The base .btn styles still drive padding/color. */
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
}
.md-shell__download[disabled] {
opacity: 0.55;
cursor: progress;
}
/* Editor host: a single grid cell with overflow:hidden so Toast UI's
internal scrollers handle the content. */
.md-shell__editor {
min-width: 0;
min-height: 0;
overflow: hidden;
}
.md-side {
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
/* Front-matter section: fixed (resizable) height, set inline by the
markdown plugin's mount + drag-handler. flex:0 0 auto so the
explicit height wins over the parent flex layout. */
.md-side--fm {
flex: 0 0 auto;
}
/* TOC section: takes everything that's left. min-height:0 so the
inner body's overflow:auto kicks in instead of pushing the
resizer off-screen. */
.md-side--toc {
flex: 1 1 auto;
min-height: 0;
}
.md-side__header {
/* Header is its own flex item so the body can stretch to fill. */
flex: 0 0 auto;
padding: 0.35rem 0.75rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
font-size: 0.72rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
}
.md-side__body {
/* Both axes — the textarea uses white-space:pre so long YAML
lines need horizontal scroll, and the TOC entries below now
extend their full width so deep headings need it too. */
flex: 1 1 auto;
overflow: auto;
min-height: 0;
padding: 0.3rem 0;
font-size: 0.85rem;
line-height: 1.45;
}
/* ── Outline list ───────────────────────────────────────────────────────── */
.md-toc__empty {
color: var(--text-muted);
font-style: italic;
padding: 0.5rem 0.75rem;
margin: 0;
font-size: 0.82rem;
}
.md-toc__list {
list-style: none;
margin: 0;
padding: 0;
}
.md-toc__item {
margin: 0;
padding: 0.22rem 0.75rem;
color: var(--text);
cursor: pointer;
border-left: 2px solid transparent;
transition: background 0.1s, border-color 0.1s, color 0.1s;
/* Single-line items but no ellipsis — long headings extend the
item's intrinsic width, and the parent .md-side__body has
overflow:auto, so they create a horizontal scrollbar instead
of getting clipped. The title attribute still carries the
full text for SR users. */
white-space: nowrap;
}
.md-toc__item:hover {
background: var(--bg-secondary);
border-left-color: var(--primary);
}
.md-toc__item:focus-visible {
outline: 2px solid var(--primary);
outline-offset: -2px;
}
.md-toc__item--l1 { padding-left: 0.75rem; font-weight: 600; }
.md-toc__item--l2 { padding-left: 1.4rem; }
.md-toc__item--l3 { padding-left: 2.05rem; font-size: 0.82rem; }
.md-toc__item--l4 { padding-left: 2.7rem; font-size: 0.8rem; color: var(--text-muted); }
.md-toc__item--l5 { padding-left: 3.35rem; font-size: 0.78rem; color: var(--text-muted); }
.md-toc__item--l6 { padding-left: 4rem; font-size: 0.78rem; color: var(--text-muted); }
/* Flash on click — applied to the heading element in the editor pane.
The class is scoped to .md-toc__flash so it doesn't paint outside
this plugin. */
.md-toc__flash {
background-color: rgba(95, 168, 224, 0.25) !important;
transition: background-color 0.3s ease;
}
/* ── Front matter editor ────────────────────────────────────────────────── */
.md-fm__body {
/* Body cell owns the CodeMirror editor; sized by the sidebar's grid row. */
padding: 0;
display: block;
overflow: hidden;
}
/* Recognised-keys caption under the header (tooltip carries the full list). */
.md-fm__hint {
padding: 2px 0.6rem 4px;
font-size: 0.72rem;
color: var(--text-muted);
cursor: help;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* CodeMirror YAML front-matter editor — fills the body cell + scrolls
internally, matching the .zddc previewer's editor styling. */
.md-fm__editor,
.md-fm__editor .CodeMirror {
height: 100%;
}
.md-fm__editor .CodeMirror {
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Consolas, monospace);
font-size: 0.8rem;
line-height: 1.45;
background: transparent;
color: var(--text);
}
.md-fm__editor .CodeMirror-gutters {
background: var(--bg-secondary);
border-right: 1px solid var(--border);
}
/* Schema-completion dropdown (show-hint add-on) — theme it to the app
palette so it reads in dark mode; show-hint.css ships light-only. */
.CodeMirror-hints {
z-index: 9600;
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Consolas, monospace);
font-size: 0.78rem;
background: var(--bg-elevated, var(--bg, #fff));
border: 1px solid var(--border, #ccc);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.28);
}
.CodeMirror-hint {
color: var(--text, #222);
padding: 2px 8px;
}
li.CodeMirror-hint-active {
background: var(--primary, #2868c8);
color: #fff;
}
/* Older .md-fm-section / .fm-list / .md-toc-resizer rules were replaced
by the .md-shell BEM block above. */
/* ── Hover info card ────────────────────────────────────────────────────── */
/* Singleton element appended to <body> by browse/js/hovercard.js.
Replaces the native title="…" tooltip on tree rows with a rich
metadata view (ZDDC parse fields + filesystem info). */
.tree-hovercard {
position: fixed;
z-index: 9000;
max-width: 28rem;
min-width: 17rem;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18),
0 2px 6px rgba(0, 0, 0, 0.10);
padding: 0.5rem 0.7rem 0.45rem;
font-family: var(--font);
font-size: 0.8rem;
line-height: 1.35;
opacity: 0;
visibility: hidden;
/* pointer-events:auto so the user can mouse into the card to
select text. The hide is delayed (HIDE_DELAY_MS in hovercard.js)
so the cursor has time to traverse the gap between row and card
before the card dismisses. */
pointer-events: auto;
/* The tree rows set user-select:none — explicitly allow it here
so dragging across the card builds a real selection that can be
Ctrl/Cmd-C'd or right-click-Copied via the browser's native menu. */
user-select: text;
cursor: default;
transition: opacity 0.1s ease;
}
.tree-hovercard.is-visible {
opacity: 1;
visibility: visible;
}
/* Highlight selected text inside the card with the primary accent so
it reads as "yes, you can copy this" rather than the default browser
selection color. */
.tree-hovercard ::selection {
background: var(--primary-light);
color: var(--text);
}
.tree-hovercard__header {
margin-bottom: 0.35rem;
}
.tree-hovercard__title {
font-weight: 600;
font-size: 0.95rem;
line-height: 1.2;
color: var(--text);
word-break: break-word;
}
.tree-hovercard__sub {
margin-top: 0.15rem;
font-family: var(--font-mono);
font-size: 0.72rem;
color: var(--text-muted);
letter-spacing: 0.01em;
}
.tree-hovercard__list {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0.12rem 0.7rem;
align-items: baseline;
}
.tree-hovercard__key {
color: var(--text-muted);
font-size: 0.74rem;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.tree-hovercard__val {
color: var(--text);
font-size: 0.82rem;
word-break: break-word;
}
.tree-hovercard__val--mono {
font-family: var(--font-mono);
font-size: 0.78rem;
}
/* Archive-reference links inside the hovercard pick up the primary
accent so they read as clickable, and stay inline with the mono
font when they sit inside a mono cell. */
.tree-hovercard__val a {
color: var(--primary, #2868c8);
text-decoration: none;
}
.tree-hovercard__val a:hover {
text-decoration: underline;
}
/* Separator stretches across both grid columns. Bleed into the
card's padding so it visually reads as a divider, not a hairline. */
.tree-hovercard__sep {
grid-column: 1 / -1;
border-top: 1px solid var(--border);
margin: 0.25rem -0.7rem;
}