Files
docmost/apps/client/src/features/public-space/components/docs/docs.module.css
T
2026-09-05 17:20:14 +01:00

959 lines
20 KiB
CSS

/* Design tokens for the public docs surface. Declared on :root (not .root)
* because mobile drawers and the spotlight render in portals outside the shell
* subtree. The accent pair is overridden at runtime by docs-theme.ts. */
/* light-dark() cannot live in a bare :root block (the transform emits a
* descendant selector that never matches :root), so dark values get their own
* attribute-qualified block; flipping semantics ride Mantine's own vars. */
:root {
--docs-header-h: 56px;
--docs-sidebar-w: 280px;
--docs-toc-w: 240px;
--docs-site-max: 96rem;
--docs-content-max: 54rem;
--docs-radius: 6px;
--docs-accent: #2b7af1;
--docs-accent-soft: color-mix(in srgb, var(--docs-accent) 10%, transparent);
/* Cloudflare-style single-ink model: one foreground for headings, bold, and
* body on a just-off-white page; neither end of the scale is pure. */
--docs-bg: oklch(99% 0 0);
--docs-fg: oklch(21% 0 0);
--docs-content-fg: var(--docs-fg);
--docs-nav-fg: oklch(47% 0 0);
--docs-muted: var(--mantine-color-dimmed);
--docs-hover: var(--mantine-color-default-hover);
--docs-faint: var(--mantine-color-gray-6);
--docs-border: var(--mantine-color-gray-2);
--docs-header-bg: color-mix(in srgb, var(--docs-bg) 78%, transparent);
}
:root[data-mantine-color-scheme="dark"] {
--docs-bg: var(--mantine-color-body);
--docs-fg: oklch(90% 0 0);
--docs-nav-fg: oklch(72% 0 0);
--docs-faint: var(--mantine-color-dark-2);
--docs-border: var(--mantine-color-dark-5);
}
.root {
min-height: 100dvh;
background-color: var(--docs-bg);
color: var(--docs-fg);
}
/* ---------- Header ---------- */
.header {
position: sticky;
top: 0;
z-index: 90;
height: var(--docs-header-h);
padding-inline: rem(20px);
background-color: var(--docs-header-bg);
backdrop-filter: saturate(180%) blur(10px);
-webkit-backdrop-filter: saturate(180%) blur(10px);
border-bottom: 1px solid var(--docs-border);
}
.headerInner {
max-width: var(--docs-site-max);
margin-inline: auto;
height: 100%;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: rem(16px);
}
.headerLeft {
display: flex;
align-items: center;
gap: rem(10px);
min-width: 0;
}
.headerCenter {
display: flex;
justify-content: center;
min-width: 0;
}
.headerRight {
display: flex;
align-items: center;
justify-content: flex-end;
gap: rem(4px);
}
.searchSlot {
@media (max-width: $mantine-breakpoint-sm) {
display: none;
}
}
.mobileOnly {
display: inline-flex;
@media (min-width: $mantine-breakpoint-sm) {
display: none;
}
}
/* Plain space name in the header, used only when there is no sidebar to
* carry it. The brand slot is reserved for future org logo/name support. */
.headerSpaceName {
font-size: rem(14.5px);
font-weight: 600;
letter-spacing: -0.011em;
color: var(--docs-fg);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-radius: var(--docs-radius);
padding: rem(4px) rem(6px);
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 1px;
}
}
/* Burger appears only once the sidebar column is collapsed. */
@media (min-width: 64em) {
.sidebarToggle {
display: none !important;
}
}
.headerAction {
color: var(--docs-faint);
border-radius: var(--docs-radius);
@media (hover: hover) {
&:hover {
color: var(--docs-fg);
background-color: var(--docs-hover);
}
}
}
/* ---------- Search ---------- */
.searchButton {
display: flex;
align-items: center;
gap: rem(8px);
width: rem(320px);
height: rem(34px);
padding-inline: rem(10px);
border: 1px solid var(--docs-border);
border-radius: rem(8px);
background-color: var(--docs-bg);
color: var(--docs-muted);
font-size: rem(13px);
cursor: pointer;
transition: border-color 120ms ease;
@media (hover: hover) {
&:hover {
border-color: light-dark(
var(--mantine-color-gray-4),
var(--mantine-color-dark-3)
);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 1px;
}
@media (max-width: 62em) {
width: rem(220px);
}
}
.searchLabel {
flex: 1;
text-align: left;
}
.searchKbd {
font-size: rem(11px);
font-weight: 500;
color: var(--docs-faint);
border: 1px solid var(--docs-border);
border-radius: rem(4px);
padding: rem(1px) rem(5px);
line-height: 1.4;
}
/* Quiet bordered chip in the docs palette instead of Mantine's full-ink default. */
.copyPageButton {
color: var(--docs-muted);
border-color: var(--docs-border);
background-color: transparent;
font-size: rem(13px);
font-weight: 500;
border-radius: var(--docs-radius);
@media (hover: hover) {
&:hover {
color: var(--docs-fg);
background-color: var(--docs-hover);
}
}
}
/* Page actions pinned to the article's top-right corner; on small viewports
* they drop into flow above the content so breadcrumbs never run under them. */
.articleActions {
position: absolute;
top: rem(30px);
right: rem(24px);
display: inline-flex;
align-items: center;
gap: rem(6px);
@media (max-width: $mantine-breakpoint-sm) {
position: static;
display: flex;
justify-content: flex-end;
margin-bottom: rem(4px);
}
}
/* Below the rail breakpoint the toc opens as a drawer overlay instead. */
.tocOverlayControl {
display: inline-flex;
@media (min-width: 75em) {
display: none;
}
}
/* ---------- Body grid ---------- */
/* Contained site layout: rails anchor the edges of a centered max-width
* container, the article centers itself in the fixed middle track. Hiding a
* rail keeps its track, so toggling never moves the content column. */
.body {
max-width: var(--docs-site-max);
margin-inline: auto;
display: grid;
grid-template-columns:
var(--docs-sidebar-w)
minmax(0, 1fr)
var(--docs-toc-w);
align-items: start;
}
.sidebar {
grid-column: 1;
width: var(--docs-sidebar-w);
position: sticky;
top: var(--docs-header-h);
height: calc(100dvh - var(--docs-header-h));
display: flex;
flex-direction: column;
padding: rem(20px) rem(10px) rem(16px) rem(20px);
opacity: 1;
transform: translateX(0);
transition:
opacity 160ms ease,
transform 160ms ease;
}
.sidebar[data-hidden="true"] {
visibility: hidden;
opacity: 0;
transform: translateX(rem(-8px));
}
.sidebarTitle {
display: block;
font-size: rem(14px);
font-weight: 600;
letter-spacing: -0.011em;
color: var(--docs-fg);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border-radius: var(--docs-radius);
padding: rem(5px) rem(8px);
@media (hover: hover) {
&:hover {
color: var(--docs-accent);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: -2px;
}
}
.sidebarDivider {
height: 1px;
background-color: var(--docs-border);
margin: rem(10px) 0 rem(14px);
flex-shrink: 0;
}
.sidebarScroll {
flex: 1;
min-height: 0;
}
.main {
grid-column: 2;
min-width: 0;
}
.article {
max-width: var(--docs-content-max);
margin-inline: auto;
padding: rem(36px) rem(32px) rem(72px);
position: relative;
}
.toc {
grid-column: 3;
width: var(--docs-toc-w);
position: sticky;
top: var(--docs-header-h);
max-height: calc(100dvh - var(--docs-header-h));
overflow-y: auto;
scrollbar-width: thin;
padding: rem(36px) rem(16px) rem(24px) rem(4px);
}
@media (max-width: 75em) {
.body {
grid-template-columns: var(--docs-sidebar-w) minmax(0, 1fr);
}
.toc {
display: none;
}
}
/* Below 64em the sidebar collapses into the burger drawer. */
@media (max-width: 64em) {
.body {
display: block;
}
.sidebar {
display: none;
}
}
@media (max-width: $mantine-breakpoint-sm) {
.article {
padding: rem(24px) rem(20px) rem(56px);
}
}
@media (prefers-reduced-motion: reduce) {
.body,
.sidebar,
.toc,
.searchButton {
transition: none;
}
}
/* ---------- Sidebar tree ---------- */
/* Applied through DocTree's rowClassName onto the engine wrapper that carries
* data-selected. Class doubled to outrank the engine's own module styles. */
.treeNodeChrome.treeNodeChrome {
border-radius: var(--docs-radius);
color: var(--docs-nav-fg);
@media (hover: hover) {
&:hover {
background-color: var(--docs-hover);
color: var(--docs-fg);
}
}
}
.treeNodeChrome.treeNodeChrome[data-selected="true"] {
background-color: var(--docs-accent-soft);
}
.treeRow {
position: relative;
display: flex;
align-items: flex-start;
gap: rem(8px);
width: 100%;
min-width: 0;
min-height: rem(32px);
padding: rem(6px) rem(4px) rem(6px) rem(8px);
text-decoration: none;
color: inherit;
font-size: rem(14px);
font-weight: 400;
line-height: 1.45;
border-radius: var(--docs-radius);
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: -2px;
}
}
/* Expanded parents read as section headers, Cloudflare-style. */
.treeRow[data-open-parent="true"] {
color: var(--docs-fg);
font-weight: 500;
}
.treeRow[data-selected="true"] {
color: var(--docs-accent);
font-weight: 500;
}
.treeIcon {
display: inline-flex;
align-items: center;
justify-content: center;
width: rem(18px);
font-size: rem(14px);
line-height: 1;
flex-shrink: 0;
margin-top: rem(2px);
}
/* Names wrap instead of truncating: the sidebar has a fixed width, so an
* ellipsis would permanently hide the tail of long titles. */
.treeText {
flex: 1;
min-width: 0;
overflow-wrap: anywhere;
}
/* Nesting rail: each row draws its ancestors' segments in the indent gutter;
* the -2px bleed covers the engine's row padding so segments connect. */
.treeGuide {
position: absolute;
top: 0;
bottom: rem(-2px);
width: 1px;
background-color: var(--docs-border);
pointer-events: none;
}
.treeChevron {
flex-shrink: 0;
color: var(--docs-faint);
transition: transform 140ms ease;
}
.treeChevron[data-open="true"] {
transform: rotate(90deg);
}
@media (prefers-reduced-motion: reduce) {
.treeChevron {
transition: none;
}
}
/* ---------- Table of contents ---------- */
.tocLabel {
display: block;
font-size: rem(11px);
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--docs-muted);
margin-bottom: rem(10px);
}
.tocList {
border-left: 1px solid var(--docs-border);
}
.tocLink {
display: block;
width: 100%;
text-align: left;
background: none;
border: 0;
border-left: 2px solid transparent;
margin-left: -1px;
padding: rem(4px) rem(8px) rem(4px) rem(11px);
font-size: rem(13px);
line-height: 1.45;
color: var(--docs-muted);
cursor: pointer;
overflow-wrap: break-word;
@media (hover: hover) {
&:hover {
color: var(--docs-fg);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: -2px;
border-radius: rem(2px);
}
}
.tocLink[data-active="true"] {
color: var(--docs-accent);
border-left-color: var(--docs-accent);
font-weight: 500;
}
/* ---------- Edit page (signed-in visitors) ---------- */
.editPageLink {
display: flex;
align-items: center;
gap: rem(6px);
margin-top: rem(16px);
padding-top: rem(14px);
border-top: 1px solid var(--docs-border);
font-size: rem(13px);
color: var(--docs-muted);
text-decoration: none;
@media (hover: hover) {
&:hover {
color: var(--docs-fg);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 2px;
border-radius: rem(2px);
}
}
/* ---------- Sidebar branding experiments (GitBook card / ReadMe line) ---------- */
/* ---------- Footer branding ---------- */
.footer {
margin-top: rem(40px);
padding-top: rem(16px);
border-top: 1px solid var(--docs-border);
}
.footerBranding {
font-size: rem(14px);
color: var(--docs-muted);
text-decoration: none;
@media (hover: hover) {
&:hover {
color: var(--docs-fg);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 2px;
border-radius: rem(2px);
}
}
/* ---------- Breadcrumbs ---------- */
.breadcrumbs {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: rem(4px);
font-size: rem(13px);
color: var(--docs-muted);
margin-bottom: rem(6px);
/* keep long trails clear of the pinned page actions */
padding-right: rem(160px);
@media (max-width: $mantine-breakpoint-sm) {
padding-right: 0;
}
}
.crumbLink {
color: var(--docs-muted);
text-decoration: none;
border-radius: rem(4px);
padding: rem(1px) rem(3px);
max-width: rem(220px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@media (hover: hover) {
&:hover {
color: var(--docs-accent);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 0;
}
}
.crumbSeparator {
color: light-dark(
var(--mantine-color-gray-4),
var(--mantine-color-dark-3)
);
user-select: none;
}
.crumbEllipsis {
color: var(--docs-muted);
border: 0;
background: none;
cursor: pointer;
border-radius: rem(4px);
padding: rem(1px) rem(4px);
@media (hover: hover) {
&:hover {
color: var(--docs-accent);
background-color: var(--docs-hover);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 0;
}
}
/* ---------- Byline ---------- */
/* Pulled up into the title's own bottom margin so it reads as one block. */
.byline {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: rem(8px);
margin-top: rem(-16px);
margin-bottom: rem(28px);
font-size: rem(13px);
color: var(--docs-muted);
}
.bylineAuthor {
display: inline-flex;
align-items: center;
gap: rem(6px);
}
.bylineDot {
color: var(--docs-faint);
user-select: none;
}
/* ---------- Prev / next ---------- */
.pageNav {
display: grid;
grid-template-columns: 1fr 1fr;
gap: rem(12px);
margin-top: rem(48px);
}
.pageNavCard {
display: flex;
flex-direction: column;
gap: rem(4px);
padding: rem(12px) rem(16px);
border: 1px solid var(--docs-border);
border-radius: rem(10px);
text-decoration: none;
min-width: 0;
transition: border-color 120ms ease;
@media (hover: hover) {
&:hover {
border-color: var(--docs-accent);
}
&:hover .pageNavTitle {
color: var(--docs-accent);
}
}
&:focus-visible {
outline: 2px solid var(--docs-accent);
outline-offset: 1px;
}
}
.pageNavCard[data-direction="next"] {
grid-column: 2;
align-items: flex-end;
text-align: right;
}
.pageNavLabel {
display: inline-flex;
align-items: center;
gap: rem(4px);
font-size: rem(12px);
color: var(--docs-muted);
}
.pageNavTitle {
font-size: rem(14px);
font-weight: 500;
color: var(--docs-fg);
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 120ms ease;
}
@media (max-width: $mantine-breakpoint-sm) {
.pageNav {
grid-template-columns: 1fr;
}
.pageNavCard[data-direction="next"] {
grid-column: auto;
}
}
/* ---------- Content ---------- */
/* Reading typography: body copy softens to a blue-gray with relaxed leading
* while headings and bold keep full contrast. Class doubled to outrank the
* shared .public-typography metrics regardless of stylesheet order. */
.root.root :global(.ProseMirror) {
color: var(--docs-content-fg);
line-height: 1.75;
letter-spacing: normal;
/* The article owns horizontal spacing here; the editor's 3rem gutter would
* misalign content with the breadcrumb, and its own background bands
* against the off-white page. */
padding-left: 0;
padding-right: 0;
background-color: transparent;
}
/* Wide columns bleed into the editor gutter that no longer exists here. */
.root.root :global(div[data-type="columns"][data-width-mode="wide"]) {
margin-left: 0;
margin-right: 0;
width: 100%;
}
/* One ink by inheritance: Mantine's baseline gives headings an explicit
* color, which forks them from the body. Neutralize instead of re-declaring,
* so changing the single content foreground repaints all text. */
.root.root :global(.ProseMirror) h1,
.root.root :global(.ProseMirror) h2,
.root.root :global(.ProseMirror) h3,
.root.root :global(.ProseMirror) h4,
.root.root :global(.ProseMirror) h5,
.root.root :global(.ProseMirror) h6,
.root.root :global(.ProseMirror) strong {
color: inherit;
}
/* Modest semibold heading scale (Cloudflare-style); class doubled to outrank
* the shared editor and .public-typography rules. */
.root.root :global(.ProseMirror) h1 {
font-size: 2.1875rem;
font-weight: 600;
letter-spacing: -0.025em;
line-height: 1.25;
}
.root.root :global(.ProseMirror) h2 {
font-size: 1.3rem;
font-weight: 600;
letter-spacing: -0.015em;
line-height: 1.4;
}
.root.root :global(.ProseMirror) h3 {
font-size: 1.1rem;
font-weight: 600;
letter-spacing: -0.01em;
line-height: 1.45;
}
.root.root :global(.ProseMirror) h4,
.root.root :global(.ProseMirror) h5,
.root.root :global(.ProseMirror) h6 {
font-size: 1rem;
font-weight: 600;
line-height: 1.5;
}
.root.root :global(.ProseMirror) strong {
font-weight: 600;
}
.root.root :global(.page-title .ProseMirror) h1 {
font-size: 2.1875rem;
font-weight: 600;
letter-spacing: -0.025em;
line-height: 1.25;
}
.root :global(.ProseMirror) a {
color: var(--docs-accent);
}
/* Internal page links (mentions, subpages lists) follow the accent like any
* other link; the app skin pins them to ink with !important, hence the
* counter-!important, and the underline tint softens to match. */
.root :global(.ProseMirror) a[class*="pageMentionLink"] {
color: var(--docs-accent) !important;
}
.root :global(.ProseMirror) [class*="pageMentionText"] {
border-bottom-color: color-mix(in srgb, var(--docs-accent) 40%, transparent);
}
.root :global(.ProseMirror) h1,
.root :global(.ProseMirror) h2,
.root :global(.ProseMirror) h3,
.root :global(.ProseMirror) h4 {
scroll-margin-top: calc(var(--docs-header-h) + rem(16px));
}
/* ---------- Content tables ---------- */
/* The wrapper carries the rounded outer border so border-collapse never
* fights border-radius; its overflow-x clips the corners. */
.root.root :global(.ProseMirror .tableWrapper) {
border: 1px solid var(--docs-border);
border-radius: rem(10px);
}
.root.root :global(.ProseMirror table td),
.root.root :global(.ProseMirror table th) {
border: 0;
border-bottom: 1px solid var(--docs-border);
border-right: 1px solid var(--docs-border);
padding: rem(10px) rem(14px);
}
.root.root :global(.ProseMirror table :is(td, th):last-child) {
border-right: 0;
}
.root.root :global(.ProseMirror table tr:last-child td),
.root.root :global(.ProseMirror table tr:last-child th) {
border-bottom: 0;
}
.root.root :global(.ProseMirror table th) {
background-color: var(--docs-hover);
color: var(--docs-fg);
font-weight: 600;
}
/* Round the corner cells too for the no-overflow (pinned header) variant,
* where the wrapper does not clip. */
.root.root :global(.ProseMirror table tr:first-child :is(th, td):first-child) {
border-top-left-radius: rem(9px);
}
.root.root :global(.ProseMirror table tr:first-child :is(th, td):last-child) {
border-top-right-radius: rem(9px);
}
.root.root :global(.ProseMirror table tr:last-child :is(th, td):first-child) {
border-bottom-left-radius: rem(9px);
}
.root.root :global(.ProseMirror table tr:last-child :is(th, td):last-child) {
border-bottom-right-radius: rem(9px);
}
.emptyState {
padding-top: rem(96px);
text-align: center;
color: var(--docs-muted);
}
/* ---------- Mobile drawers ---------- */
.drawerTree {
height: calc(100dvh - rem(60px));
display: flex;
flex-direction: column;
}
/* ---------- Print ---------- */
@page public-doc {
background-color: #fff;
}
/* Only the article prints; the body grid collapses so it takes the page width. */
@media print {
:global(html):has(.root),
:global(body):has(.root) {
page: public-doc;
background-color: #fff !important;
}
:global(html[data-docs-print-color-scheme="dark"])
.root.root
:global(.codeBlock svg) {
filter: invert(1) hue-rotate(180deg);
}
.header,
.sidebar,
.toc,
.articleActions,
.breadcrumbs,
.pageNav,
.footer {
display: none !important;
}
.root {
--docs-bg: #fff;
--docs-fg: #1f1f1f;
--docs-content-fg: var(--docs-fg);
--docs-nav-fg: #495057;
--docs-muted: #5f6368;
--docs-hover: #f1f3f5;
--docs-faint: #868e96;
--docs-border: #dee2e6;
--docs-header-bg: #fff;
min-height: 0;
background-color: #fff;
color: var(--docs-fg);
}
.body {
display: block;
}
.article {
max-width: none;
padding: 0;
}
}