Mirror content layout with text direction; admin stays LTR
This commit is contained in:
@@ -19,8 +19,8 @@ Pagerite is a single-user CMS/blog. This document records the initial high-level
|
||||
|
||||
- Content is written in **Markdown** with powerful extensions (tables, footnotes, code highlighting, etc.).
|
||||
- **Embedded HTML is passed through unfiltered**, including inline scripts and other dynamic content the author wants to post. This is safe by the single-trusted-author assumption above.
|
||||
- Renderer: **markdown-it-py** with mdit-py-plugins (footnotes, definition lists, task lists, brace-attributes, admonitions and `::: name` containers — generic `<div class="name">` wrappers (the name may be followed by brace attributes: `::: aside {.right}`), of which `::: aside` floats as a muted side box and `{.margin}` / `::: margin` marks any block a margin note — on all but phone widths they are taken out of flow into the side zone at the article's left (the region the nav sidebar overlays, or the sidebar's own track when the layout reserves one) and the text never moves — and `::: nocols` opts its section out of column layout; tables and strikethrough from the default preset), GitHub-style alerts (`> [!NOTE]` / TIP / IMPORTANT / WARNING / CAUTION, rendered in the admonition callout styling), with `html=True` for raw passthrough, `typographer=True` for SmartyPants-style replacements in body text (curly quotes, `--` / `---` → en / em dashes, `...` → ellipsis, `(c)` → ©, etc.), and `breaks=True` so single line breaks inside paragraphs become `<br>` — including inside blockquotes, where every newline is kept and a blank `>` line starts a new paragraph. Code spans/blocks and raw HTML are left untouched. Fenced code blocks are highlighted server-side with **Pygments** (`nowrap` spans styled by `/_assets/pygments-*.css`, which maps every token class onto the `--code-*` variables; the base stylesheet defines light and dark palette sets resolved via `light-dark()`, so each theme gets the set matching its `color-scheme` and may only retint `--code-bg` to keep the well in the page's color family); a JS copy button appears on hover. Should this prove limiting, we implement our own renderer on top of html5tagger, which we already use for all HTML generation.
|
||||
- **Files are content-addressed.** Uploads (`PUT /_api/files/{filename}`) are stored on disk (`<hostname>/files/`, RAM-cached uncompressed + zstd) by content hash — blake3, first 6 bytes hex + original extension — and served immutable from `/_f/…`. Raster images (not GIF) and SVGs (rasterized) are recompressed via mediapreview: the original is kept as `{hash}.orig{ext}` (internal only, never served — it may carry EXIF data; SVG originals stay servable as `{hash}.svg`) while pages link the extension-less `/_f/{hash}` and the server picks from the derivatives (`{hash}.avif` / `{hash}.webp` / `{hash}.jpg`) by Accept header — a format only when listed explicitly (`image/avif` → AVIF, `image/webp` → WebP, otherwise JPEG), with `vary: accept`; an explicit extension in the URL pins the format. Absolute URLs that survive page renames and dedupe identical content; pages no longer own files. An image standing alone in its paragraph becomes a block `<figure>` — with `<figcaption>` when it has a title; images inline with text and raw `<img>` HTML stay plain inline images. Positioning is by attribute classes: `{.right}` — `{.right}`, `{.left}` float at 30% of the text column (the caption wraps within it; an explicit `width=300` makes the figure shrink-wrap the image instead), `{.margin}` makes it a margin note, placed in the side zone left of the text on all but phone widths, `{.wide}` goes full bleed (viewport edge to edge, or up to the docked editor; the sidebar stacks on top of it); plain attributes like `width=300` work too. The same brace syntax on a block's last line (no blank line between) applies to the whole block: a paragraph ending with `{.wide}` becomes a full-width element that breaks out of the column layout, and space-separated at the end of a text line (`some text {.small}`) the braces likewise belong to the block — a space is what keeps them off an image or link ending the line, which keep their own directly-attached attrs; text size classes `{.small}` / `{.large}` / `{.huge}` (em-based) work on any block; written on the line after a block it applies to that preceding block — this is how headings, `::: containers` and code fences take classes (a wide code fence goes full bleed like a wide figure). Headings (h1/h2) clear floats, so images never overflow into the next section.
|
||||
- Renderer: **markdown-it-py** with mdit-py-plugins (footnotes, definition lists, task lists, brace-attributes, admonitions and `::: name` containers — generic `<div class="name">` wrappers (the name may be followed by brace attributes: `::: aside {.right}`), of which `::: aside` floats as a muted side box and `{.margin}` / `::: margin` marks any block a margin note — on all but phone widths they are taken out of flow into the side zone at the article's start edge (left in LTR, right in RTL — the region the nav sidebar overlays, or the sidebar's own track when the layout reserves one) and the text never moves — and `::: nocols` opts its section out of column layout; tables and strikethrough from the default preset), GitHub-style alerts (`> [!NOTE]` / TIP / IMPORTANT / WARNING / CAUTION, rendered in the admonition callout styling), with `html=True` for raw passthrough, `typographer=True` for SmartyPants-style replacements in body text (curly quotes, `--` / `---` → en / em dashes, `...` → ellipsis, `(c)` → ©, etc.), and `breaks=True` so single line breaks inside paragraphs become `<br>` — including inside blockquotes, where every newline is kept and a blank `>` line starts a new paragraph. Code spans/blocks and raw HTML are left untouched. Fenced code blocks are highlighted server-side with **Pygments** (`nowrap` spans styled by `/_assets/pygments-*.css`, which maps every token class onto the `--code-*` variables; the base stylesheet defines light and dark palette sets resolved via `light-dark()`, so each theme gets the set matching its `color-scheme` and may only retint `--code-bg` to keep the well in the page's color family); a JS copy button appears on hover. Should this prove limiting, we implement our own renderer on top of html5tagger, which we already use for all HTML generation.
|
||||
- **Files are content-addressed.** Uploads (`PUT /_api/files/{filename}`) are stored on disk (`<hostname>/files/`, RAM-cached uncompressed + zstd) by content hash — blake3, first 6 bytes hex + original extension — and served immutable from `/_f/…`. Raster images (not GIF) and SVGs (rasterized) are recompressed via mediapreview: the original is kept as `{hash}.orig{ext}` (internal only, never served — it may carry EXIF data; SVG originals stay servable as `{hash}.svg`) while pages link the extension-less `/_f/{hash}` and the server picks from the derivatives (`{hash}.avif` / `{hash}.webp` / `{hash}.jpg`) by Accept header — a format only when listed explicitly (`image/avif` → AVIF, `image/webp` → WebP, otherwise JPEG), with `vary: accept`; an explicit extension in the URL pins the format. Absolute URLs that survive page renames and dedupe identical content; pages no longer own files. An image standing alone in its paragraph becomes a block `<figure>` — with `<figcaption>` when it has a title; images inline with text and raw `<img>` HTML stay plain inline images. Positioning is by attribute classes: `{.right}` — `{.right}`, `{.left}` float at 30% of the text column, to its end/start edge following the text direction (the caption wraps within it; an explicit `width=300` makes the figure shrink-wrap the image instead), `{.margin}` makes it a margin note, placed in the side zone at the text's start edge on all but phone widths, `{.wide}` goes full bleed (viewport edge to edge, or up to the docked editor; the sidebar stacks on top of it); plain attributes like `width=300` work too. The same brace syntax on a block's last line (no blank line between) applies to the whole block: a paragraph ending with `{.wide}` becomes a full-width element that breaks out of the column layout, and space-separated at the end of a text line (`some text {.small}`) the braces likewise belong to the block — a space is what keeps them off an image or link ending the line, which keep their own directly-attached attrs; text size classes `{.small}` / `{.large}` / `{.huge}` (em-based) work on any block; written on the line after a block it applies to that preceding block — this is how headings, `::: containers` and code fences take classes (a wide code fence goes full bleed like a wide figure). Headings (h1/h2) clear floats, so images never overflow into the next section.
|
||||
|
||||
## Page structure and navigation
|
||||
|
||||
|
||||
@@ -168,7 +168,8 @@ const abuseRows = computed(() => formatAbuseRows(rangeData.value?.abuse || [], c
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="analytics-view">
|
||||
<!-- Untranslated admin dashboard: always LTR, like the editor panel. -->
|
||||
<div class="analytics-view" lang="en" dir="ltr">
|
||||
<div class="analytics-panel">
|
||||
<header>
|
||||
<h1>Analytics</h1>
|
||||
|
||||
@@ -288,8 +288,10 @@ body {
|
||||
|
||||
/* 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.
|
||||
on both sides. The sidebar sits in the start gutter (grid columns are
|
||||
flow-relative: on RTL pages the whole composition mirrors), so it
|
||||
appearing or disappearing never shifts the article; the end 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
|
||||
@@ -305,16 +307,16 @@ body {
|
||||
content length — code excluded) lift the 78rem cap: main takes the full
|
||||
width and the article composes itself inside it — fluid, bounded text
|
||||
lanes with the surplus left vacant (see the article layout rules
|
||||
below). The left track — main always sits in column 2 — collapses to
|
||||
below). The start track — main always sits in column 2 — collapses to
|
||||
zero when the page has no sidebar; the sidebar then simply overlays
|
||||
the vacant zone, as it does on single-column pages. */
|
||||
body:has(.multicol) #content {
|
||||
grid-template-columns: 0 minmax(0, 1fr);
|
||||
}
|
||||
|
||||
/* With a sidebar the left lane gets its own track at every width, so the
|
||||
/* With a sidebar the start lane gets its own track at every width, so the
|
||||
sidebar never overlaps the article and the article leans on the
|
||||
viewport's right edge (surplus extends the lane). The lane is flexible:
|
||||
viewport's end edge (surplus extends the lane). The lane is flexible:
|
||||
12rem when space is tight, growing up to 150% (18rem) once the viewport
|
||||
exceeds the article's 88.5rem (86rem + main's side padding). The --lane
|
||||
variable doubles as the measure for the margin boxes and the .wide
|
||||
@@ -398,7 +400,7 @@ body.editing #sidebar {
|
||||
|
||||
#sidebar {
|
||||
grid-column: 1;
|
||||
/* Pinned to the page's left edge (not the article's) and kept in view
|
||||
/* Pinned to the page's start 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;
|
||||
@@ -409,8 +411,11 @@ body.editing #sidebar {
|
||||
width: 12rem;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
padding: 1rem 1rem 1rem 1.25rem;
|
||||
border-radius: 0 0 0.5rem 0;
|
||||
/* The extra inline-start padding (the viewport-edge side, mirroring with
|
||||
the direction) matches main's side padding. */
|
||||
padding: 1rem;
|
||||
padding-inline: 1.25rem 1rem;
|
||||
border-end-start-radius: 0.5rem;
|
||||
background: color-mix(var(--bg) 75%, transparent);
|
||||
backdrop-filter: blur(0.5rem);
|
||||
}
|
||||
@@ -437,7 +442,7 @@ body.editing #sidebar {
|
||||
#sidebar ul ul li::before {
|
||||
content: "🔹";
|
||||
display: inline-block;
|
||||
margin-left: -1.3em;
|
||||
margin-inline-start: -1.3em;
|
||||
width: 1.3em;
|
||||
}
|
||||
|
||||
@@ -578,7 +583,7 @@ main {
|
||||
background: color-mix(var(--bg) 30%, transparent);
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
@@ -679,19 +684,19 @@ article * + h6 {
|
||||
so the gap becomes the figure's margin plus the full --list-indent. */
|
||||
article :is(ul, ol:not([type])) {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
padding-inline-start: 0;
|
||||
--list-indent: 2em;
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
article :is(ul, ol:not([type])) > li {
|
||||
padding-left: var(--list-indent);
|
||||
padding-inline-start: var(--list-indent);
|
||||
}
|
||||
|
||||
article ul li::before {
|
||||
content: "🔹";
|
||||
display: inline-block;
|
||||
margin-left: calc(-1 * var(--list-indent));
|
||||
margin-inline-start: calc(-1 * var(--list-indent));
|
||||
width: var(--list-indent);
|
||||
text-align: center;
|
||||
}
|
||||
@@ -713,13 +718,13 @@ article ol:not([type]) > li::before {
|
||||
content: counter(item) ".";
|
||||
color: var(--muted);
|
||||
display: inline-block;
|
||||
margin-left: calc(-1 * var(--list-indent));
|
||||
margin-inline-start: calc(-1 * var(--list-indent));
|
||||
width: var(--list-indent);
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
/* Task lists: real clickable checkboxes. The checkbox stands in for the
|
||||
list marker — taken out of flow, left-aligned in the indent box and
|
||||
list marker — taken out of flow, start-aligned in the indent box and
|
||||
centered on the first line's middle, so the item text starts at the
|
||||
same edge as every other list item's. */
|
||||
article .task-list-item {
|
||||
@@ -732,7 +737,7 @@ article .task-list-item::before {
|
||||
|
||||
article .task-list-item-checkbox {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
top: calc(0.5lh - .15ex);
|
||||
translate: 0 -50%;
|
||||
font-size: inherit;
|
||||
@@ -772,7 +777,7 @@ article h1 .edit-link {
|
||||
position: static;
|
||||
font-size: 1.1rem;
|
||||
vertical-align: 0.3em;
|
||||
margin-left: 0.4rem;
|
||||
margin-inline-start: 0.4rem;
|
||||
}
|
||||
|
||||
/* Section pens sit at the end of anchored h2s, dimmer than the page pen
|
||||
@@ -781,7 +786,7 @@ article h2 .edit-section {
|
||||
position: static;
|
||||
font-size: 0.85rem;
|
||||
vertical-align: 0.35em;
|
||||
margin-left: 0.4rem;
|
||||
margin-inline-start: 0.4rem;
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
@@ -811,10 +816,10 @@ article dd {
|
||||
}
|
||||
|
||||
/* The long-article composition (.multicol): a fluid but bounded text
|
||||
lane with a 16rem side zone at the article's left, centered in main —
|
||||
surplus width becomes vacant space, never endless text (with a sidebar
|
||||
the article leans right instead and the sidebar's track is the left
|
||||
lane; see below). (The backend render splits the body into .colseg
|
||||
lane with a 16rem side zone at the article's start side, centered in
|
||||
main — surplus width becomes vacant space, never endless text (with a
|
||||
sidebar the article leans to the end edge instead and the sidebar's
|
||||
track is the start lane; see below). (The backend render splits the body into .colseg
|
||||
segments separated by full-width h2s and .wide elements, tags
|
||||
text-heavy segments of several paragraphs .cols — a ::: nocols
|
||||
container opts its section out, and column-filling paragraphs are
|
||||
@@ -832,7 +837,7 @@ article.multicol {
|
||||
@container (min-width: 45rem) {
|
||||
/* The side zone (not on phones): lane content indents 16rem; margin
|
||||
boxes ({.margin} / ::: margin blocks, ::: aside, {.margin} figures)
|
||||
are taken out of flow and placed against the article's left edge —
|
||||
are taken out of flow and placed against the article's start edge —
|
||||
the same region the nav sidebar overlays. The boxes stay in the
|
||||
column segment at their anchor point (the backend render no longer
|
||||
splits segments around them); absolute positioning off the article —
|
||||
@@ -842,14 +847,14 @@ article.multicol {
|
||||
article.multicol>.colseg,
|
||||
article.multicol>h1,
|
||||
article.multicol>h2 {
|
||||
margin-left: 16rem;
|
||||
margin-inline-start: 16rem;
|
||||
}
|
||||
|
||||
article.multicol .margin,
|
||||
article.multicol .aside,
|
||||
article.multicol figure.margin {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 14rem;
|
||||
max-width: none;
|
||||
margin: 0.3rem 0 0;
|
||||
@@ -870,11 +875,11 @@ article.multicol {
|
||||
}
|
||||
}
|
||||
|
||||
/* With a sidebar, the sidebar's 12rem track IS the left lane at every
|
||||
/* With a sidebar, the sidebar's 12rem track IS the start lane at every
|
||||
width (see #content): no in-article zone, the text lane runs fluid (up
|
||||
to 86rem) and leans on main's right edge — surplus width extends the
|
||||
left lane instead of balancing out on the right — and margin boxes
|
||||
hang into the lane off the article's left border, sliding under the
|
||||
to 86rem) and leans on main's end edge — surplus width extends the
|
||||
start lane instead of balancing out at the end — and margin boxes
|
||||
hang into the lane off the article's start border, sliding under the
|
||||
translucent sticky nav, which only ever occupies its top. (Not below
|
||||
48rem: there the sidebar becomes a link strip above the article and
|
||||
there is no lane to fall into.) */
|
||||
@@ -884,8 +889,8 @@ article.multicol {
|
||||
margin-inline: auto 0;
|
||||
}
|
||||
|
||||
/* The sidebar fills the flexible lane (its left side stays on the
|
||||
viewport's left edge, growing rightward). */
|
||||
/* The sidebar fills the flexible lane (its start side stays on the
|
||||
viewport's start edge, growing toward the article). */
|
||||
body:has(#sidebar):has(.multicol):not(.editing) #sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -893,29 +898,29 @@ article.multicol {
|
||||
body:has(#sidebar):has(.multicol):not(.editing) article.multicol>.colseg,
|
||||
body:has(#sidebar):has(.multicol):not(.editing) article.multicol>h1,
|
||||
body:has(#sidebar):has(.multicol):not(.editing) article.multicol>h2 {
|
||||
margin-left: 0;
|
||||
margin-inline-start: 0;
|
||||
}
|
||||
|
||||
body:has(#sidebar):has(.multicol):not(.editing) article.multicol .margin,
|
||||
body:has(#sidebar):has(.multicol):not(.editing) article.multicol .aside,
|
||||
body:has(#sidebar):has(.multicol):not(.editing) article.multicol figure.margin {
|
||||
position: absolute;
|
||||
/* Attached to the article's left border (1.25rem gap), hanging into
|
||||
the left lane and growing leftward with it: 12rem when the lane is
|
||||
/* Attached to the article's start border (1.25rem gap), hanging into
|
||||
the start lane and growing with it: 12rem when the lane is
|
||||
tight, up to 150% (18rem) when the track or the surplus has room
|
||||
(100cqw - 100% is the surplus left of the right-leaning article).
|
||||
(100cqw - 100% is the surplus beside the end-leaning article).
|
||||
The lane (track + main's padding) always guarantees the room. */
|
||||
--box-w: min(18rem, var(--lane) + 100cqw - 100% - 1.25rem);
|
||||
width: var(--box-w);
|
||||
max-width: none;
|
||||
left: calc(-1.25rem - var(--box-w));
|
||||
inset-inline-start: calc(-1.25rem - var(--box-w));
|
||||
margin: 0.3rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* A shrink-wrapped figure (explicit image width) centers in the plain
|
||||
layout; inside a column the centering looks adrift — left-align.
|
||||
Floated figures keep their own margins (the text gap). */
|
||||
layout; inside a column the centering looks adrift — align to the start
|
||||
edge. Floated figures keep their own margins (the text gap). */
|
||||
.multicol .colseg.cols figure:has(img[width]):not(:has(.left), :has(.right), .margin) {
|
||||
margin-inline: 0;
|
||||
}
|
||||
@@ -988,13 +993,15 @@ article a:hover {
|
||||
|
||||
/* Blockquotes: spacing comes from the blockquote itself (bottom-only like
|
||||
everything else in articles); inner paragraphs keep only the gap between
|
||||
them. The negative left margin pushes the bar out past the text edge, so
|
||||
them. The negative start 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);
|
||||
margin: 0 0 1rem;
|
||||
margin-inline-start: -0.5rem;
|
||||
padding: 0;
|
||||
padding-inline-start: 0.25rem;
|
||||
border-inline-start: 0.25rem solid var(--accent2);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@@ -1009,17 +1016,19 @@ blockquote p + p {
|
||||
/* Admonitions (markdown !!! note/warning/...) and GitHub-style alerts
|
||||
(> [!NOTE] ...): a lightweight callout in the blockquote idiom — accent
|
||||
bar and a faint wash, recolored per type, with a type emoji on the
|
||||
title. The negative left margin pushes bar and wash out past the text
|
||||
title. The negative start margin pushes bar and wash out past the text
|
||||
edge so the inner text aligns with surrounding paragraphs — same trick
|
||||
as blockquotes and code blocks (margin-left = border + padding-left).
|
||||
Bottom-only margins like everything else in articles; inner paragraphs
|
||||
carry no margins of their own. */
|
||||
as blockquotes and code blocks (margin-inline-start = border +
|
||||
padding-inline-start). Bottom-only margins like everything else in
|
||||
articles; inner paragraphs carry no margins of their own. */
|
||||
.admonition,
|
||||
.markdown-alert {
|
||||
margin: 0 0 1rem -1.15rem;
|
||||
margin: 0 0 1rem;
|
||||
margin-inline-start: -1.15rem;
|
||||
padding: 0.4rem 0.9rem;
|
||||
border-left: 0.25rem solid var(--admonition-color, var(--accent));
|
||||
border-radius: 0 0.3rem 0.3rem 0;
|
||||
border-inline-start: 0.25rem solid var(--admonition-color, var(--accent));
|
||||
border-start-end-radius: 0.3rem;
|
||||
border-end-end-radius: 0.3rem;
|
||||
background: color-mix(var(--admonition-color, var(--accent)) 7%, transparent);
|
||||
}
|
||||
|
||||
@@ -1037,7 +1046,7 @@ blockquote p + p {
|
||||
|
||||
.admonition-title::before,
|
||||
.markdown-alert-title::before {
|
||||
padding-right: 0.35em;
|
||||
padding-inline-end: 0.35em;
|
||||
}
|
||||
|
||||
.admonition.note .admonition-title::before,
|
||||
@@ -1099,18 +1108,19 @@ blockquote p + p {
|
||||
Where the layout has room for a side zone — multicol pages, the
|
||||
sidebar's track, the wide single-column gutter (see the article
|
||||
section and the figure rules below) — the boxes are taken out of flow
|
||||
and absolutely positioned into it, off the article's left border, each
|
||||
and absolutely positioned into it, off the article's start border, each
|
||||
at the vertical spot where it occurs in the text (boxes occurring
|
||||
closer together than their heights may overlap — keep them apart);
|
||||
otherwise they stay in-column left floats (consecutive floats stack
|
||||
via clear: left). Headings already clear floats, so in-column boxes
|
||||
never bleed into the next section. */
|
||||
otherwise they stay in-column start floats (consecutive floats stack
|
||||
via clear: inline-start). Headings already clear floats, so in-column
|
||||
boxes never bleed into the next section. */
|
||||
.aside {
|
||||
float: left;
|
||||
clear: left;
|
||||
float: inline-start;
|
||||
clear: inline-start;
|
||||
width: 30%;
|
||||
max-width: 20rem;
|
||||
margin: 0.3rem 1.2rem 1rem 0;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-end: 1.2rem;
|
||||
padding: 0.6rem 0.9rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
@@ -1140,11 +1150,12 @@ blockquote p + p {
|
||||
}
|
||||
|
||||
.margin {
|
||||
float: left;
|
||||
clear: left;
|
||||
float: inline-start;
|
||||
clear: inline-start;
|
||||
width: 30%;
|
||||
max-width: 20rem;
|
||||
margin: 0.3rem 1.2rem 1rem 0;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-end: 1.2rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
@@ -1153,10 +1164,10 @@ 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;
|
||||
past them by its own padding. Themes that add a leading border must
|
||||
extend margin-inline-start by the border width to keep this
|
||||
alignment. */
|
||||
margin-inline: -0.8rem;
|
||||
background: var(--code-bg);
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
@@ -1196,7 +1207,7 @@ p code {
|
||||
}
|
||||
|
||||
code:not(pre code):first-child {
|
||||
padding-left: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
/* Click-to-copy button (added by pagerite.js) */
|
||||
@@ -1241,7 +1252,7 @@ td {
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
background: linear-gradient(180deg,
|
||||
var(--table-head-a, color-mix(var(--accent) 10%, var(--surface))),
|
||||
var(--table-head-b, color-mix(var(--accent) 18%, var(--surface))));
|
||||
@@ -1313,20 +1324,24 @@ figure img:not([width]) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Floated figures: {.right} / {.left}, defaulting to 30% of the column
|
||||
and capped at half of it. */
|
||||
/* Floated figures: {.right} / {.left} float to the text column's end/start
|
||||
edge — the class names are author-facing and fixed, but the sides follow
|
||||
the text direction (in RTL, .left floats right) — defaulting to 30% of
|
||||
the column and capped at half of it. */
|
||||
figure:has(.right) {
|
||||
float: right;
|
||||
float: inline-end;
|
||||
width: 30%;
|
||||
max-width: 50%;
|
||||
margin: 0.3rem 0 1rem 1em;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-start: 1em;
|
||||
}
|
||||
|
||||
figure:has(.left) {
|
||||
float: left;
|
||||
float: inline-start;
|
||||
width: 30%;
|
||||
max-width: 50%;
|
||||
margin: 0.3rem 1em 1rem 0;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-end: 1em;
|
||||
}
|
||||
|
||||
/* The same floats for other blocks: ::: left / ::: right containers
|
||||
@@ -1334,17 +1349,19 @@ figure:has(.left) {
|
||||
tables all take the class directly ({.right} at the end of a
|
||||
paragraph's last line, a trailing {.left} line after a fence, ...). */
|
||||
:is(div, p, pre, blockquote, table).right {
|
||||
float: right;
|
||||
float: inline-end;
|
||||
width: 30%;
|
||||
max-width: 50%;
|
||||
margin: 0.3rem 0 1rem 1em;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-start: 1em;
|
||||
}
|
||||
|
||||
:is(div, p, pre, blockquote, table).left {
|
||||
float: left;
|
||||
float: inline-start;
|
||||
width: 30%;
|
||||
max-width: 50%;
|
||||
margin: 0.3rem 1em 1rem 0;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-end: 1em;
|
||||
}
|
||||
|
||||
/* An image with an explicit width attribute shrink-wraps instead: the
|
||||
@@ -1356,13 +1373,15 @@ figure:has(img[width]) {
|
||||
}
|
||||
|
||||
/* {.margin} figures (the class moves onto the figure wrapper at render —
|
||||
see markdown.py) float left like {.left} ones — until they fall into
|
||||
the side zone (see the composition rules up in the article section). */
|
||||
see markdown.py) float to the start edge like {.left} ones — until
|
||||
they fall into the side zone (see the composition rules up in the
|
||||
article section). */
|
||||
figure.margin {
|
||||
float: left;
|
||||
float: inline-start;
|
||||
width: 30%;
|
||||
max-width: 50%;
|
||||
margin: 0.3rem 1em 1rem 0;
|
||||
margin: 0.3rem 0 1rem;
|
||||
margin-inline-end: 1em;
|
||||
}
|
||||
|
||||
/* Click-to-enlarge (pagerite.js): article figure images open in a
|
||||
@@ -1439,12 +1458,12 @@ article figure img {
|
||||
}
|
||||
}
|
||||
|
||||
/* Wide single-column pages: margin boxes lean into the vacant left
|
||||
/* Wide single-column pages: margin boxes lean into the vacant start-side
|
||||
gutter instead (below 104rem the gutter cannot hold the box, and while
|
||||
editing the docked panel reshapes the gutters — in both they stay
|
||||
plain floats). Out of flow like on multicol pages: the box hangs off
|
||||
the article's left border, growing with the gutter up to 150% (18rem),
|
||||
its right side 1.25rem off the border. */
|
||||
the article's start border, growing with the gutter up to 150% (18rem),
|
||||
its end side 1.25rem off the border. */
|
||||
@media (min-width: 104rem) {
|
||||
body:not(.editing):not(:has(.multicol)) article .margin,
|
||||
body:not(.editing):not(:has(.multicol)) article .aside,
|
||||
@@ -1453,14 +1472,14 @@ article figure img {
|
||||
--box-w: min(18rem, (100vw - 78rem) / 2 - 1.25rem);
|
||||
width: var(--box-w);
|
||||
max-width: none;
|
||||
left: calc(-1.25rem - var(--box-w));
|
||||
inset-inline-start: calc(-1.25rem - var(--box-w));
|
||||
margin: 0.3rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* In the wide symmetric gutters (where the sidebar overlays the flexible
|
||||
left gutter rather than a reserved track) the sidebar flexes with the
|
||||
gutter up to 150% — its left side stays on the viewport's edge. */
|
||||
start gutter rather than a reserved track) the sidebar flexes with the
|
||||
gutter up to 150% — its start side stays on the viewport's edge. */
|
||||
@media (min-width: 102rem) {
|
||||
body:not(.editing) #sidebar {
|
||||
width: min(18rem, 100%);
|
||||
@@ -1509,7 +1528,7 @@ body.editing pre.wide {
|
||||
|
||||
/* Narrow single-column pages with a sidebar: below 102rem the symmetric
|
||||
gutters can no longer both hold the sidebar, so #content reserves it
|
||||
with a flexible left track (see the matching media query below) and the
|
||||
with a flexible start track (see the matching media query below) and the
|
||||
article always starts at the lane's width (+ main's 1.25rem padding) —
|
||||
the bleed margin measures off --lane. Scoped by :has(#sidebar) since
|
||||
the sidebar element is omitted entirely on pages without
|
||||
@@ -1536,9 +1555,9 @@ body:has(.multicol) pre.wide {
|
||||
}
|
||||
|
||||
/* Multicol with a sidebar track (≥48rem, see #content): main starts at
|
||||
the flexible lane's width and the article leans right, so the bleed
|
||||
extends left past the surplus and the lane to the true viewport edge —
|
||||
sliding under the translucent sidebar — and right past main's
|
||||
the flexible lane's width and the article leans to the end edge, so the
|
||||
bleed extends past the surplus and the lane to the true viewport start
|
||||
edge — sliding under the translucent sidebar — and past main's end
|
||||
padding. */
|
||||
@media (min-width: 48rem) {
|
||||
body:has(#sidebar):has(.multicol):not(.editing) figure:has(.wide),
|
||||
@@ -1556,7 +1575,7 @@ figcaption {
|
||||
hyphens: auto;
|
||||
-webkit-hyphens: auto;
|
||||
text-wrap: pretty;
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
/* Never let a long caption stretch a shrink-to-fit figure wider than the
|
||||
image; the caption wraps at the figure's width instead. */
|
||||
width: 0;
|
||||
@@ -1582,7 +1601,7 @@ article h2 {
|
||||
|
||||
/* Narrow windows with a sidebar: below 102rem the symmetric gutters can no
|
||||
longer both hold the 12rem sidebar, so reserve its space with a flexible
|
||||
left track instead of letting it overlap the article (multicol pages use
|
||||
start track instead of letting it overlap the article (multicol pages use
|
||||
the same track at every width — see the #content rules above; their
|
||||
higher-specificity rule wins there). The lane is 12rem when space is
|
||||
tight, growing up to 150% (18rem) once the viewport exceeds the
|
||||
@@ -1702,10 +1721,10 @@ article h2 {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* The single-column sidebar .wide margins assume a left sidebar column;
|
||||
with the sidebar on top the article is viewport-wide and the plain
|
||||
centered bleed applies again. (Multicol pages need no override: their
|
||||
cqw bleed is exact at any width.) */
|
||||
/* The single-column sidebar .wide margins assume a start-side sidebar
|
||||
column; with the sidebar on top the article is viewport-wide and the
|
||||
plain centered bleed applies again. (Multicol pages need no override:
|
||||
their cqw bleed is exact at any width.) */
|
||||
body:has(#sidebar):not(.editing):not(:has(.multicol)) figure:has(.wide) {
|
||||
margin-inline: calc(50% - 50vw);
|
||||
}
|
||||
@@ -1746,7 +1765,7 @@ article h2 {
|
||||
.dateline {
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
text-align: left;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.footnotes {
|
||||
|
||||
@@ -111,12 +111,13 @@ article h3 {
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left-color: var(--accent);
|
||||
border-inline-start-color: var(--accent);
|
||||
background: color-mix(var(--accent) 6%, transparent);
|
||||
padding: 0.4rem 0.9rem;
|
||||
/* Keep the quoted text on the paragraph edge: the tinted box extends
|
||||
past it by its own border/padding, like code blocks. */
|
||||
margin: 0 -0.9rem 1rem calc(-0.25rem - 0.9rem);
|
||||
margin: 0 0 1rem;
|
||||
margin-inline: calc(-0.25rem - 0.9rem) -0.9rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -125,9 +126,9 @@ blockquote {
|
||||
bar stays in both. */
|
||||
pre {
|
||||
border: 1px solid transparent;
|
||||
border-left: 0.25rem solid var(--accent);
|
||||
border-inline-start: 0.25rem solid var(--accent);
|
||||
/* Text on the paragraph edge: the box extends by padding + border. */
|
||||
margin-left: calc(-0.8rem - 0.25rem);
|
||||
margin-inline-start: calc(-0.8rem - 0.25rem);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,9 +144,9 @@ article h1 {
|
||||
font-weight: 700;
|
||||
padding-bottom: 0.5rem;
|
||||
/* The hazard-stripe underline breaks out of the page box: the negative
|
||||
right margin extends the h1's box (and thus its background) all the
|
||||
way to the viewport's right edge. */
|
||||
margin-right: calc((100% - 100vw) / 2);
|
||||
end margin extends the h1's box (and thus its background) all the
|
||||
way to the viewport's edge on that side. */
|
||||
margin-inline-end: calc((100% - 100vw) / 2);
|
||||
background:
|
||||
linear-gradient(-55deg,
|
||||
transparent 0 0.2rem,
|
||||
@@ -186,20 +186,21 @@ article ul ul ul li::before {
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left-color: var(--accent2);
|
||||
border-inline-start-color: var(--accent2);
|
||||
background: color-mix(var(--accent2) 6%, transparent);
|
||||
padding: 0.25rem 0.75rem;
|
||||
/* Keep the quoted text on the paragraph edge: the tinted box extends
|
||||
past it by its own border/padding, like code blocks. */
|
||||
margin: 0 -0.75rem 1rem -1rem;
|
||||
margin: 0 0 1rem;
|
||||
margin-inline: -1rem -0.75rem;
|
||||
}
|
||||
|
||||
/* Code follows the color scheme; the dark-scheme well joins the violet
|
||||
family (--code-bg above). The orange side bar stays in both. */
|
||||
pre {
|
||||
border-left: 0.25rem solid var(--accent);
|
||||
border-inline-start: 0.25rem solid var(--accent);
|
||||
/* Text on the paragraph edge: the box extends by padding + border. */
|
||||
margin-left: calc(-0.8rem - 0.25rem);
|
||||
margin-inline-start: calc(-0.8rem - 0.25rem);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ article ul li::before {
|
||||
content: "◆";
|
||||
color: var(--accent);
|
||||
font-size: 0.8em;
|
||||
margin-left: calc(-1 * var(--list-indent) / 0.8);
|
||||
margin-inline-start: calc(-1 * var(--list-indent) / 0.8);
|
||||
width: calc(var(--list-indent) / 0.8);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ article ul ul ul li::before {
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left-color: var(--accent2);
|
||||
border-inline-start-color: var(--accent2);
|
||||
}
|
||||
|
||||
/* Code panels sit slightly lighter than the page; the token colors come
|
||||
|
||||
@@ -160,7 +160,7 @@ main::before {
|
||||
top edge and a grassy shadow. */
|
||||
#sidebar {
|
||||
background: linear-gradient(160deg, #f4faddd9, #d9eec5cf);
|
||||
border-right: 1px solid #ffffff80;
|
||||
border-inline-end: 1px solid #ffffff80;
|
||||
border-bottom: 1px solid var(--line);
|
||||
box-shadow: 0 0.3rem 1rem #4f913b1f;
|
||||
border-radius: 1rem;
|
||||
@@ -225,7 +225,7 @@ article ul ul ul li::before {
|
||||
/* Quotes get a grassy edge and a wash of sunlight. */
|
||||
blockquote {
|
||||
color: #4d6849;
|
||||
border-left-color: var(--accent2);
|
||||
border-inline-start-color: var(--accent2);
|
||||
background: linear-gradient(90deg, #fff0a238, transparent 70%);
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
|
||||
+3
-2
@@ -349,8 +349,9 @@ def _layout(
|
||||
property attributes, everything else (description, twitter:*) as name.
|
||||
|
||||
``lang`` is the served language for <html lang>; an RTL language (ar,
|
||||
fa, ...) also puts dir="rtl" on <html> (the editor panel carries its own
|
||||
lang="en" dir="ltr", so it is unaffected). ``canonical`` and
|
||||
fa, ...) also puts dir="rtl" on <html> (the editor panel and the
|
||||
analytics dashboard carry their own lang="en" dir="ltr", so they are
|
||||
unaffected). ``canonical`` and
|
||||
``alternates`` ((hreflang, href) pairs) are the page's language URLs
|
||||
(see docs/localization.md), emitted right after the viewport and before
|
||||
the social tags: canonical first, then the hreflang alternates.
|
||||
|
||||
Reference in New Issue
Block a user