:root {
--mbr-icon-search: url("data:image/svg+xml,%20%20%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%221.2em%22%20height%3D%221.2em%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22currentColor%22%20d%3D%22M416%20208c0%2045.9-14.9%2088.3-40%20122.7l126.6%20126.7c12.5%2012.5%2012.5%2032.8%200%2045.3s-32.8%2012.5-45.3%200L330.7%20376c-34.4%2025.2-76.8%2040-122.7%2040C93.1%20416%200%20322.9%200%20208S93.1%200%20208%200s208%2093.1%20208%20208M208%20352a144%20144%200%201%200%200-288a144%20144%200%201%200%200%20288%22%2F%3E%3C%2Fsvg%3E");
/* Sidebar Navigation Variables (for mbr-browse-single) */
--mbr-sidebar-width: 280px;
--mbr-hide-nav-bp: 1024px;
--mbr-show-tags: block; /* Set to 'none' to hide tags in sidebar */
/* Pull Quote Styling Variables */
--mbr-pullquote-bg: var(--pico-muted-border-color);
--mbr-pullquote-border: var(--pico-primary);
--mbr-pullquote-color: var(--pico-color);
/* Marginalia Styling Variables */
--mbr-marginalia-bg: var(--pico-secondary-background);
/* --mbr-marginalia-border: var(--pico-muted-border-color); */
--mbr-marginalia-border: var(--pico-secondary-hover-background);
--mbr-marginalia-color: var(--pico-muted-color);
/* GitHub Alert: Note (Blue) */
--mbr-alert-note-bg: rgba(227, 242, 253, 0.4);
--mbr-alert-note-border: #1976d2;
--mbr-alert-note-icon-color: #1976d2;
/* GitHub Alert: Tip (Green) */
--mbr-alert-tip-bg: rgba(232, 245, 233, 0.4);
--mbr-alert-tip-border: #388e3c;
--mbr-alert-tip-icon-color: #388e3c;
/* GitHub Alert: Important (Purple) */
--mbr-alert-important-bg: rgba(243, 229, 245, 0.4);
--mbr-alert-important-border: #7b1fa2;
--mbr-alert-important-icon-color: #7b1fa2;
/* GitHub Alert: Warning (Orange) */
--mbr-alert-warning-bg: rgba(255, 243, 224, 0.4);
--mbr-alert-warning-border: #f57c00;
--mbr-alert-warning-icon-color: #f57c00;
/* GitHub Alert: Caution (Red) */
--mbr-alert-caution-bg: rgba(255, 235, 238, 0.4);
--mbr-alert-caution-border: #c62828;
--mbr-alert-caution-icon-color: #c62828;
/* Incomplete marker (TK/TODO/FIXME/XXX) — a wash only, no underline. */
--mbr-incomplete-bg: rgba(255, 235, 59, 0.18);
/* Task annotations (!! / !!! priority, #tag, @due, @done, [-] canceled) */
--mbr-task-pri-high-color: #f57c00;
--mbr-task-pri-urgent-color: #c62828;
--mbr-task-pri-size: 0.55em;
--mbr-task-tag-bg: rgba(115, 130, 140, 0.16);
--mbr-task-tag-color: var(--pico-muted-color, #5c6b73);
--mbr-task-chip-bg: rgba(115, 130, 140, 0.1);
--mbr-task-chip-color: var(--pico-muted-color, #5c6b73);
--mbr-task-chip-radius: 0.5em;
--mbr-task-canceled-color: var(--pico-muted-color, #5c6b73);
/* Arrival flash for a #mbr-task-N deep link. */
--mbr-task-flash-color: rgba(255, 213, 79, 0.55);
/* Review notes (r / R). One accent per note type, matching the six in
* components/src/review/types.ts. The panel restates its rules against these
* same properties inside its shadow root -- custom properties cross that
* boundary but the rules using them do not. */
--mbr-review-issue-color: #c62828;
--mbr-review-suggestion-color: #f57c00;
--mbr-review-note-color: #1976d2;
--mbr-review-praise-color: #388e3c;
--mbr-review-question-color: #7b1fa2;
--mbr-review-insight-color: #0288d1;
--mbr-review-stale-color: var(--pico-muted-color, #5c6b73);
--mbr-review-highlight-bg: rgba(255, 193, 7, 0.28);
--mbr-review-marker-size: 0.8em;
/* Paper margins. Applied as padding on the content box rather than as an
* `@page` margin -- see the "Print Styles" section for the Safari reason. */
--mbr-print-margin-block: 0.5in;
--mbr-print-margin-inline: 1in;
/* Definition Lists (FAQ-style disclosure) Variables.
* All colors resolve through Pico custom properties, so light/dark mode and
* every color theme follow automatically -- override these, not the rules. */
--mbr-dl-question-color: var(--pico-color);
--mbr-dl-question-hover-color: var(--pico-primary-hover);
--mbr-dl-question-weight: 600;
--mbr-dl-answer-color: var(--pico-muted-color);
--mbr-dl-separator-color: var(--pico-muted-border-color);
--mbr-dl-marker-color: var(--pico-primary);
--mbr-dl-marker-size: 0.62em;
--mbr-dl-marker-gap: 0.55em;
/* Vertical breathing room above/below each question and under each answer. */
--mbr-dl-item-spacing: var(--pico-spacing);
/* How far the answer is indented from the question. */
--mbr-dl-answer-indent: calc(var(--pico-spacing) * 1.5);
--mbr-dl-transition-duration: 0.25s;
--mbr-dl-transition-easing: ease;
/* Alert Icons (SVG Data URIs) */
--mbr-icon-note: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='16' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='8' x2='12.01' y2='8'%3E%3C/line%3E%3C/svg%3E");
--mbr-icon-tip: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2a7 7 0 0 0-7 7c0 2.38 1.19 4.47 3 5.74V17a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-2.26c1.81-1.27 3-3.36 3-5.74a7 7 0 0 0-7-7z'%3E%3C/path%3E%3Cpath d='M9 21h6'%3E%3C/path%3E%3C/svg%3E");
--mbr-icon-important: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
--mbr-icon-warning: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z'%3E%3C/path%3E%3Cline x1='12' y1='9' x2='12' y2='13'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
--mbr-icon-caution: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2'%3E%3C/polygon%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
/* tie reveal.js slides styles to our pico styles and theme */
--r-background-color: var(--pico-background-color);
--r-main-font: var(--pico-font-family);
--r-main-font-size: calc(var(--pico-font-size) * 1.25);
--r-main-color: var(--pico-color);
--r-block-margin: calc(var(--pico-spacing) * 1.2);
--r-heading-margin: var(--r-block-margin);
--r-heading-font: var(--pico-font-family);
--r-heading-color: var(--pico-primary);
--r-heading-line-height: 1.2;
--r-heading-letter-spacing: normal;
--r-heading-text-transform: uppercase;
--r-heading-text-shadow: none;
--r-heading-font-weight: 700;
--r-heading1-text-shadow: none;
--r-heading1-size: calc(var(--pico-font-size) * 3.1);
--r-heading2-size: calc(var(--pico-font-size) * 1.8);
--r-heading3-size: calc(var(--pico-font-size) * 1.6);
--r-heading4-size: calc(var(--pico-font-size) * 1.3);
--r-code-font: var(--pico-font-family-monospace);
--r-link-color: var(--pico-primary);
/* not sure on this one; could do lch(from var(--pico-primary) calc(l - 20) c h) to darken */
--r-link-color-dark: var(--pico-primary);
--r-link-color-hover: var(--pico-primary-hover);
--r-selection-background-color: var(--pico-text-selection-color);
--r-selection-color: var(--pico-color);
--r-overlay-element-bg-color: var(--pico-modal-overlay-background-color);
--r-overlay-element-fg-color: var(--pico-color);
}
main > section[data-theme="light"] {
background-color: rgba(255, 255, 255, 0.85);
padding: var(--pico-block-spacing-vertical) 0;
}
main > section[data-theme="dark"] {
background-color: rgba(0, 0, 0, 0.85);
padding: var(--pico-block-spacing-vertical) 0;
}
.mbr-search-button {
width: 1.2em;
height: 1.2em;
mask-size: cover;
mask: var(--mbr-icon-search) no-repeat 100% 100%;
background-color: var(--pico-color);
border: none;
outline: none;
}
body > header {
background-color: var(--pico-background-color);
border-bottom: 2px solid var(--pico-secondary-border);
position: sticky;
top: 0;
padding-block: 0;
z-index: 999;
}
/* Offset anchor jumps (including `:target` and permalink clicks) so the
* linked heading isn't hidden under the sticky header above (nav ul is
* 2.4em + 2px border). */
html {
scroll-padding-top: 3em;
}
body > header > nav {
padding-block: 0;
margin-block: 0;
}
body > header > nav > ul {
height: 2.4em;
}
body > header nav details[class="dropdown"] summary {
border-color: transparent;
background-color: transparent;
margin-right: -14px;
}
body > header nav details[class="dropdown"] summary::after {
background: none;
}
nav[aria-label="breadcrumb"] ul li {
padding-top: 0;
font-style: italic;
}
body > footer {
border-top: 1px solid var(--pico-secondary-border);
}
/* ==================== Sidebar Navigation Grid Layout ==================== */
/*
* CSS Grid layout for body when mbr-browse-single sidebar is active.
* The component adds 'mbr-has-sidebar' class to body when in desktop mode (>1024px).
* On mobile (<1024px), the sidebar is an overlay drawer - no grid layout needed.
*/
/* Desktop: sidebar visible inline via CSS grid.
*
* `screen and` is load-bearing, not decoration. Per CSS 2.1 a width query in
* paged media resolves against the page box (a Letter sheet is ~816px, so this
* block should never match when printing) -- but WebKit evaluates width and
* viewport units against the on-screen window instead
* (https://bugs.webkit.org/show_bug.cgi?id=250685, open since 2023;
* https://bugs.webkit.org/show_bug.cgi?id=126591 for `vw`). Unqualified, this
* block therefore applies while printing from any Safari window wider than
* 1024px, dropping a `min-height: 100vh` grid and two sticky children into the
* printed layout. Qualifying the media type sidesteps the bug entirely.
*
* Pico's own breakpoints are unqualified and have the same exposure; the print
* block's `main.container { max-width: none }` neutralises the consequence we
* can actually reach. */
@media screen and (min-width: 1024px) {
body.mbr-has-sidebar {
display: grid;
grid-template-columns: var(--mbr-sidebar-width) 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"sidebar header"
"sidebar main"
"sidebar footer";
min-height: 100vh;
}
body.mbr-has-sidebar > mbr-browse-single {
grid-area: sidebar;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
border-right: 1px solid var(--pico-muted-border-color);
z-index: 100;
}
body.mbr-has-sidebar > header {
grid-area: header;
position: sticky;
top: 0;
}
body.mbr-has-sidebar > main {
grid-area: main;
}
body.mbr-has-sidebar > footer {
grid-area: footer;
}
}
/* Mobile: no grid layout, sidebar is overlay drawer handled by component.
* `screen and` for the same reason as the desktop block above. */
@media screen and (max-width: 1023px) {
body.mbr-has-sidebar {
display: block;
}
}
figure > video {
width: 100%;
height: auto;
max-height: calc(100vh - 50px);
max-width: 100%;
align-items: center;
/* display: inline-flex; */
user-select: none;
}
figure > figcaption {
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
flex-wrap: wrap-reverse;
}
figure > figcaption > * {
flex-grow: 1;
}
figure > figcaption > mbr-video-extras {
flex-grow: 2;
}
figure {
margin-bottom: var(--pico-block-spacing-vertical);
}
/* ==================== Video Theater Mode ==================== */
figure.theater {
/* Break out of parent container to fill viewport width */
width: 100vw;
margin-left: calc(-50vw + 50%);
margin-right: calc(-50vw + 50%);
/* Dark background for cinematic feel */
background: #000;
/* Flex column to stack video + controls, cap to viewport */
display: flex;
flex-direction: column;
align-items: center;
max-height: 95vh;
/* Sit above sibling content but below sticky header (z-index: 999) */
position: relative;
z-index: 100;
padding: 0;
}
figure.theater > video {
flex: 1 1 auto;
min-height: 0; /* allow flex shrink below intrinsic size */
width: 100%;
max-height: 80vh;
object-fit: contain; /* maintain aspect ratio, letterbox if needed */
}
figure.theater > figcaption {
flex: 0 0 auto;
width: 100%;
max-width: 1200px;
padding: 0.5rem 1rem;
color: var(--pico-muted-color);
}
/* figure > video::cue { */
/* font-size: 0.875em; */
/* margin: 1em; */
/* padding: 0.5em; */
/* } */
/* media-provider > video::cue { */
/* display: none; */
/* } */
ol > li {
list-style: decimal;
}
li::marker {
color: var(--pico-muted-color);
}
/* ==================== Dark Mode Overrides ==================== */
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) {
/* Marginalia - Dark Mode */
--mbr-marginalia-bg: rgba(32, 38, 50, 0.6);
/* GitHub Alerts - Dark Mode */
--mbr-alert-note-bg: rgba(13, 71, 161, 0.2);
--mbr-alert-note-border: #42a5f5;
--mbr-alert-note-icon-color: #42a5f5;
--mbr-alert-tip-bg: rgba(27, 94, 32, 0.2);
--mbr-alert-tip-border: #66bb6a;
--mbr-alert-tip-icon-color: #66bb6a;
--mbr-alert-important-bg: rgba(74, 20, 140, 0.2);
--mbr-alert-important-border: #ab47bc;
--mbr-alert-important-icon-color: #ab47bc;
--mbr-alert-warning-bg: rgba(230, 81, 0, 0.2);
--mbr-alert-warning-border: #ffa726;
--mbr-alert-warning-icon-color: #ffa726;
--mbr-alert-caution-bg: rgba(183, 28, 28, 0.2);
--mbr-alert-caution-border: #ef5350;
--mbr-alert-caution-icon-color: #ef5350;
/* Task annotations - Dark Mode */
--mbr-task-pri-high-color: #ffa726;
--mbr-task-pri-urgent-color: #ef5350;
--mbr-task-tag-bg: rgba(148, 163, 184, 0.2);
--mbr-task-chip-bg: rgba(148, 163, 184, 0.14);
--mbr-task-flash-color: rgba(255, 213, 79, 0.28);
/* Review notes - Dark Mode */
--mbr-review-issue-color: #ef5350;
--mbr-review-suggestion-color: #ffa726;
--mbr-review-note-color: #64b5f6;
--mbr-review-praise-color: #66bb6a;
--mbr-review-question-color: #ba68c8;
--mbr-review-insight-color: #4fc3f7;
--mbr-review-highlight-bg: rgba(255, 193, 7, 0.22);
}
}
[data-theme="dark"] {
/* Pull Quote - Dark Mode */
--mbr-pullquote-bg: rgba(42, 49, 64, 0.4);
--mbr-pullquote-border: var(--pico-primary-hover);
/* Marginalia - Dark Mode */
--mbr-marginalia-bg: rgba(32, 38, 50, 0.6);
/* GitHub Alerts - Dark Mode */
--mbr-alert-note-bg: rgba(13, 71, 161, 0.2);
--mbr-alert-note-border: #42a5f5;
--mbr-alert-note-icon-color: #42a5f5;
--mbr-alert-tip-bg: rgba(27, 94, 32, 0.2);
--mbr-alert-tip-border: #66bb6a;
--mbr-alert-tip-icon-color: #66bb6a;
--mbr-alert-important-bg: rgba(74, 20, 140, 0.2);
--mbr-alert-important-border: #ab47bc;
--mbr-alert-important-icon-color: #ab47bc;
--mbr-alert-warning-bg: rgba(230, 81, 0, 0.2);
--mbr-alert-warning-border: #ffa726;
--mbr-alert-warning-icon-color: #ffa726;
--mbr-alert-caution-bg: rgba(183, 28, 28, 0.2);
--mbr-alert-caution-border: #ef5350;
--mbr-alert-caution-icon-color: #ef5350;
/* Task annotations - Dark Mode */
--mbr-task-pri-high-color: #ffa726;
--mbr-task-pri-urgent-color: #ef5350;
--mbr-task-tag-bg: rgba(148, 163, 184, 0.2);
--mbr-task-chip-bg: rgba(148, 163, 184, 0.14);
--mbr-task-flash-color: rgba(255, 213, 79, 0.28);
/* Review notes - Dark Mode */
--mbr-review-issue-color: #ef5350;
--mbr-review-suggestion-color: #ffa726;
--mbr-review-note-color: #64b5f6;
--mbr-review-praise-color: #66bb6a;
--mbr-review-question-color: #ba68c8;
--mbr-review-insight-color: #4fc3f7;
--mbr-review-highlight-bg: rgba(255, 193, 7, 0.22);
}
/* ==================== Standard Block Quotes (> - Level 1 Nesting) ==================== */
blockquote {
/* avoid awkward indents when we nest blockquotes by clearing some defaults */
padding: 0;
border: none;
}
blockquote > p:last-child {
/* this will remove the double padding from the blockquote element and the p for all of these */
margin-bottom: 0;
}
:not(blockquote) > blockquote:not(:has(> blockquote)) {
padding: var(--pico-spacing);
border-left: 0.25rem solid var(--pico-blockquote-border-color);
}
blockquote:not(:has(blockquote)) p {
font-style: italic;
}
blockquote > p {
font-size: var(--pico-font-size);
}
/* ==================== Pull Quotes (>> - Level 2 Nesting) ==================== */
/*
* Pull quotes are created with double blockquote nesting (>>).
* Styled like magazine pull quotes: larger text, bold border, italic styling.
*
* Example markdown:
* >> This is a pull quote with larger, emphasized text
*/
:not(blockquote) > blockquote > blockquote:not(:has(> blockquote)) {
--pico-font-size: 1.5rem;
--pico-font-weight: 500;
font-style: italic;
background-color: var(--mbr-pullquote-bg);
border-left: 0.5rem solid var(--mbr-pullquote-border);
color: var(--mbr-pullquote-color);
padding: calc(var(--pico-spacing) * 2);
}
/* ==================== GitHub-Style Alerts ==================== */
/*
* GitHub-style alert blockquotes with semantic colors and icons.
* Classes are automatically added by pulldown-cmark for markdown like:
*
* > [!NOTE]
* > This is a note alert
*/
/* Base alert styling */
blockquote.markdown-alert-note,
blockquote.markdown-alert-tip,
blockquote.markdown-alert-important,
blockquote.markdown-alert-warning,
blockquote.markdown-alert-caution {
position: relative;
padding: var(--pico-spacing);
padding-left: calc(var(--pico-spacing) * 2.5);
margin: var(--pico-typography-spacing-vertical) 0;
border-left: 0.5rem solid;
border-radius: var(--pico-border-radius);
}
/* Icon positioning */
blockquote.markdown-alert-note::before,
blockquote.markdown-alert-tip::before,
blockquote.markdown-alert-important::before,
blockquote.markdown-alert-warning::before,
blockquote.markdown-alert-caution::before {
content: "";
position: absolute;
left: calc(var(--pico-spacing) * 0.75);
top: calc(var(--pico-spacing) * 1.5);
width: 22px;
height: 22px;
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
}
/* Alert title styling */
blockquote.markdown-alert-note > p:first-of-type::before,
blockquote.markdown-alert-tip > p:first-of-type::before,
blockquote.markdown-alert-important > p:first-of-type::before,
blockquote.markdown-alert-warning > p:first-of-type::before,
blockquote.markdown-alert-caution > p:first-of-type::before {
--pico-font-weight: 700;
display: block;
margin-bottom: 0.25rem;
}
/* Note Alert (Blue) */
blockquote.markdown-alert-note {
background-color: var(--mbr-alert-note-bg);
border-color: var(--mbr-alert-note-border);
}
blockquote.markdown-alert-note::before {
mask-image: var(--mbr-icon-note);
background-color: var(--mbr-alert-note-icon-color);
}
blockquote.markdown-alert-note > p:first-of-type::before {
content: "Note";
color: var(--mbr-alert-note-icon-color);
}
/* Tip Alert (Green) */
blockquote.markdown-alert-tip {
background-color: var(--mbr-alert-tip-bg);
border-color: var(--mbr-alert-tip-border);
}
blockquote.markdown-alert-tip::before {
background-color: var(--mbr-alert-tip-icon-color);
mask-image: var(--mbr-icon-tip);
}
blockquote.markdown-alert-tip > p:first-of-type::before {
content: "Tip";
color: var(--mbr-alert-tip-icon-color);
}
/* Important Alert (Purple) */
blockquote.markdown-alert-important {
background-color: var(--mbr-alert-important-bg);
border-color: var(--mbr-alert-important-border);
}
blockquote.markdown-alert-important::before {
mask: var(--mbr-icon-important);
background-color: var(--mbr-alert-important-icon-color);
}
blockquote.markdown-alert-important > p:first-of-type::before {
content: "Important";
color: var(--mbr-alert-important-icon-color);
}
/* Warning Alert (Orange) */
blockquote.markdown-alert-warning {
background-color: var(--mbr-alert-warning-bg);
border-color: var(--mbr-alert-warning-border);
}
blockquote.markdown-alert-warning::before {
background-color: var(--mbr-alert-warning-icon-color);
mask-image: var(--mbr-icon-warning);
}
blockquote.markdown-alert-warning > p:first-of-type::before {
content: "Warning";
color: var(--mbr-alert-warning-icon-color);
}
/* Caution Alert (Red) */
blockquote.markdown-alert-caution {
background-color: var(--mbr-alert-caution-bg);
border-color: var(--mbr-alert-caution-border);
}
blockquote.markdown-alert-caution::before {
mask-image: var(--mbr-icon-caution);
background-color: var(--mbr-alert-caution-icon-color);
}
blockquote.markdown-alert-caution > p:first-of-type::before {
content: "Caution";
color: var(--mbr-alert-caution-icon-color);
}
/* Incomplete marker — TK/TODO/FIXME/XXX, or whatever incomplete_markers
* configures. A block whose first text starts with a marker is wrapped whole;
* every other occurrence has just the marker word wrapped. The first wrapper on
* a source line also carries an id="mbr-marker-{line}" deep-link anchor.
* Highlights drafts in server/GUI mode; off by default in static builds. */
.mbr-incomplete {
background: var(--mbr-incomplete-bg);
padding: 0 0.15rem;
border-radius: 2px;
}
/* A block-initial wrap containing a later inline wrap would otherwise stack two
* translucent washes on the same word. The outer span already says "this block
* is incomplete"; the inner one has nothing left to add. */
.mbr-incomplete .mbr-incomplete {
background: none;
padding: 0;
}
/* ==================== Task Lists ==================== */
/*
* Markup emitted by `markdown::collect_events_and_headings` for `- [ ]`,
* `- [x]`, `- [-]` (canceled) and `- [>]` (moved), with the `@due` / `@done` /
* `#tag` / `!!` annotations lifted out of the text and rendered as chips:
*
* <li><input type="checkbox" class="mbr-task-check" id="mbr-task-12"
* data-mbr-task-line="12" data-mbr-task-status="open" disabled>
* <span class="mbr-task-text">write the report</span>
* <span class="mbr-task-pri mbr-task-pri-urgent" …></span>
* <span class="mbr-task-tag">#work</span>
* <time class="mbr-task-due" datetime="2026-08-05">Aug 5</time></li>
*
* The `<li>` carries no class of its own -- pulldown-cmark gives the HTML
* writer no attribute channel for list items -- so the bullet is suppressed by
* matching on the checkbox instead. The second selector covers a loose list,
* where the item's content is wrapped in a paragraph.
*/
li:has(> .mbr-task-check),
li:has(> p > .mbr-task-check) {
list-style: none;
}
.mbr-task-check {
/* Vertically centre against the text rather than sitting on the baseline. */
vertical-align: middle;
margin-inline-end: 0.4em;
}
/* Only when editing is on does `mbr-task-doc.ts` drop `disabled` and add this
* class -- a box that cannot be written to must not invite a click. */
.mbr-task-check.mbr-task-editable {
cursor: pointer;
}
/*
* Arrival flash for a `#mbr-task-42` deep link, applied to the task's <li> by
* `mbr-task-doc.ts` on load and on every hashchange.
*
* A fading background rather than a border or an outline, so nothing in the
* line moves: a layout shift landing at the same moment as the fragment jump
* would scroll the task the user just followed a link to back off screen.
*/
@keyframes mbr-task-flash {
from {
background-color: var(--mbr-task-flash-color, rgba(255, 213, 79, 0.55));
}
to {
background-color: transparent;
}
}
li.mbr-task-flash,
.mbr-task-flash {
border-radius: 4px;
animation: mbr-task-flash 1.6s ease-out 1;
}
/* Respect a reduced-motion preference by holding the highlight steady and
* letting the cleanup timeout remove it, rather than animating it away. */
@media (prefers-reduced-motion: reduce) {
li.mbr-task-flash,
.mbr-task-flash {
animation: none;
background-color: var(--mbr-task-flash-color, rgba(255, 213, 79, 0.55));
}
}
/* Canceled (`[-]`) and moved (`[>]`) tasks. Replaces the bare <s> the old
* renderer emitted, so the strikethrough now stops at the task's own text and
* leaves its subtasks and chips legible. */
.mbr-task-canceled {
text-decoration: line-through;
color: var(--mbr-task-canceled-color);
}
/* Priority dot. Empty by design: the element is a swatch, and its meaning
* reaches assistive tech through `role="img"` + `aria-label`. */
.mbr-task-pri {
display: inline-block;
width: var(--mbr-task-pri-size);
height: var(--mbr-task-pri-size);
border-radius: 50%;
vertical-align: baseline;
}
.mbr-task-pri-high {
background: var(--mbr-task-pri-high-color);
}
.mbr-task-pri-urgent {
background: var(--mbr-task-pri-urgent-color);
}
.mbr-task-tag {
background: var(--mbr-task-tag-bg);
color: var(--mbr-task-tag-color);
border-radius: var(--mbr-task-chip-radius);
padding: 0.05em 0.5em;
font-size: 0.85em;
white-space: nowrap;
}
/*
* Date chips. Styled neutrally on purpose: whether a due date is overdue
* depends on today's date, and this HTML is cached by the server and frozen
* outright by a static build, so an "overdue" class baked in at render time
* would be wrong by the next morning. The frontend adds one at runtime; the
* machine-readable value it needs is already in each chip's `datetime`.
*/
.mbr-task-due,
.mbr-task-completed,
.mbr-task-moved {
background: var(--mbr-task-chip-bg);
color: var(--mbr-task-chip-color);
border-radius: var(--mbr-task-chip-radius);
padding: 0.05em 0.5em;
font-size: 0.85em;
white-space: nowrap;
}
/*
* Chip icons. The gap is a margin rather than a space inside `content`,
* because CSS consumes one whitespace character as the terminator of a hex
* escape -- `content: "\2713 "` renders the tick with no space after it.
*/
.mbr-task-due::before,
.mbr-task-completed::before,
.mbr-task-moved::before {
margin-inline-end: 0.3em;
}
.mbr-task-due::before {
content: "\1F5D3\FE0E"; /* 🗓 calendar, text presentation */
}
.mbr-task-completed::before {
content: "\2713"; /* ✓ */
}
.mbr-task-moved::before {
content: "\2192"; /* → */
}
/* ==================== Marginalia (>>> - Level 3+ Nesting) ==================== */
/*
* Marginalia are created with triple blockquote nesting (>>>).
* Uses a hover/click indicator to reveal sidenote content when there aren't margins.
*
* Example markdown:
* >>> This is a margin note shown in gutter or via indicator
*/
/* Marginalia container - inline indicator approach */
blockquote blockquote:has(blockquote) {
position: relative;
}
blockquote blockquote blockquote {
display: block;
position: absolute;
left: 100%;
top: -3rem;
font-size: 0.75em;
}
/* Indicator marker */
blockquote blockquote blockquote::before {
content: "†";
display: inline-block;
color: var(--pico-primary);
font-weight: 700;
font-size: 1.5em;
cursor: pointer;
padding: 0 4px;
border-radius: var(--pico-border-radius);
}
blockquote blockquote blockquote:hover::before,
blockquote blockquote blockquote:focus-within::before {
background-color: var(--mbr-marginalia-bg);
}
/* Hide marginalia content by default if screen is less than x pixels; media queries below override */
blockquote blockquote blockquote p {
position: absolute;
display: none;
z-index: 1000;
/* Popover styling */
background-color: var(--pico-background-color);
border: 1px solid var(--mbr-marginalia-border);
border-left: 3px solid var(--pico-primary);
border-radius: var(--pico-border-radius);
box-shadow: var(--pico-box-shadow);
padding: calc(var(--pico-spacing) * 0.75);
margin: 0;
/* Sizing and positioning */
min-width: 280px;
max-width: 90vw;
transform: translateX(-100%) translateY(-50%);
/* Text styling */
--pico-font-size: 0.875em;
--pico-line-height: 1.15;
}
/* Show marginalia on hover */
blockquote blockquote blockquote:hover p,
blockquote blockquote blockquote:focus-within p {
display: block;
}
/* Popover arrow - points right toward the † indicator */
blockquote blockquote blockquote p::before {
content: "";
position: absolute;
left: 100%;
top: 12px;
border: 8px solid transparent;
border-left-color: var(--mbr-marginalia-border);
}
blockquote blockquote blockquote p::after {
content: "";
position: absolute;
left: 100%;
top: 13px;
border: 7px solid transparent;
border-left-color: var(--pico-background-color);
margin-left: -1px;
}
/* Show marginalia fixed in margin when screen is wide enough.
*
* `screen and` for the same WebKit reason as the sidebar block above, and here
* it changes printed output: the print block resets these paragraphs' position,
* transform and max-width but NOT their `width`, so on a Safari window ≥2000px
* an unqualified query left `width: calc(100vw - 40px)` -- a *window*-relative
* width in WebKit -- applied to every margin note on paper. */
@media screen and (min-width: 2000px) {
blockquote blockquote blockquote::before {
content: "";
}
blockquote blockquote blockquote p {
left: calc(100% + 5px);
width: calc(100vw - 40px);
transform: translateY(-50%);
max-width: 250px;
display: block;
border: none;
box-shadow: none;
background: none;
border-radius: 0;
}
blockquote blockquote blockquote p::before,
blockquote blockquote blockquote p::after {
left: auto;
right: 20px;
border: none;
transform: none;
}
}
/* Multiple paragraphs in marginalia */
blockquote blockquote blockquote p + p {
position: static;
visibility: visible;
opacity: 1;
display: none;
margin-top: calc(var(--pico-typography-spacing-vertical) * 0.5);
padding: 0;
border: none;
box-shadow: none;
background: none;
width: auto;
max-width: none;
}
blockquote blockquote blockquote:hover p + p,
blockquote blockquote blockquote:focus-within p + p {
display: block;
}
/* Touch device support - keep visible longer after tap */
@media (hover: none) {
blockquote blockquote blockquote:focus p,
blockquote blockquote blockquote:active p {
visibility: visible;
display: block;
opacity: 1;
}
}
pre.mermaid {
padding: var(--pico-typography-spacing-vertical);
}
/* ==================== Header Title ==================== */
/*
* The page title sits between two groups of buttons in a Pico `nav`, which is
* `display: flex; justify-content: space-between`. A flex item will not shrink
* below its content by default (`min-width: auto`), so a long title -- or a
* narrow window, or a large font -- pushed the buttons on either side out of
* the header instead of giving way.
*
* `min-width: 0` on the title's own group and item is what allows the shrink;
* the clamp then falls to the `<strong>`. Deliberately scoped to this one group
* by class rather than applied to `nav > ul`, because the button groups must
* keep sizing to their contents.
*/
body > header nav > ul.mbr-title-group,
body > header nav > ul.mbr-title-group > li.mbr-nav-title {
min-width: 0;
}
/*
* One line, clipped with an ellipsis. `display: block` because `text-overflow`
* does nothing on an inline box, and `<strong>` is inline by default.
*
* The clipping lives here and *not* on the `<li>`: the li carries Pico's
* `data-tooltip`, whose bubble is an absolutely-positioned pseudo-element, and
* an `overflow: hidden` ancestor would cut it off.
*/
body > header nav > ul.mbr-title-group > li.mbr-nav-title > strong {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/*
* The tooltip is Pico's, added by `components/src/nav-title.ts` only while the
* text is actually clipped -- so the focus ring below appears only on a title
* that has something hidden to reveal.
* But exclude the mbr-nav-title from tooltip dotted underline
*/
body > header nav > ul.mbr-title-group > li.mbr-nav-title[data-tooltip] {
cursor: help;
border-bottom: none;
}
body
> header
nav
> ul.mbr-title-group
> li.mbr-nav-title[data-tooltip]::before {
/*
* Long titles are the whole reason this exists, so let the bubble wrap rather
* than run off the edge of a narrow window.
*
* `width: max-content` is doing the real work. Pico's bubble is absolutely
* positioned, so without an explicit width it shrink-to-fits *within its
* containing block* -- which is this `<li>`, the one element on the page we
* have just made as narrow as possible. The result was a one-word-per-line
* column. `max-content` sizes it to the text instead, and `max-width` is what
* then brings it back under the viewport.
*/
width: max-content;
max-width: min(90vw, 40rem);
white-space: normal;
text-align: center;
}
/* ==================== Link Enhancement Tooltips (GUI Mode) ==================== */
/*
* Subtle tooltip styling for links enhanced by mbr-link-enhancement component.
* Only active in GUI mode where browser URL and status bar is not visible.
* Users can override these styles in their own theme.css. And can disable by removing
* the component that adds them or by using display: none; on this selector.
*/
a.mbr-link-enhanced[data-tooltip]::before {
font-size: 0.65em;
background-color: rgb(from var(--pico-tooltip-background-color) r g b / 0.75);
}
a.mbr-link-enhanced[data-tooltip]::after {
color: rgb(from var(--pico-tooltip-background-color) r g b / 0.75);
}
/* ==================== Collapsible Headings + Permalinks ==================== */
/* Hidden sibling content when a heading is collapsed */
.sectionhidden {
display: none;
}
/* Collapsible heading affordance */
.mbr-collapsible {
cursor: pointer;
user-select: none;
}
.mbr-collapsible:focus-visible {
outline: 2px solid var(--pico-primary-focus, var(--pico-primary));
outline-offset: 2px;
}
/* "+" prefix when collapsed */
.mbr-section-collapsed::before {
content: "+ ";
color: var(--pico-muted-color);
font-weight: normal;
}
/* Permalink anchor — hidden by default, revealed on heading hover/focus */
/*
* The "#" is generated content, not a text node, so that selecting a heading
* does not copy it and print/find-in-page never see it. mbr-heading-enhancer.ts
* deliberately leaves the element empty; do not move this back into the markup.
*/
.mbr-heading-anchor::after {
content: "#";
}
.mbr-heading-anchor {
margin-left: 0.4em;
color: var(--pico-muted-color);
text-decoration: none;
font-weight: normal;
opacity: 0;
font-size: 0.85em;
transition: opacity 0.15s ease-in-out;
}
:is(h1, h2, h3, h4, h5, h6):hover .mbr-heading-anchor,
:is(h1, h2, h3, h4, h5, h6):focus-within .mbr-heading-anchor {
opacity: 0.5;
}
.mbr-heading-anchor:hover,
.mbr-heading-anchor:focus {
opacity: 1;
color: var(--pico-primary);
text-decoration: underline;
}
/* ==================== Screen Reader Only (Accessibility) ==================== */
/*
* Visually hidden but accessible to screen readers and search indexers.
* Used for high-weight search content (titles, filenames) that should
* be indexed by Pagefind with maximum weight but not visible on page.
*/
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* ==================== Find in Page (GUI mode) ==================== */
/*
* <mbr-find-bar> registers its match ranges in CSS.highlights, which is a
* DOCUMENT-scoped registry, and the ranges point into the light DOM under
* main#wrapper. A ::highlight() rule inside the element's shadow root would
* therefore match nothing at all -- these have to live here, in a sheet linked
* from _head.html on every page and overridable via user.css.
*
* Only color, background-color, text-decoration*, text-shadow and
* -webkit-text-stroke are honoured inside ::highlight(); borders and outlines
* are ignored. An engine without the Custom Highlight API drops just these two
* rules, and the bar falls back to a plain Selection on the active match.
*/
::highlight(mbr-find) {
background-color: rgba(255, 214, 0, 0.4);
color: inherit;
}
::highlight(mbr-find-active) {
background-color: #ff9632;
color: #000;
}
/*
* Review-note wash. Same document-scoped-registry reasoning as the find rules
* above: `<mbr-review>` registers ranges that point into the light DOM under
* main#wrapper, so these cannot live in the element's own shadow styles.
*
* Only color, background-color, text-decoration*, text-shadow and
* -webkit-text-stroke are honoured inside ::highlight(), which is why the
* quoted span gets a wash and an underline rather than a border. An engine
* without the Custom Highlight API drops this rule and keeps every marker --
* the marker, not the wash, is what a reader clicks.
*/
::highlight(mbr-review) {
background-color: var(--mbr-review-highlight-bg, rgba(255, 193, 7, 0.28));
text-decoration: underline dotted;
text-underline-offset: 0.15em;
}
/* ==================== Review Note Markers (r / R) ==================== */
/*
* One marker per anchored note, injected into the block it belongs to by
* `components/src/review/markers.ts`.
*
* The element is deliberately EMPTY -- its glyph is generated content. A real
* text node inside the block would join the block's text run, so selecting a
* paragraph would copy its markers, every `textContent` reader would see them,
* and the review feature would end up quoting its own markers. Generated
* content is excluded from the copy buffer in every engine. Same trick as
* `.mbr-heading-anchor::after`.
*/
/*
* The marker sits in the margin beside the block's FIRST line -- the line the
* note actually refers to -- rather than in the text flow.
*
* Appended as the block's last child, an inline marker lands after the last
* word of a wrapped paragraph, which reads as a mark floating between two
* lines and points at the wrong line entirely. Taking it out of flow and
* pinning it to `top: 0` puts it level with the line whose number the note
* stores. Same placement idea as the `>>>` marginalia dagger above.
*
* `left: 100%` puts it in the container's own horizontal padding, which exists
* at every viewport width, so this needs no breakpoint. `:has()` scopes the
* positioning context to blocks that actually carry a marker -- making every
* `<p>` on the page a containing block would change stacking for anything
* absolutely positioned inside one.
*/
[data-mbr-line]:has(> .mbr-review-marker) {
position: relative;
}
/*
* The marker is a flex box holding one inline `<svg>`.
*
* # Why so many resets
*
* The element is `role="button"`, and Pico styles `[role=button]` as a full
* button: `padding: var(--pico-form-element-spacing-vertical) ...-horizontal`
* (~0.75rem 1rem), a border, and `background-color: var(--pico-primary-background)`.
* Combined with Pico's global `box-sizing: border-box` on `*`, a fixed `width`
* on this box left a content area of ZERO -- and the `<svg>` inside, being a
* shrinkable flex item, collapsed to nothing. The visible result was a solid
* blue rectangle: Pico's primary button background, with no icon in it.
*
* So: no fixed width (the box shrink-wraps icon + padding, which makes
* box-sizing irrelevant), `flex: none` on the icon so it can never be shrunk
* away again, and an explicit reset of every button property Pico sets. None
* of the declarations below is decorative.
*/
.mbr-review-marker {
position: absolute;
left: 100%;
top: 0;
margin-left: 0.25rem;
/* Resets of Pico's [role=button] styling. */
width: auto;
height: auto;
padding: 6px;
border: none;
box-shadow: none;
font-size: var(--mbr-review-marker-size, 0.8em);
font-weight: inherit;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
border-radius: var(--pico-border-radius, 4px);
/* Colour goes on the BACKGROUND, with a white icon on top. Tinting the icon
* itself and leaving the background transparent put a mid-saturation stroke
* on the page background, which failed contrast at this size. */
background-color: var(--mbr-review-note-color, #1976d2);
color: #fff;
transition: filter 0.15s ease;
}
.mbr-review-marker > svg {
display: block;
/* Never shrink: as a flex item the icon is otherwise the first thing to be
* squeezed out when anything constrains the box. */
flex: none;
width: 1.15em;
height: 1.15em;
}
.mbr-review-marker:hover,
.mbr-review-marker:focus-visible {
filter: brightness(1.15);
}
.mbr-review-marker[data-mbr-review-type="issue"] {
background-color: var(--mbr-review-issue-color, #c62828);
}
.mbr-review-marker[data-mbr-review-type="suggestion"] {
background-color: var(--mbr-review-suggestion-color, #f57c00);
}
.mbr-review-marker[data-mbr-review-type="praise"] {
background-color: var(--mbr-review-praise-color, #388e3c);
}
.mbr-review-marker[data-mbr-review-type="question"] {
background-color: var(--mbr-review-question-color, #7b1fa2);
}
.mbr-review-marker[data-mbr-review-type="insight"] {
background-color: var(--mbr-review-insight-color, #0288d1);
}
/* A note whose quoted text can no longer be found. It keeps its last known
* line and is never deleted, so the marker says so instead. */
.mbr-review-marker[data-mbr-review-stale="true"] {
background-color: var(--mbr-review-stale-color, #5c6b73);
opacity: 0.75;
}
/*
* More than one note on the same anchor: one marker, a count badge.
* Absolutely positioned so it cannot push the icon off centre -- as a flex
* sibling it would share the box and shift the icon left.
*/
.mbr-review-marker[data-mbr-review-count]:not(
[data-mbr-review-count="1"]
)::after {
content: attr(data-mbr-review-count);
position: absolute;
top: -0.35em;
right: -0.35em;
min-width: 1.15em;
padding: 0 0.15em;
font-size: 0.7em;
line-height: 1.15em;
text-align: center;
font-weight: 700;
color: var(--pico-color, #333);
background: var(--pico-background-color, #fff);
border: 1px solid var(--pico-muted-border-color, #ccc);
border-radius: 1em;
}
/*
* The note card shown when a marker is hovered, focused or clicked.
*
* Lives here and not in `<mbr-review>`'s `static styles` for the same reason
* the footnote card's rules do: the element is appended to `document.body`, so
* a rule inside the component's shadow root would not reach it. Without these
* rules the card is `position: static` and lands unstyled at the very end of
* the document -- present, but invisible where the reader is looking.
*/
.mbr-review-popover {
position: fixed;
z-index: 1000;
max-width: min(380px, 90vw);
max-height: 50vh;
overflow-y: auto;
padding: 0.6rem 0.75rem;
font-size: 0.875em;
line-height: 1.5;
color: var(--pico-color);
background: var(--pico-background-color);
border: 1px solid var(--pico-muted-border-color);
border-radius: var(--pico-border-radius);
box-shadow: var(--pico-box-shadow, 0 4px 16px rgba(0, 0, 0, 0.18));
}
.mbr-review-popover-card + .mbr-review-popover-card {
margin-top: 0.6rem;
padding-top: 0.6rem;
border-top: 1px solid var(--pico-muted-border-color);
}
.mbr-review-popover-head {
font-size: 0.75em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--pico-muted-color, #5c6b73);
margin-bottom: 0.2rem;
}
.mbr-review-popover-body {
white-space: pre-wrap;
}
.mbr-review-popover pre {
margin: 0.4rem 0 0;
padding: 0.4rem 0.5rem;
font-size: 0.85em;
white-space: pre-wrap;
background: var(--pico-card-background-color, #f8f9fa);
border-radius: var(--pico-border-radius);
}
.mbr-review-popover-actions {
display: flex;
gap: 0.4rem;
margin-top: 0.5rem;
}
/*
* These are real <button>s in the light DOM, so Pico styles them as full
* buttons -- the same trap that collapsed the marker. Every property below
* that looks redundant is overriding something Pico sets. (The panel and the
* note form are inside shadow roots, where Pico cannot reach them.)
*/
.mbr-review-popover-actions button {
width: auto;
margin: 0;
padding: 0.15rem 0.5rem;
font-size: 0.8em;
font-weight: inherit;
line-height: 1.4;
background: transparent;
color: var(--pico-primary, #0172ad);
border: 1px solid var(--pico-muted-border-color, #ccc);
border-radius: var(--pico-border-radius, 4px);
box-shadow: none;
cursor: pointer;
}
.mbr-review-popover-actions button:hover {
background: var(--pico-secondary-background, rgba(0, 0, 0, 0.05));
}
/* ==================== Footnotes (GitHub-Flavored Markdown) ==================== */
/*
* Footnotes are created with [^1] syntax in markdown.
* The footnote definitions appear at the bottom of the document.
*
* Example markdown:
* Here is some text with a footnote[^1].
*
* [^1]: This is the footnote content.
*/
/* Footnote reference links (the superscript numbers in the text) */
sup.footnote-reference {
font-size: 0.75em;
vertical-align: super;
line-height: 0;
text-decoration: none;
font-weight: 600;
top: -0.2em;
}
/* Footnote definitions container - all footnotes at bottom */
.footnote-definition {
font-size: 0.875em;
line-height: 1.5;
margin: 0 0 0.75rem 0;
padding-left: 2.5rem;
color: var(--pico-muted-color);
}
/* Add a subtle divider before the first footnote */
.footnote-definition:first-of-type {
margin-top: var(--pico-spacing);
padding-top: var(--pico-spacing);
border-top: 1px solid var(--pico-muted-border-color);
}
/* Footnote label (the number) */
.footnote-definition-label {
font-weight: 700;
color: var(--pico-primary);
min-width: 2rem;
text-align: right;
padding-right: 0.5rem;
display: inline;
margin: 0;
}
.footnote-definition sup {
vertical-align: baseline;
font-size: 1em;
margin-block-start: 1em;
display: inline;
position: static;
}
.footnote-definition-label::after {
content: ".";
}
/* Footnote content paragraphs */
.footnote-definition p {
margin: 0;
display: inline;
}
/* Back-reference link (↩) */
.footnote-definition a[href^="#fnref"] {
margin-left: 0.25em;
text-decoration: none;
opacity: 0.6;
font-size: 0.9em;
}
.footnote-definition a[href^="#fnref"]:hover {
opacity: 1;
}
/*
* Hover preview card (desktop only), injected by <mbr-footnote-preview>.
* Shows the footnote's content in a floating card when the reader hovers or
* focuses a footnote reference. Positioned via inline left/top by the component.
*/
.mbr-footnote-popover {
position: fixed;
z-index: 1000;
max-width: min(360px, 90vw);
max-height: 50vh;
overflow-y: auto;
padding: 0.5rem 0.75rem;
font-size: 0.875em;
line-height: 1.5;
color: var(--pico-color);
background: var(--pico-background-color);
border: 1px solid var(--pico-muted-border-color);
border-radius: var(--pico-border-radius);
box-shadow: var(--pico-box-shadow, 0 4px 16px rgba(0, 0, 0, 0.18));
}
/* Collapse the definition's inline-flow margins inside the card. */
.mbr-footnote-popover p {
margin: 0;
}
.mbr-footnote-popover p + p {
margin-top: 0.5rem;
}
/* ==================== Definition Lists (FAQ-style disclosure) ==================== */
/*
* Markdown definition lists render as an FAQ: each `<dt>` is a question, and
* its `<dd>` answer is collapsed until the question is activated.
*
* Example markdown:
* What is mbr?
* : A markdown browser, previewer and static site generator.
*
* This is deliberately JavaScript-free. The only hook is `tabindex="0"` on
* every `<dt>` (emitted by `src/html.rs`), which makes the question focusable
* so `:focus` can stand in for "open".
*
* ACCEPTED LIMITATION -- clicking an already-open question does NOT close it,
* because the click just re-focuses a `<dt>` that already has focus and CSS
* has no way to toggle. Clicking a different question, or anywhere off the
* list, does close it. The same mechanic means clicking *inside* an open
* answer (to select its text, say) moves focus to the body and collapses it;
* select from the question end, or print, to work around that. A real toggle
* needs `<details>` markup or JavaScript, and neither is on the table here.
*
* Scoped to `main` so it only ever touches rendered markdown -- `<dl>`s built
* by templates or components stay untouched. (`<mbr-keys>` also renders a
* `<dl>`, but it lives in a shadow root this sheet cannot reach anyway.)
*/
main dl {
margin-block: calc(var(--pico-spacing) * 1.5);
/* Each `dt` draws the separator above itself; this closes off the last
* answer so the list reads as a bounded block. */
border-bottom: 1px solid var(--mbr-dl-separator-color);
}
main dl > dt {
cursor: pointer;
-webkit-user-select: none;
user-select: none;
color: var(--mbr-dl-question-color);
font-weight: var(--mbr-dl-question-weight);
border-top: 1px solid var(--mbr-dl-separator-color);
padding-block: var(--mbr-dl-item-spacing);
/* Hanging indent: the marker sits in the gutter and the second line of a
* long question lines up under the first line's text, not under the marker. */
padding-inline-start: calc(
var(--mbr-dl-marker-size) + var(--mbr-dl-marker-gap)
);
text-indent: calc(
-1 * (var(--mbr-dl-marker-size) + var(--mbr-dl-marker-gap))
);
border-radius: var(--pico-border-radius);
transition: color var(--mbr-dl-transition-duration)
var(--mbr-dl-transition-easing);
}
/* The negative `text-indent` above is inherited, and it applies to the first
* line of every *block container* -- which includes inline-blocks. Pico draws
* `code` / `kbd` as `inline-block`, so a question like "set a page's `<body>`
* classes?" pulled its code span's text a marker-width to the left, over the
* word before it. Resetting it on every descendant is safe: an inline element
* ignores `text-indent`, so only the inline-blocks notice. */
main dl > dt * {
text-indent: 0;
}
main dl > dt:hover {
color: var(--mbr-dl-question-hover-color);
}
/* Suppress the ring on mouse activation, then put a real one back for keyboard
* users below -- same idiom as `.mbr-collapsible` above. */
main dl > dt:focus {
outline: none;
}
main dl > dt:focus-visible {
outline: 2px solid var(--pico-primary-focus, var(--pico-primary));
outline-offset: 2px;
}
/* Disclosure marker. A square box clipped to a triangle, rather than the
* classic zero-size border trick, so `rotate()` pivots about the box's centre
* instead of drifting as it turns. */
main dl > dt::before {
content: "";
display: inline-block;
inline-size: var(--mbr-dl-marker-size);
block-size: var(--mbr-dl-marker-size);
margin-inline-end: var(--mbr-dl-marker-gap);
vertical-align: -0.04em;
background-color: var(--mbr-dl-marker-color);
clip-path: polygon(25% 0%, 25% 100%, 90% 50%);
transition: transform var(--mbr-dl-transition-duration)
var(--mbr-dl-transition-easing);
}
/* Rotate the marker while this question's answers are open. The second
* selector reads "the nearest preceding question of the answer holding focus":
* it has a later answer with focus inside, and no *other* question stands
* between the two. Without the `:not()` every question above the open one
* would point down as well. */
main dl > dt:focus::before,
main dl > dt:has(~ dd:focus-within):not(:has(~ dt ~ dd:focus-within))::before,
main dl > dt:has(+ dd.mbr-find-reveal)::before {
transform: rotate(90deg);
}
/* Collapsed answer.
*
* `visibility: hidden` is doing more work than it looks. `height: 0;
* overflow: hidden` alone hides the answer visually but leaves any links
* inside it in the tab order, so a keyboard user tabs into content they cannot
* see. `visibility: hidden` removes descendants from both the tab order and
* the accessibility tree, which fixes that.
*
* It has to be `visibility` specifically: `display: none` would kill the
* height transition, and `content-visibility: hidden` / `interactivity: inert`
* would make the answer permanently unfocusable, breaking the focus handoff in
* the rule below. */
main dl > dd {
visibility: hidden;
height: 0;
overflow: hidden;
/* Zeroed so a collapsed answer contributes no height at all. Margins stay 0
* in both states; only padding is restored on open, which keeps the
* separator lines evenly placed. `overflow: hidden` also establishes a block
* formatting context, so a loose definition's `<p>` margins cannot collapse
* out through this box and reopen a gap. */
margin-block: 0;
padding-block: 0;
margin-inline-start: var(--mbr-dl-answer-indent);
color: var(--mbr-dl-answer-color);
}
/* Pico gives `p`, `ol`, `ul`, `blockquote`, `address`, `table`, `th` and `td`
* an explicit `color: var(--pico-color)`, so an answer holding a list, a loose
* paragraph or a table drew those in the question's colour, not the answer's.
* Inherit instead -- at any depth, since a nested list's `ul` is colored too.
* Links, code, headings and `li::marker` set their own colour and are untouched;
* a pull quote's rule out-ranks this one, so it keeps its own colour as well. */
main dl > dd :where(p, ol, ul, blockquote, address, table, th, td) {
color: inherit;
}
/* Open answer.
*
* The combinator is `~`, not `+`, because one question may legitimately own
* several answers -- markdown's `Term` / `: one` / `: two` emits a `<dt>`
* followed by two sibling `<dd>`s, and so does any tight list whose entries
* are not blank-line separated. With `+` only the first answer would ever
* open, and since a closed answer is `visibility: hidden` the rest would be
* both invisible and untabbable, i.e. unreachable content.
*
* The `dd:focus-within` selectors are what make tabbing from a question into a
* link inside its answer work. At the instant focus leaves the `dt`,
* `dt:focus ~ dd` still matches, so the answers are visible and their links
* are focusable and can be reached; the moment focus lands inside,
* `dd:focus-within` takes over. Style recalculation happens after the focus
* move, so there is no frame in which neither selector matches and the answer
* flickers shut. `dd:focus-within ~ dd` keeps the *other* answers of a
* multi-answer question open too, so tabbing reaches every one of them.
*
* `dd:focus-within` can never open a *closed* answer on its own: `visibility:
* hidden` makes its descendants unfocusable, so focus can only ever get inside
* by way of the question. */
main dl > dt:focus ~ dd,
main dl > dd:focus-within,
main dl > dd:focus-within ~ dd {
visibility: visible;
height: auto;
padding-block: 0 var(--mbr-dl-item-spacing);
}
/* The GUI find bar's stand-in for `dt:focus` (`<mbr-find-bar>`, class
* `FIND_REVEAL_CLASS` in `find-in-page.ts`). When the active match is inside a
* collapsed answer the bar opens it -- but it cannot focus the question, since
* focus must stay in the find input, so it puts this class on every `<dd>` of
* the entry instead (the same set `dt:focus ~ dd` opens) and the marker rule
* above turns for it too. Being on the answers themselves, it needs no "until
* the next question" counterpart. The bar removes it when the active match
* moves on, and after the bar closes, on the reader's next click elsewhere. */
main dl > dd.mbr-find-reveal {
visibility: visible;
height: auto;
padding-block: 0 var(--mbr-dl-item-spacing);
}
/* `~` reaches every later sibling, including the answers belonging to the NEXT
* question, so shut those again. CSS has no "until" combinator; interposing a
* `dt` is how we say "past the end of this entry". These win on specificity
* (one extra type selector) as well as source order, and they are inert
* whenever nothing is focused -- which is why they do not fight the print
* rules below. */
main dl > dt:focus ~ dt ~ dd,
main dl > dd:focus-within ~ dt ~ dd {
visibility: hidden;
height: 0;
padding-block: 0;
}
/* Height only animates where the intrinsic-size keywords can be interpolated.
* Everything is gated together so browsers without it (Safari, at time of
* writing) snap the answer open cleanly rather than animating the padding
* against a height that jumps. No `max-height` magic numbers. */
@supports (interpolate-size: allow-keywords) {
:root {
/* Inherited, so it is set at the root as the feature intends. */
interpolate-size: allow-keywords;
}
main dl > dd {
transition:
height var(--mbr-dl-transition-duration) var(--mbr-dl-transition-easing),
padding-block var(--mbr-dl-transition-duration)
var(--mbr-dl-transition-easing),
visibility var(--mbr-dl-transition-duration);
/* `visibility` interpolates by its own rule -- any value strictly between
* the endpoints resolves to `visible` -- so a closing answer stays on
* screen for the whole duration and only vanishes at the end, which is
* what lets the collapse animate instead of blinking out. `allow-discrete`
* is a no-op for `visibility` itself; it is here so that a genuinely
* discrete property added to this list by an override behaves too. */
transition-behavior: allow-discrete;
}
}
/* Must follow the @supports block: equal specificity, so source order decides. */
@media (prefers-reduced-motion: reduce) {
main dl > dt,
main dl > dt::before,
main dl > dd {
transition: none;
}
}
/* Flashcard pages (`type: flashcard`): the disclosure above stays -- a question
* with its answer hidden is what a flashcard is -- and each card's review
* history is collapsed into a one-line `<details>` by `<mbr-flashcards>`
* (`flashcards/dom.ts`). Opening that `<summary>` moves focus into the history
* `<dd>`, which `dd:focus-within ~ dd` keeps open but which would close the
* answers *before* it; this keeps the earlier answers of the same card open
* too (the `:not()` stops at the previous card, as with the marker rule). */
body.flashcard
main
dl
> dd:has(~ dd:focus-within):not(:has(~ dt ~ dd:focus-within)) {
visibility: visible;
height: auto;
padding-block: 0 var(--mbr-dl-item-spacing);
}
main dl > dd.mbr-fc-history summary {
font-size: 0.85em;
color: var(--pico-muted-color);
cursor: pointer;
margin: 0;
}
main dl > dd.mbr-fc-history details {
margin: 0;
}
main dl > dd.mbr-fc-history ul {
font-size: 0.85em;
margin-block: 0.4em 0;
}
/* Flashcard rating colours: one definition for the deck's rating buttons
* (`flashcards/styles.ts`) and the reading view's progress indicators below.
* Again is a true red, not Pico's --pico-del-color: that is a muted salmon
* that reads as orange beside Hard's amber. Same pair as chat's slot 0.
* Declared on `[data-theme]` too so Good/Easy, which follow Pico's own
* colours, resolve against the theme of the element that sets one. */
:root,
[data-theme] {
--mbr-fc-again: #c62828;
--mbr-fc-hard: #b7791f;
--mbr-fc-good: var(--pico-ins-color, #2e7d32);
--mbr-fc-easy: var(--pico-primary, #0172ad);
--mbr-fc-unreviewed: #9ca3af;
}
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--mbr-fc-again: #ef5350;
--mbr-fc-unreviewed: #6b7280;
}
}
[data-theme="dark"] {
--mbr-fc-again: #ef5350;
--mbr-fc-unreviewed: #6b7280;
}
/* Reading-view progress (`flashcards/progress.ts`): a reviewed question's
* left border is its latest rating. Scoped to `main` so it can never reach
* the deck overlay (appended to <body>; its clones drop the class anyway).
* The extra padding keeps the disclosure marker off the border. `:where()`
* keeps the specificity of a bare `main dl > dt`, so the print rules further
* down (no marker, no indent) still win on paper. */
main
dl
> dt:where(
.mbr-fc-last-again,
.mbr-fc-last-hard,
.mbr-fc-last-good,
.mbr-fc-last-easy
) {
border-left: 2px solid var(--mbr-fc-last);
padding-inline-start: calc(
var(--mbr-dl-marker-size) + var(--mbr-dl-marker-gap) + 0.4em
);
}
main dl > dt.mbr-fc-last-again {
--mbr-fc-last: var(--mbr-fc-again);
}
main dl > dt.mbr-fc-last-hard {
--mbr-fc-last: var(--mbr-fc-hard);
}
main dl > dt.mbr-fc-last-good {
--mbr-fc-last: var(--mbr-fc-good);
}
main dl > dt.mbr-fc-last-easy {
--mbr-fc-last: var(--mbr-fc-easy);
}
/* A heading's pie of its cards' latest ratings. Textless (an aria-hidden svg
* inside a role="img" span), so copying the heading copies only its words. */
main .mbr-fc-pie {
display: inline-block;
width: 0.8em;
height: 0.8em;
margin-inline-start: 0.4em;
vertical-align: -0.05em;
line-height: 0;
}
main .mbr-fc-pie svg {
display: block;
width: 100%;
height: 100%;
}
main .mbr-fc-pie-again {
fill: var(--mbr-fc-again);
}
main .mbr-fc-pie-hard {
fill: var(--mbr-fc-hard);
}
main .mbr-fc-pie-good {
fill: var(--mbr-fc-good);
}
main .mbr-fc-pie-easy {
fill: var(--mbr-fc-easy);
}
main .mbr-fc-pie-unreviewed {
fill: var(--mbr-fc-unreviewed);
}
/* ==================== Contact card ====================
*
* The page header of `type: person` / `type: organization` notes
* (`_contact_card.html`). Pico variables only, so it follows every theme and
* both color schemes; no fixed widths, so it reflows down to a phone.
*
* Section titles are <p>, not <h2>: `<mbr-heading-enhancer>` gives every
* `main` heading a permalink, and "Phone" is not a place anyone links to. */
.mbr-contact-card {
--mbr-contact-avatar-size: 5.5rem;
margin: 0 0 var(--pico-block-spacing-vertical, 1.5rem);
padding: var(--pico-spacing, 1rem) calc(var(--pico-spacing, 1rem) * 1.25);
border: 1px solid var(--pico-muted-border-color, #e0e0e0);
border-radius: var(--pico-border-radius, 0.5rem);
background: var(--pico-card-background-color, transparent);
box-shadow: var(--pico-card-box-shadow, none);
}
.mbr-contact-card-identity {
display: flex;
align-items: center;
gap: calc(var(--pico-spacing, 1rem) * 1.25);
}
.mbr-contact-card-avatar {
flex: none;
width: var(--mbr-contact-avatar-size);
height: var(--mbr-contact-avatar-size);
border-radius: 50%;
object-fit: cover;
background: var(--pico-secondary-background, #eee);
}
.mbr-contact-card[data-kind="organization"] .mbr-contact-card-avatar {
border-radius: var(--pico-border-radius, 0.5rem);
}
.mbr-contact-card-avatar-default {
display: inline-flex;
padding: calc(var(--mbr-contact-avatar-size) * 0.18);
color: var(--pico-secondary-inverse, #fff);
background: var(--pico-secondary, #6c757d);
}
/* The genealogy tint the old infobox carried, kept subtle and theme-relative. */
.mbr-contact-card[data-gender="female"] .mbr-contact-card-avatar-default {
background: color-mix(in srgb, var(--pico-secondary, #6c757d) 45%, #c2477a);
}
.mbr-contact-card[data-gender="male"] .mbr-contact-card-avatar-default {
background: color-mix(in srgb, var(--pico-secondary, #6c757d) 45%, #2f6fb5);
}
.mbr-contact-card-heading {
min-width: 0;
}
.mbr-contact-card-heading > * {
margin: 0;
}
.mbr-contact-card-heading > * + * {
margin-top: 0.25rem;
}
.mbr-contact-card-name {
overflow-wrap: anywhere;
}
.mbr-contact-card-role span + span::before,
.mbr-contact-card-aliases span + span::before {
content: " · ";
color: var(--pico-muted-color, #666);
}
.mbr-contact-card-aliases {
font-style: italic;
color: var(--pico-muted-color, #666);
}
.mbr-contact-card-chip {
display: inline-block;
width: auto;
margin: 0.25rem 0 0;
padding: 0.15rem 0.7rem;
font-size: 0.85rem;
border-radius: 999px;
--pico-background-color: transparent;
--pico-color: var(--pico-primary, inherit);
--pico-border-color: var(--pico-primary-border, currentColor);
}
.mbr-contact-card-sections {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
gap: var(--pico-spacing, 1rem) calc(var(--pico-spacing, 1rem) * 1.5);
margin-top: var(--pico-spacing, 1rem);
padding-top: var(--pico-spacing, 1rem);
border-top: 1px solid var(--pico-muted-border-color, #e0e0e0);
}
/* Pico gives every <section> a block margin; the grid gap already spaces these. */
.mbr-contact-card-section {
margin: 0;
}
.mbr-contact-card-section-title {
margin: 0 0 0.35rem;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--pico-muted-color, #666);
}
.mbr-contact-card-section ul {
margin: 0;
padding: 0;
}
.mbr-contact-card-section li {
list-style: none;
margin: 0 0 0.35rem;
overflow-wrap: anywhere;
}
.mbr-contact-card-section address {
margin: 0;
font-style: normal;
}
.mbr-contact-card-label {
display: block;
font-size: 0.8rem;
color: var(--pico-muted-color, #666);
}
.mbr-contact-card-place::before {
content: " — ";
color: var(--pico-muted-color, #666);
}
/* A place listed on its own (no date known) needs no separator. */
.mbr-contact-card-label + .mbr-contact-card-place::before {
content: none;
}
@media (max-width: 576px) {
.mbr-contact-card {
--mbr-contact-avatar-size: 4rem;
}
.mbr-contact-card-identity {
align-items: flex-start;
}
}
@media print {
.mbr-contact-card {
box-shadow: none;
break-inside: avoid;
}
/* An interactive "open the info panel" control means nothing on paper. */
mbr-contact-backlinks {
display: none;
}
}
/* ==================== Chat Blocks (```chat) ==================== */
/*
* Speech bubbles for ```chat code blocks (src/chat.rs), rendered server-side
* so they work in static builds and QuickLook without any JavaScript.
*
* Each speaker gets one of eight colour slots (`.mbr-chat-c0` … `c7`) from a
* stable hash of their name. The slot sets `--mbr-chat-color`; the name label
* uses it directly and the bubble is a light wash of it over the page
* background, so every Pico theme, light or dark, gets readable bubbles from
* the one palette below. Override `--mbr-chat-c0` … `--mbr-chat-c7` to recolour.
*/
:root {
--mbr-chat-c0: #c62828;
--mbr-chat-c1: #1565c0;
--mbr-chat-c2: #2e7d32;
--mbr-chat-c3: #6a1b9a;
--mbr-chat-c4: #e65100;
--mbr-chat-c5: #00838f;
--mbr-chat-c6: #ad1457;
--mbr-chat-c7: #827717;
}
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--mbr-chat-c0: #ef5350;
--mbr-chat-c1: #64b5f6;
--mbr-chat-c2: #66bb6a;
--mbr-chat-c3: #ba68c8;
--mbr-chat-c4: #ffa726;
--mbr-chat-c5: #4dd0e1;
--mbr-chat-c6: #f06292;
--mbr-chat-c7: #d4e157;
}
}
[data-theme="dark"] {
--mbr-chat-c0: #ef5350;
--mbr-chat-c1: #64b5f6;
--mbr-chat-c2: #66bb6a;
--mbr-chat-c3: #ba68c8;
--mbr-chat-c4: #ffa726;
--mbr-chat-c5: #4dd0e1;
--mbr-chat-c6: #f06292;
--mbr-chat-c7: #d4e157;
}
.mbr-chat {
display: flex;
flex-direction: column;
gap: 0.6rem;
margin-block: 0 var(--pico-typography-spacing-vertical, 1.5rem);
}
.mbr-chat-msg {
--mbr-chat-color: var(--mbr-chat-c0);
align-self: flex-start;
max-width: 75%;
padding: 0.45rem 0.8rem;
border-radius: 1rem 1rem 1rem 0.25rem;
border: 1px solid var(--pico-muted-border-color);
/* Fallback first: color-mix() is the wash, where supported. */
background: var(--pico-card-sectioning-background-color);
background: color-mix(
in srgb,
var(--mbr-chat-color) 10%,
var(--pico-background-color)
);
border-color: color-mix(in srgb, var(--mbr-chat-color) 30%, transparent);
overflow-wrap: anywhere;
}
.mbr-chat-c0 {
--mbr-chat-color: var(--mbr-chat-c0);
}
.mbr-chat-c1 {
--mbr-chat-color: var(--mbr-chat-c1);
}
.mbr-chat-c2 {
--mbr-chat-color: var(--mbr-chat-c2);
}
.mbr-chat-c3 {
--mbr-chat-color: var(--mbr-chat-c3);
}
.mbr-chat-c4 {
--mbr-chat-color: var(--mbr-chat-c4);
}
.mbr-chat-c5 {
--mbr-chat-color: var(--mbr-chat-c5);
}
.mbr-chat-c6 {
--mbr-chat-color: var(--mbr-chat-c6);
}
.mbr-chat-c7 {
--mbr-chat-color: var(--mbr-chat-c7);
}
.mbr-chat-right {
align-self: flex-end;
border-radius: 1rem 1rem 0.25rem 1rem;
}
.mbr-chat-center {
align-self: center;
border-radius: 1rem;
text-align: center;
}
/* Same speaker again: pull the bubble up under the previous one. */
.mbr-chat-cont {
margin-top: -0.35rem;
}
.mbr-chat-name {
margin-bottom: 0.15rem;
color: var(--mbr-chat-color);
font-size: 0.8em;
font-weight: 600;
}
.mbr-chat-meta {
margin-top: 0.2rem;
color: var(--pico-muted-color);
font-size: 0.75em;
text-align: right;
}
.mbr-chat-body > :first-child,
.mbr-chat-comment > :first-child,
.mbr-chat-md > :first-child {
margin-top: 0;
}
.mbr-chat-body > :last-child,
.mbr-chat-comment > :last-child,
.mbr-chat-md > :last-child {
margin-bottom: 0;
}
.mbr-chat-delim {
align-self: stretch;
margin-block: 0.4rem;
/* The muted *border* colour all but vanishes on dark themes. */
border-top: 1px dashed var(--pico-muted-color);
opacity: 0.5;
}
.mbr-chat-comment {
align-self: center;
max-width: 85%;
color: var(--pico-muted-color);
font-size: 0.85em;
text-align: center;
}
.mbr-chat-md {
align-self: stretch;
}
/* Pico gives every `p`/`ol`/`ul` an explicit `color: var(--pico-color)`, so
* without this a comment's paragraph ignores the muted colour set above. The
* meta line needs no such rule: it is a bare `div`, never markdown. */
.mbr-chat-comment > * {
color: inherit;
}
@media (max-width: 576px) {
.mbr-chat-msg {
max-width: 90%;
}
}
@media print {
.mbr-chat-msg {
break-inside: avoid;
background: none;
border-color: #999;
}
}
/* ==================== Print Styles ==================== */
@media print {
/* PAGE MARGINS ARE ZERO ON PURPOSE -- the sheet's margins come from
* `main.container`'s padding below. Do not "restore" a margin here.
*
* A non-zero `@page` margin makes Safari (and WKWebView, i.e. GUI mode) lay
* out a large empty band at the top of EVERY page; Chrome is unaffected.
* Zeroing the page box and moving the whitespace into the content box
* removes it. This was arrived at empirically -- printing is the only way to
* observe it -- so treat the pairing as load-bearing and re-test on Safari
* before changing either half.
*
* The mechanism is NOT the WebKit `@page`-vs-print-media rule-collection
* ordering, which was the obvious suspect: that would predict a band here
* too, since this rule is itself nested inside `@media print`. Whatever the
* true cause, the trigger is the non-zero value, not the nesting. Consistent
* with that, the band could also be cleared at print time by changing any
* print-dialog setting (in either direction) or by enabling print-media
* emulation in devtools -- both of which force a re-layout. GUI mode exposes
* no dialog controls, so there it was unavoidable. */
@page {
margin: 0;
}
/* Hide chrome with an ALLOWLIST, not a denylist.
*
* `<main>` is the content; every other child of `<body>` is chrome. Listing
* the chrome by tag name instead does not survive contact with this
* codebase, because which components are emitted depends on the mode:
* `_display_enhancements.html` adds six elements to every page, `_footer.html`
* adds `<mbr-keys>` and `<mbr-fuzzy-nav>` always, `<mbr-live-reload>` in
* server mode and `<mbr-find-bar>` + `<mbr-link-enhancement>` in GUI mode,
* and the sidebar layout prepends `<mbr-browse-single>`. A denylist silently
* rots every time a component is added, and the failure is invisible until
* someone prints -- a missed element that is `position: fixed` paints on the
* FIRST page only, and one that is `display: block` with height pushes the
* content down. Inverting the test makes new components correct by default.
*
* This also covers the overlays components append straight to `<body>` at
* runtime -- the editor backdrop (`editor-crepe.ts`), the picker backdrop
* (`editor-picker-shared.ts`) and the footnote hover card
* (`mbr-footnote-preview.ts`) -- all of which are `position: fixed` and would
* otherwise paint over page one.
*
* Two carve-outs:
* - `<mbr-genealogy>` sits outside `<main>` but is generated *content*: the
* family chart on `type: person` pages. It printed before this rule
* existed and it still has to.
* - `.mbr-print-keep` is the opt-in for repositories that put real content
* in `.mbr/_footer_custom.html`. Without it their content would vanish
* from the page with no clue why. */
body > *:not(main, mbr-genealogy, .mbr-print-keep) {
display: none !important;
}
/* Review markers are injected *inside* `<main>`, so the rule above does not
* reach them. They are UI, not content -- a printed page should carry the
* document, not the reader's private annotations on it. The popover and the
* panel are `body >` children and are already hidden. */
.mbr-review-marker {
display: none !important;
}
/* Reset grid layout for printing */
body.mbr-has-sidebar {
display: block;
}
/* Reduce font size (~40% smaller than base) */
/* `color-scheme: light` is defense-in-depth, not a fix: pico's base colors
* are already light on paper because its dark override is gated on
* `screen` (see the "Dark Mode Overrides" `only screen` idiom above). This
* just makes that intent explicit and nudges native UI (form controls,
* scrollbars) light. */
:root {
--pico-font-size: 62.5%;
color-scheme: light;
}
/* The paper margins live HERE, not on `@page` -- see the note by the `@page`
* rule above for why. `--mbr-print-margin-*` are the knobs to override.
*
* Known trade-off of moving the vertical margin into the content box: the
* padding applies at the start and end of the box, so a document that runs
* past one sheet gets its top margin on page 1 and its bottom margin on the
* last page, while continuation pages fall back to the printer's own
* unprintable area. The horizontal padding is unaffected and applies to
* every page. A non-zero `@page` margin is what would give uniform vertical
* margins, and that is exactly what triggers the Safari band. */
main.container {
margin: 0;
padding: var(--mbr-print-margin-block) var(--mbr-print-margin-inline);
max-width: none;
}
/* ---------- Nothing is collapsed or clipped on paper ----------
*
* Every collapse, clamp and clip in this sheet is an affordance for *reading
* on a screen*: it trades content for skimmability, on the understanding
* that the reader can always open the thing back up. Paper takes that
* bargain away. A section the reader collapsed before hitting Print does not
* come back -- it is simply missing from the page, with nothing to indicate
* that anything was there. So print undoes all of it.
*
* The one deliberate exception is `.sr-only`; see the end of this block. */
/* Sibling content hidden by a collapsed heading (`<mbr-heading-enhancer>`).
* This is the one that bites: collapsing is a transient reading gesture, not
* an instruction about the document, and the reader has no reason to expect
* it to follow them onto the paper.
*
* `revert` and not `block`, because the class is applied to whatever
* siblings happen to follow the heading -- `<p>`, `<ul>`, `<table>`,
* `<figure>`, `<pre>` -- and each has its own correct display. `revert`
* hands each element back its user-agent value. */
.sectionhidden {
display: revert;
}
/* The "+" marker announces hidden content. Nothing is hidden here. */
.mbr-section-collapsed::before {
content: none;
}
/* Permalink anchors only exist to be hovered. */
.mbr-heading-anchor {
display: none;
}
/* Raw `<details>` blocks. GFM passes literal `<details><summary>` straight
* through, and `docs/markdown/index.md` actively recommends it as the way to
* get a real toggle -- so a block the reader left closed prints with its body
* missing.
*
* CSS cannot set the `open` attribute, so the only lever is the user agent's
* own `::details-content` box, which carries `content-visibility: hidden`
* while the element is closed. Overriding that is the technique the CSSWG
* settled on when it finally closed csswg-drafts#2084, "Ability to style
* <details>/<summary> to be open (e.g. for print styles)", in August 2025.
*
* BROWSER SUPPORT, PLAINLY: `::details-content` is Chrome/Edge 131+, Safari
* 18.4+, Firefox 143+ (Baseline newly-available, September 2025); GUI mode
* uses the system WebKit, so it tracks the installed Safari. Anywhere older
* this rule is simply dropped as an unknown selector, a closed `<details>`
* still prints closed, and there is NO CSS-only alternative -- no shipping
* engine ever exposed a light-DOM hook for the closed content (Firefox 49-102
* suppressed frame construction outright in C++; everything since, in all
* three engines, hides a shadow slot), which is precisely why that CSSWG
* issue sat open for eight years. Do not add `details > *:not(summary) {
* display: block }` as a "fallback": the selector *matches*, so it looks
* applied in devtools, but the flat-tree slot above it is hidden and nothing
* happens -- in every engine. The real fallback is a `beforeprint` listener
* flipping the `open` attribute, which is JavaScript, and so deliberately
* not done here.
*
* `content-visibility` alone is what defeats the user agent; it does not
* need `!important`, because both Blink and WebKit deliberately let author
* rules outrank the inline style they put on that shadow slot. The
* `!important`s are aimed at *user.css*, which `_head.html` links after this
* sheet: a repository that copied MDN's accordion pattern
* (`details::details-content { block-size: 0; overflow: clip }`) matches this
* selector exactly, and `@media print` adds no specificity, so without them
* source order would re-collapse the block on paper. */
details::details-content {
content-visibility: visible !important;
block-size: auto !important;
overflow: visible !important;
}
/* The marker is drawn from the `open` attribute, which is still absent -- so
* without this the triangle points "closed" beside content that is plainly
* open. `disclosure-open` is the same keyword the UA stylesheet uses for
* `details[open]`, so it is available wherever the marker renders at all. */
details > summary:first-of-type {
list-style-type: disclosure-open;
}
/* Oembed link cards clamp the description to 200px and clip what does not
* fit. There is nothing to clip *for* on paper. */
.mbr-social-link-box {
max-height: none;
overflow: visible;
}
/* Kanban boards lose cards on paper twice over: `height: 85vh` cuts off
* everything below one screenful, and the columns scroll horizontally past
* the width of the sheet. Flattening to a single column is the only shape
* that guarantees every card reaches the page -- the board reads as a run of
* headed lists rather than side-by-side columns, which is the right trade
* when the alternative is dropping cards silently.
*
* `break-after` has to be reset too: `always` is a *page* break once there
* is no multicol context to consume it, so each list would otherwise eject
* the rest of the board onto a fresh sheet.
*
* Unscoped on purpose -- `.kanban` lands on `<section>` for `--- {.kanban}`
* and on `<body>` itself when a note sets `style: kanban` in frontmatter. */
.kanban {
height: auto;
max-height: none;
columns: auto;
overflow: visible;
}
.kanban > ul,
.kanban > ol,
.kanban ul:last-child,
.kanban ol:last-child {
break-before: auto;
break-after: auto;
}
/* Theater figures size themselves against the viewport and bleed to its full
* width. The video is dropped below, but the caption is content and must not
* be squeezed by a viewport-relative height. */
figure.theater {
max-height: none;
width: auto;
margin-inline: 0;
background: none;
}
/* Prevent awkward page breaks */
pre,
code,
figure,
blockquote,
table,
ul,
ol {
page-break-inside: avoid;
}
/* style inline katex the same as inline code by default */
.math-inline {
border-radius: var(--pico-border-radius);
background: var(--pico-code-background-color);
color: var(--pico-code-color);
font-weight: var(--pico-font-weight);
line-height: initial;
font-size: 0.875em;
}
/* Marginalia: display inline for print (not absolute positioned) */
blockquote blockquote:has(blockquote) {
position: static;
}
blockquote blockquote blockquote {
position: static;
display: block;
font-size: 0.85em;
margin: var(--pico-spacing) 0;
padding: var(--pico-spacing);
background-color: var(--mbr-marginalia-bg);
border-left: 3px solid var(--pico-primary);
}
/* Hide the indicator marker in print */
blockquote blockquote blockquote::before {
content: none;
}
/* Show marginalia content normally in print */
blockquote blockquote blockquote p {
position: static;
display: block;
transform: none;
min-width: auto;
max-width: none;
background: none;
border: none;
box-shadow: none;
padding: 0;
margin: 0;
}
/* Hide popover arrows in print */
blockquote blockquote blockquote p::before,
blockquote blockquote blockquote p::after {
content: none;
}
/* Show additional paragraphs in marginalia */
blockquote blockquote blockquote p + p {
display: block;
}
/* Video/media elements: use auto height, hide player controls */
figure > video,
media-player {
max-height: none;
height: auto;
}
/* Hide video player (videos cannot be printed meaningfully) */
media-player,
video {
display: none;
}
/* Show a placeholder message for videos */
figure:has(media-player)::after,
figure:has(video)::after {
content: "[Video content - see digital version]";
display: block;
font-style: italic;
color: var(--pico-muted-color);
padding: var(--pico-spacing);
border: 1px dashed var(--pico-muted-border-color);
text-align: center;
}
/* Reduce GitHub alert icon size for print */
blockquote.markdown-alert-note::before,
blockquote.markdown-alert-tip::before,
blockquote.markdown-alert-important::before,
blockquote.markdown-alert-warning::before,
blockquote.markdown-alert-caution::before {
width: 14px;
height: 14px;
top: calc(var(--pico-spacing) * 1.25);
}
/* Footnotes: ensure they stay together and print cleanly */
.footnote-definition {
page-break-inside: avoid;
color: inherit;
}
.footnote-definition:first-of-type {
break-before: avoid;
}
/* Definition lists: force every answer open. A printed FAQ showing only the
* questions would be useless.
*
* All three selectors are needed. Printing does NOT clear focus -- someone
* who opens a question and then hits Print still has a focused `<dt>` -- so
* the two re-collapse rules would otherwise keep matching and, at one extra
* type selector, outrank a bare `main dl > dd` here. Repeating them verbatim
* makes this an equal-specificity tie that source order settles our way,
* which is why the "Definition Lists" section has to stay above this block. */
main dl > dd,
main dl > dt:focus ~ dt ~ dd,
main dl > dd:focus-within ~ dt ~ dd {
visibility: visible;
height: auto;
overflow: visible;
padding-block: 0 var(--mbr-dl-item-spacing);
}
/* The disclosure chevron means nothing on paper. Dropping the marker box
* also means the question's hanging indent has to be undone, or the first
* line would hang out into the margin. */
main dl > dt::before {
content: none;
}
main dl > dt {
padding-inline-start: 0;
text-indent: 0;
/* Keep a question with the answer it introduces. */
break-after: avoid;
}
main dl > dt,
main dl > dd {
page-break-inside: avoid;
}
/* ---------- DELIBERATE EXCEPTION: `.sr-only` stays hidden ----------
*
* Do not add `.sr-only` to the "nothing is collapsed on paper" rules above.
* It is not collapsed content. `index.html` (and `home`/`section`/`tag`)
* emit a visually hidden *duplicate* of the page title purely so Pagefind
* can weight it (`<span class="sr-only" data-pagefind-weight="10">`) --
* revealing it would print the title twice, once as a stray line and once as
* the real `<h1>` directly beneath it.
*
* Nothing is lost by leaving it: the visible heading carries the same text.
* `aria-hidden` needs no print rule either -- it hides content from the
* accessibility tree, never visually -- and `[hidden]` is deliberately left
* alone, because unlike a collapsed section it is an explicit statement by
* the author that the content does not apply. */
}
/* ==================== Enhanced Oembed Social Link Boxes ==================== */
.mbr-social-link-box {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 1em;
max-width: 900px;
max-height: 200px;
overflow: hidden;
}
.mbr-social-link {
grid-column: span 2;
text-decoration: none;
}
.mbr-social-link header {
font-size: 1rem;
font-weight: bold;
margin-bottom: var(--pico-typography-spacing-vertical);
}
.mbr-social-link p {
font-size: 0.75rem;
margin: 0;
color: var(--pico-secondary);
}
/*
* PRINT TIP: For best results when printing:
* 1. Use light mode (not dark mode)
* 2. Enable "Print backgrounds" in your browser's print dialog
*/
/* ============================= Kanban Display ========================= */
/*
* just use `--- {.kanban}` or put style: kanban in the frontmatter as appropriate
*/
.kanban {
columns: 18rem; /* needs to be fixed since we don't know how many columns */
column-gap: 2rem;
column-fill: auto; /* should avoid balancing and just fill left first */
/* column-rule: 1px solid var(--pico-muted-border-color); */
overflow-x: auto; /* allow horizontal scroll */
overflow-y: visible;
max-width: 100%;
height: 85vh;
}
.kanban h1,
.kanban h2,
.kanban h3,
.kanban h4,
.kanban h5,
.kanban h6 {
margin-top: 0;
break-after: avoid;
-webkit-column-break-after: avoid;
break-inside: avoid;
-webkit-column-break-inside: avoid;
}
.kanban h1:has(+ ul),
.kanban h1:has(+ ol),
.kanban h2:has(+ ul),
.kanban h2:has(+ ol),
.kanban h3:has(+ ul),
.kanban h3:has(+ ol),
.kanban h4:has(+ ul),
.kanban h4:has(+ ol),
.kanban h5:has(+ ul),
.kanban h5:has(+ ol),
.kanban h6:has(+ ul),
.kanban h6:has(+ ol) {
break-after: avoid;
-webkit-column-break-after: avoid;
break-inside: avoid;
-webkit-column-break-inside: avoid;
}
.kanban > ul,
.kanban > ol {
margin-left: 0;
padding-left: 0;
break-after: column;
break-inside: avoid;
-webkit-column-break-inside: avoid;
-webkit-column-break-before: avoid;
break-before: avoid;
}
.kanban > ul > li,
.kanban > ol > li {
margin-bottom: var(--pico-block-spacing-vertical);
padding: var(--pico-block-spacing-vertical)
var(--pico-block-spacing-horizontal);
border-radius: var(--pico-border-radius);
background: var(--pico-card-background-color);
box-shadow: var(--pico-card-box-shadow);
display: block;
/* background-color: var(--pico-card-sectioning-background-color); */
font-weight: bold;
break-inside: avoid;
-webkit-column-break-inside: avoid;
}
.kanban ul > li ul,
.kanban ol > li ol {
font-weight: normal;
margin-top: 1em;
}
.kanban ul:last-child,
.kanban ol:last-child {
break-after: always;
}
/************ OUTLINE **********************/
.outline main {
font-size: 16px;
line-height: 1.6;
}
.outline main h1,
.outline main h2,
.outline main h3,
.outline main h4,
.outline main h5,
.outline main h6 {
font-weight: 600;
line-height: 1.1;
margin: 36px 24px 16px 0;
padding: 0;
}
.outline main h1,
.outline main h2 {
border-bottom: 1px solid #eaecef;
}
.outline main h1 {
font-size: 32px;
line-height: 40px;
margin: 0 0 16px;
padding: 0 0 9.600000381469727px;
}
.outline main h2 {
font-size: 24px;
line-height: 30px;
padding: 0 0 7.199999809265137px;
}
.outline main h3 {
font-size: 20px;
line-height: 25px;
}
.outline main h4 {
font-size: 16px;
line-height: 20px;
margin: 24px 0 16px;
padding: 0;
}
.outline main h5 {
font-size: 14px;
line-height: 17px;
}
.outline main h6 {
font-size: 13.600000381469727px;
line-height: 17px;
}
.outline main {
line-height: 1.6;
padding: 3px;
}
.outline main p {
margin: 1em 0;
}
.outline main > *:first-child {
margin-top: 0 !important;
}
.outline main > *:last-child {
margin-bottom: 0 !important;
}
.outline main p,
.outline main blockquote,
.outline main ul,
.outline main ol,
.outline main dl,
.outline main table,
.outline main pre {
margin-bottom: 16px;
}
.outline main ol,
.outline main ul {
padding-left: 2.6ch;
}
.outline main ul > li {
list-style-type: disc;
}
.outline main ol > li {
list-style-type: upper-alpha;
}
.outline main ol ol > li {
list-style-type: decimal;
}
.outline main ol ol ol > li {
list-style-type: lower-alpha;
}
.outline main ol ol ol ol > li {
list-style-type: lower-roman;
}
.outline main ol ol,
.outline main ol ul,
.outline main ul ul,
.outline main ul ol {
margin-top: 0;
margin-bottom: 0;
}
.outline main li > p {
margin-bottom: 0;
}
.outline main li p + p {
margin-top: 16px;
}
.outline main dl {
padding: 0;
}
.outline main dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 700;
}
.outline main dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.outline main blockquote {
padding: 0 15px;
margin-left: 0;
color: #777;
border-left: 4px solid #ddd;
}
.outline main blockquote > :first-child {
margin-top: 0;
}
.outline main blockquote > :last-child {
margin-bottom: 0;
}
@media print {
body.outline {
background: #fff;
}
.outline main img,
.outline main table,
.outline main figure {
page-break-inside: avoid;
}
.outline main {
background: #fff;
border: none !important;
font-size: 12px;
}
.outline main pre code {
overflow: visible;
}
}
.outline main {
counter-reset: H2;
}
.outline main h2 {
counter-reset: H3;
}
.outline main h2:before {
content: counter(H2) ". ";
counter-increment: H2;
counter-reset: H3;
counter-reset: H4;
counter-reset: H5;
}
.outline main h3 {
counter-reset: H4;
}
.outline main h3:before {
content: counter(H2) "." counter(H3) ". ";
counter-increment: H3;
counter-reset: H4;
counter-reset: H5;
}
.outline main h4 {
counter-reset: H5;
}
.outline main h4:before {
content: counter(H2) "." counter(H3) "." counter(H4) ". ";
counter-increment: H4;
counter-reset: H5;
}
.outline main h5:before {
content: counter(H2) "." counter(H3) "." counter(H4) "." counter(H5) ". ";
counter-increment: H5;
}