Mirror content layout with text direction; admin stays LTR

This commit is contained in:
2026-09-03 20:01:23 +00:00
parent d6d07db2c5
commit e6a0c57446
8 changed files with 141 additions and 118 deletions
+117 -98
View File
@@ -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 {