/* Base structural and functional styles for Pagerite pages and editors. Color choices are intentionally conservative CSS variables so themes can override them without fighting layout or interaction rules. */ @import url("./fonts/fonts.css"); @import url("./pygments.css"); :root { /* The base theme is light, period — this also resolves the light-dark() code palette below (themes declare their own color-scheme). */ color-scheme: light; --bg: #ffffff; --surface: #f5f5f5; --text: #1a1a1a; --muted: #666666; --accent: #0056b3; --accent2: #003d80; --line: #0000001a; /* Links stay quiet — mostly text color with a hint of the accent — and light up to the full accent on hover. */ --link: color-mix(in oklab, var(--text) 50%, var(--accent)); /* Code highlighting palette, consumed by pygments.css: complete light and dark sets (background included), resolved by light-dark() from the used color-scheme. A theme picks a set simply by declaring color-scheme; a fixed dark code block on a light page pins the dark set with `pre { color-scheme: dark }`. Only override individual --code-* variables for specific theming needs. */ --code-bg: light-dark(var(--surface), #0d1117); --code-text: light-dark(#24292f, #e6edf3); --code-muted: light-dark(#6e7781, #6e7681); --code-comment: light-dark(#59636e, #8b949e); --code-keyword: light-dark(#cf222e, #ff7b72); --code-builtin: light-dark(#0550ae, #79c0ff); --code-string: light-dark(#0a3069, #a5d6ff); --code-function: light-dark(#8250df, #d2a8ff); --code-type: light-dark(#953800, #f0883e); --code-tag: light-dark(#116329, #7ee787); --code-error: light-dark(#d1242f, #f85149); --code-added: light-dark(#1a7f37, #56d364); --code-hl: light-dark(#fff8c5, #6e7681); /* One variable per available font family (faces in fonts/fonts.css); everything else references these, so font stacks live here only. */ --font-source-sans: "Source Sans 3", system-ui, sans-serif; --font-source-serif: "Source Serif 4", serif; --font-fraunces: "Fraunces", serif; --font-literata: "Literata", serif; --font-playfair: "Playfair Display", serif; --font-cormorant: "Cormorant", serif; --font-inter: "Inter", system-ui, sans-serif; --font-montserrat: "Montserrat", system-ui, sans-serif; --font-fira-code: "Fira Code", ui-monospace, monospace; --font-cause: "Cause", system-ui, sans-serif; --font-exo2: "Exo 2", system-ui, sans-serif; --font-new-rocker: "New Rocker", "Playfair Display", serif; --font-body: var(--font-source-sans); --font-heading: var(--font-source-serif); /* The brand follows the heading font unless overridden separately. */ --font-brand: var(--font-heading); --font-code: var(--font-fira-code); /* Width of the docked editor panel (used both here for shifting the page and in the Vue editor's own styles). */ --editor-w: min(46rem, 50vw); } * { box-sizing: border-box; } html { scroll-behavior: smooth; /* Native-scrollbar fallback styling (JS off or before pagerite.js runs): thin, theme-muted thumb on a transparent track. With JS the scrollbars are replaced by OverlayScrollbars (see pagerite.js) — floating, auto-hidden scrollbars styled by the --os-* variables below, so they never reserve layout space or shift the page when appearing. */ scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--muted) 45%, transparent) transparent; } .os-scrollbar { --os-size: 0.5rem; --os-thumb-bg: color-mix(in srgb, var(--muted) 45%, transparent); --os-thumb-hover-bg: color-mix(in srgb, var(--muted) 65%, transparent); --os-thumb-active-bg: var(--muted); --os-track-bg: transparent; --os-thumb-border-radius: 0.25rem; } body { font-family: var(--font-body); font-size: 1.05rem; line-height: 1.65; /* Tabular numerals wherever the active font supports them; avoids numbers jumping in width as counters/values change. */ font-variant-numeric: tabular-nums; margin: 0; background: var(--bg); color: var(--text); /* Full-bleed elements (.wide) size to 100vw, which counts the vertical scrollbar; clip the few stray pixels instead of scrolling. */ overflow-x: clip; /* Full height even on short pages: the footer sits at the bottom and the docked editor (sized by #content) never collapses. */ min-height: 100vh; display: flex; flex-direction: column; } #content { flex: 1; } /* Full-width banner: image header with the brand and nav overlaid. Base provides a plain fallback; themes override with artwork. */ #banner { position: relative; display: flex; flex-direction: column; justify-content: flex-end; min-height: 11rem; background: linear-gradient(135deg, var(--surface), var(--bg)); border-bottom: 1px solid var(--line); } /* Per-page banner content (img, styled div, inline SVG...) fills the banner; swapped along with #nav/#main on fetch-navigation. The backend inlines the effective banner design's SVG artwork here first (pagerite/themes/{design}/banner.svg, marked svg[data-design]), then the page's own banner code after it. */ #page-banner { position: absolute; inset: 0; overflow: hidden; /* Stack the design artwork and the page's own banner code on top of each other (artwork first): the banner is a background layer, author code overlays it. A single child behaves exactly as before. */ display: grid; } /* :not(style, script): author-level display:block would override the UA's display:none on those and render their source as banner text. */ #page-banner>*:not(style, script) { grid-area: 1 / 1; display: block; width: 100%; height: 100%; object-fit: cover; /* Scroll parallax: pagerite.js sets --pry on ; the banner stays windowed in place while the artwork drifts inside it. The scale provides overscan so the drift never reveals an edge. */ transform: scale(1.25) translateY(var(--pry, 0px)); will-change: transform; } /* The design artwork wrapper (div[data-design]) takes the sizing and parallax above; an svg inside it fills it (it is what used to be the direct child before designs got a wrapper). */ #page-banner [data-design]>svg { display: block; width: 100%; height: 100%; } #brand, #nav { position: relative; } #brand { font-family: var(--font-brand); font-weight: 700; font-size: 2.4rem; text-decoration: none; /* One line always: pagerite.js shrinks the font size to fit instead of wrapping (the themed size is the maximum). */ white-space: nowrap; margin: auto 1.25rem 0; padding-top: 1.5rem; color: var(--text); } /* Nav overlaid at the bottom of the banner */ #nav { font-size: 1.3em; padding: 0.35rem 1.25rem; } #nav ul { list-style: none; margin: 0; padding: 0; display: flex; gap: 0.25rem 1.5rem; flex-wrap: wrap; align-items: baseline; } #nav a { text-decoration: none; color: var(--text); } #nav ul ul a { color: var(--muted); } #nav a:hover { color: var(--accent); } #nav span { color: var(--muted); font-size: 0.95rem; } #nav .current { color: var(--accent); font-weight: 500; } /* Sidebar + main row. A symmetric grid: the article column is sized by the viewport alone (never by content), with equally sized flexible gutters on both sides. The sidebar sits in the left gutter, so it appearing or disappearing never shifts the article; the right gutter balances it. The outer tracks are minmax(0, 1fr) — a plain 1fr has an `auto` minimum, which let the 12rem sidebar expand its track at narrow widths and push the article off-center; now the sidebar overlays the gutter edge instead (it is translucent/blurred exactly so it can pass over content, and full-bleed .wide images slide underneath it). */ #content { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 78rem) minmax(0, 1fr); /* The docked editor pushes the content (not the header) right. */ transition: margin-left 0.25s ease; } /* Long articles (.multicol is added by pagerite.js based on content length) lift the 78rem cap and scrap the right gutter: a 1fr left gutter (which holds the overlaying sidebar) and the article taking all the rest, out to the right viewport edge. The column count follows the width (see the `columns: 30rem` rule below). The .wide breakout is re-anchored to the left gutter below (the article is no longer viewport-centered). */ body:has(.multicol) #content { grid-template-columns: minmax(0, 1fr) minmax(0, 4fr); } body.editing #content { margin-left: var(--editor-w); padding-left: 1rem; /* gap between the docked editor and the content */ /* No overflow clipping here: .editor-host lives outside this box (negative left), and .wide shrink-wraps to the remaining space. */ } /* The sidebar's gutter space is needed by the editor instead. */ body.editing #sidebar { display: none; } /* The editor host lives inside #content: it starts below the banner and ends above the footer. The panel itself sticks to the viewport while scrolling (but never taller than the content area). */ .editor-host { position: absolute; top: 0; bottom: 0; left: calc(0px - var(--editor-w)); width: var(--editor-w); } .editor-root.overlay { position: sticky; top: 0; height: 100vh; max-height: 100%; background: var(--bg); animation: editor-slide-in 0.25s ease; } .editor-root.overlay.closing { transform: translateX(-100%); transition: transform 0.25s ease; } @keyframes editor-slide-in { from { transform: translateX(-100%); } } /* The banner's own pen: opens the site editor (banner + structure). Qualified with `button` to beat the later .edit-link rule's left offset (both classes apply to the same element). */ button.banner-edit-link { position: absolute; top: 0.6rem; right: 1.25rem; left: auto; z-index: 10; font: inherit; border: none; cursor: pointer; background: none; padding: 0; } #sidebar { grid-column: 1; /* Pinned to the page's left edge (not the article's) and kept in view while scrolling. Translucent + blurred rather than an opaque box, so full-bleed .wide images can pass underneath without a hard edge. */ justify-self: start; align-self: start; position: sticky; top: 0; z-index: 1; width: 12rem; max-height: 100vh; overflow-y: auto; padding: 1rem 1rem 1rem 1.25rem; border-radius: 0 0 0.5rem 0; background: color-mix(in srgb, var(--bg) 75%, transparent); backdrop-filter: blur(0.5rem); } #sidebar ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.8em; line-height: 1.0; } #sidebar a { text-decoration: none; color: var(--muted); } #sidebar a:hover { color: var(--accent); } #sidebar .current { color: var(--accent); font-weight: 500; } main { grid-column: 2; /* No top padding: a leading wide image sits flush under the banner, and text-first pages get their spacing from the h1's top margin instead. */ padding: 0 1.25rem 3rem; } article h1, article h2, article h3 { font-family: var(--font-heading); font-weight: 600; line-height: 1.25; } article h1 { font-size: 2.2rem; margin: 2rem 0 1.2rem; } /* Margin strategy: bottom-only inside articles. Top margins misalign column tops and collapse unpredictably; spacing comes from below. */ article p, article ul, article ol, article dl, article blockquote, article pre, article figure, article table { margin-top: 0; margin-bottom: 1rem; } article h3 { margin: 1.4rem 0 0.4rem; } /* Lists: small diamond emoji markers — blue 🔹 on odd nesting levels, orange 🔸 on even. The marker occupies a 1em outdented box so wrapped lines align. */ article ul { list-style: none; padding-inline-start: 1em; } article ul li::before { content: "🔹"; display: inline-block; margin-left: -1.3em; width: 1.3em; } article ul ul li::before { content: "🔸"; } article ul ul ul li::before { content: "🔹"; } /* Task lists: real checkboxes (not emoji), clickable in the public page. */ article .task-list-item::before { content: none; } article .task-list-item-checkbox { cursor: pointer; margin-inline-end: 0.35em; vertical-align: middle; } article { position: relative; } .edit-link { position: absolute; top: 0.2rem; /* In the left gutter, on the same side as the docked editor panel. */ left: -2.2rem; z-index: 2; /* stay above full-bleed .wide images */ font: inherit; background: none; border: none; padding: 0; cursor: pointer; opacity: 0.7; text-shadow: 0 0 0.1em black; } /* pagerite.js tucks the pen at the end of the article's first h1. */ article h1 .edit-link { position: static; font-size: 1.1rem; vertical-align: 0.3em; margin-left: 0.4rem; } .edit-link:hover { opacity: 1; } /* Login/profile buttons injected by pagerite.js when Paskia SSO is in use. Both sit in the banner's top-right corner, to the right of the site pen, and are styled like the edit pens. */ button.login-link, button.profile-link { position: absolute; top: 0.6rem; right: 0.5rem; z-index: 10; font: inherit; border: none; cursor: pointer; background: none; padding: 0; opacity: 0.7; text-shadow: 0 0 0.1em black; } button.login-link:hover, button.profile-link:hover { opacity: 1; } article p, article li, article dd { text-align: justify; hyphens: auto; } /* Multi-column reading, but only for long articles (pagerite.js adds .multicol based on content length and splits the body into .colseg segments separated by full-width h2s and wide figures; only segments with enough text get .cols). No fixed breakpoint: `columns: 30rem` lets CSS fit as many columns of at least 30rem as the article's current width allows — since .multicol also uncaps the article width (see #content above), a wider window simply yields more columns. */ .multicol .colseg.cols { columns: 30rem; column-gap: 3.5rem; column-rule: 1px solid var(--line); } .multicol .colseg { margin-bottom: 1rem; p, li { break-inside: avoid-column; } figure, pre, blockquote, table, dl { break-inside: avoid; } } article h2 { font-size: 1.5rem; margin: 2.2rem 0 0.6rem; } article a { color: var(--link); font-weight: 500; text-decoration: none; } article a:hover { color: var(--accent); } /* Blockquotes: inner paragraphs carry no margins (spacing comes from the blockquote itself, bottom-only like everything else in articles). The negative left margin pushes the bar out past the text edge, so quoted text aligns with the surrounding paragraphs — same trick as code blocks. */ blockquote { margin: 0 0 1rem -0.5rem; padding: 0 0 0 0.25rem; border-left: 0.25rem solid var(--accent2); color: var(--muted); } blockquote p { margin: 0; } pre { overflow-x: auto; padding: 0.5rem 0.8rem; /* Code text aligns with the surrounding paragraphs: the box extends past them by its own padding. Themes that add a left border must extend margin-left by the border width to keep this alignment. */ margin-left: -0.8rem; margin-right: -0.8rem; background: var(--code-bg); border-radius: 4px; position: relative; } /* Inline code integrates with the text, no box of its own */ p code, li code { font-size: 0.85em; } /* Click-to-copy button (added by pagerite.js) */ .copy { position: absolute; top: 0.35rem; right: 0.35rem; font: inherit; font-size: 0.75rem; padding: 0.15rem 0.6rem; color: var(--muted); background: var(--bg); border: 1px solid var(--line); border-radius: 4px; cursor: pointer; opacity: 0; transition: opacity 0.2s; } pre:hover .copy, .copy:focus-visible { opacity: 1; } .copy.copied { color: var(--accent); } code { font-family: var(--font-code); font-size: 0.88em; } /* Tables separate by color, not lines: the header is a soft vertical gradient tinted with the theme's accent (themes can override the --table-head-* stops outright), body cells carry a very faint diagonal wash of --table-tint that lifts them apart, and only a whisper of a row separator remains. */ table { border-collapse: collapse; } th, td { padding: 0.35rem 0.8rem; border: none; } th { text-align: left; background: linear-gradient(180deg, var(--table-head-a, color-mix(in srgb, var(--accent) 10%, var(--surface))), var(--table-head-b, color-mix(in srgb, var(--accent) 18%, var(--surface)))); } td { background: linear-gradient(160deg, color-mix(in srgb, var(--table-tint, var(--accent)) 5%, transparent), transparent 75%); } tbody tr:nth-child(even) td { background: linear-gradient(160deg, color-mix(in srgb, var(--table-tint, var(--accent)) 9%, transparent), color-mix(in srgb, var(--table-tint, var(--accent)) 3%, transparent) 75%); } tbody tr+tr td { border-top: 1px solid color-mix(in srgb, var(--table-tint, var(--accent)) 12%, transparent); } /* Definition lists are laid out as a lightweight two-column grid — terms in an accent column with a sensible minimum width, descriptions filling the rest — with no borders or fills (tables carry the heavier shading). Explicit grid columns keep runs of multiple