Adds a UI checkbox next to the existing Sort dropdown that surfaces
hidden entries when ACL would otherwise allow read. Default off
(matches today's filtered behavior). On toggle, browse re-fetches
the current directory with ?hidden=1 and re-renders.
┌─ browse toolbar ─────────────────────────────────────────────┐
│ Sort: [Name (A→Z) ▾] ☐ Show hidden │
└──────────────────────────────────────────────────────────────┘
Server-side surface:
- internal/fs/tree.go ListDirectory gains an `includeHidden bool`
parameter. The .-prefix filter (previously hard-coded) now also
drops _-prefix entries (matches dispatch's reserved-prefix guard)
and honors the new flag.
- internal/handler/directory.go reads `?hidden=1` from the request
and threads it through.
- cmd/zddc-server/main.go dispatcher relaxes its dot-prefix and
_-prefix guards for GET/HEAD when `?hidden=1` is set, so clicking
a hidden entry's link works. `_app/` (apps cache) stays
unconditionally reserved — those bytes must go through the apps
resolver. Writes to hidden paths stay blocked (the file API has
its own segment check that the flag does NOT relax).
- internal/listing/listing.go: signature parity (the lower-level
helper that's used by tests + non-cascade listing paths).
Security model unchanged: the ACL chain on the parent dir is the only
real gate. Whoever can read the dir can see its contents — toggling
"Show hidden" just stops the client-side filter from masking
.-prefixed and _-prefixed entries. Hidden paths today:
• <dir>/.zddc ACL YAML — already exposed via /.profile/zddc
• <dir>/.converted/<base> cached MD→DOCX/HTML/PDF, same sensitivity as source
• <root>/.zddc.d/tokens/ per-token metadata; filename = sha256(token)
so not bearer-usable. Default root ACL
restricts to admins; matches /.tokens UI.
• <root>/.zddc.d/logs/ access logs; same admins-only audience
• <root>/_app/ cached upstream tool HTML (public)
• <root>/_template/ install.zip scaffolding (public)
None of these contain bearer credentials or secret material that the
existing ACL doesn't already gate. The walls are still the cascade.
713 lines
18 KiB
CSS
713 lines
18 KiB
CSS
/* ── Layout ──────────────────────────────────────────────────────────────── */
|
|
|
|
html, body {
|
|
margin: 0;
|
|
padding: 0;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
font-family: var(--font);
|
|
color: var(--text);
|
|
background-color: var(--bg);
|
|
}
|
|
|
|
#appMain {
|
|
position: relative;
|
|
height: calc(100vh - 2.65rem); /* clear .app-header */
|
|
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;
|
|
}
|
|
|
|
.toolbar__count {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* ── 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__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;
|
|
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. */
|
|
.preview-pane__body > * {
|
|
flex: 1;
|
|
min-height: 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;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.15rem 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);
|
|
}
|
|
|
|
.tree-row.is-selected .tree-name__label {
|
|
color: var(--text);
|
|
}
|
|
|
|
.tree-name__chevron {
|
|
display: inline-block;
|
|
width: 1rem;
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
flex-shrink: 0;
|
|
font-family: monospace;
|
|
font-size: 0.65rem;
|
|
}
|
|
|
|
.tree-row[data-isdir="true"] .tree-name__chevron::before,
|
|
.tree-row[data-iszip="true"] .tree-name__chevron::before {
|
|
content: "▸";
|
|
}
|
|
|
|
.tree-row[data-isdir="true"].expanded .tree-name__chevron::before,
|
|
.tree-row[data-iszip="true"].expanded .tree-name__chevron::before {
|
|
content: "▾";
|
|
}
|
|
|
|
.tree-name__chevron--leaf::before {
|
|
content: "";
|
|
}
|
|
|
|
.tree-name__icon {
|
|
flex-shrink: 0;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.tree-name__label {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
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 client-side for folders that aren't on
|
|
disk yet (canonical project folders). Rendered muted so the user
|
|
reads them as "available but empty" rather than ordinary entries.
|
|
Hover/select states still apply; the hint sits to the right of the
|
|
label. */
|
|
.tree-row--virtual .tree-name__icon,
|
|
.tree-row--virtual .tree-name__label {
|
|
opacity: 0.65;
|
|
}
|
|
.tree-name__hint {
|
|
margin-left: 0.5rem;
|
|
font-size: 0.78rem;
|
|
color: var(--text-muted);
|
|
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);
|
|
}
|
|
|
|
/* ── Status bar ──────────────────────────────────────────────────────────── */
|
|
|
|
.status-bar {
|
|
padding: 0.4rem 1rem;
|
|
background: var(--bg-secondary);
|
|
border-top: 1px solid var(--border);
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
min-height: 1.6rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.status-bar.is-error { color: var(--danger); }
|
|
.status-bar.is-info { color: var(--text); }
|
|
|
|
/* ── 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;
|
|
grid-template-columns: 280px 1fr; /* JS overrides on resize */
|
|
grid-template-areas: "sidebar content";
|
|
height: 100%;
|
|
min-height: 0;
|
|
background: var(--bg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Sidebar (col 1): two stacked sections — Front matter (top, fixed
|
|
default 180 px, drag-resizable) and TOC (bottom, takes the rest). */
|
|
.md-shell__sidebar {
|
|
grid-area: sidebar;
|
|
display: grid;
|
|
grid-template-rows: 180px 1fr; /* JS overrides on resize */
|
|
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 between front-matter and TOC inside the sidebar.
|
|
Spans both rows by placement, then absolutely positioned to overlay
|
|
the grid-row boundary. */
|
|
.md-shell__fmresizer {
|
|
grid-column: 1;
|
|
grid-row: 1;
|
|
align-self: end;
|
|
justify-self: stretch;
|
|
height: 6px;
|
|
margin-bottom: -3px;
|
|
cursor: row-resize;
|
|
background: transparent;
|
|
z-index: 2;
|
|
transition: background 0.12s;
|
|
}
|
|
.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: grid;
|
|
grid-template-rows: auto 1fr;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
.md-side--toc {
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
.md-side__header {
|
|
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 {
|
|
overflow-y: 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;
|
|
/* Truncate long headings rather than wrap; the title attribute
|
|
carries the full text. */
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
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 textarea; sized by the sidebar's grid row. */
|
|
padding: 0;
|
|
display: block;
|
|
overflow: hidden;
|
|
}
|
|
.md-fm__textarea {
|
|
width: 100%;
|
|
height: 100%;
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0.4rem 0.6rem;
|
|
border: 0;
|
|
background: transparent;
|
|
color: var(--text);
|
|
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Consolas, monospace);
|
|
font-size: 0.8rem;
|
|
line-height: 1.45;
|
|
resize: none;
|
|
outline: none;
|
|
white-space: pre;
|
|
overflow: auto;
|
|
tab-size: 2;
|
|
}
|
|
.md-fm__textarea::placeholder {
|
|
color: var(--text-muted);
|
|
font-style: italic;
|
|
}
|
|
.md-fm__textarea:focus {
|
|
background: var(--surface-2, rgba(0, 0, 0, 0.025));
|
|
}
|
|
.md-fm__textarea[readonly] {
|
|
color: var(--text-muted);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* ── Sort control ────────────────────────────────────────────────────────── */
|
|
.sort-control {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.sort-control__label {
|
|
user-select: none;
|
|
}
|
|
|
|
.sort-control__select {
|
|
font-family: var(--font);
|
|
font-size: 0.8rem;
|
|
padding: 0.2rem 0.4rem;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sort-control__select:focus {
|
|
outline: 2px solid var(--primary);
|
|
outline-offset: -1px;
|
|
}
|
|
|
|
.sort-control__checkbox {
|
|
/* Pair with the "Show hidden" label as a unified control. The
|
|
parent .sort-control already does horizontal flex + gap, so the
|
|
checkbox just needs sensible vertical alignment + a clickable
|
|
hit target. */
|
|
margin: 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Older .md-fm-section / .fm-list / .md-toc-resizer rules were replaced
|
|
by the .md-shell BEM block above. */
|