Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
825379ca2a |
@@ -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.).
|
- 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.
|
- **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 — both drop into the left margin when the layout has room for it (the sticky sidebar shares that gutter and slides over them translucently, like full-bleed images), and stay in-column floats otherwise — 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.
|
- 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 float in 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 by content hash — blake3, first 6 bytes hex + original extension — and served immutable from `/_f/{hash}.ext`. 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}` drops it into the left margin like a margin note, `{.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; 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.
|
- **Files are content-addressed.** Uploads (`PUT /_api/files/{filename}`) are stored by content hash — blake3, first 6 bytes hex + original extension — and served immutable from `/_f/{hash}.ext`. 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, floating 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; 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
|
## Page structure and navigation
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ Pagerite is a single-user CMS/blog. This document records the initial high-level
|
|||||||
|
|
||||||
## Reading experience
|
## Reading experience
|
||||||
|
|
||||||
- The article column is sized by the **viewport, never by content**: a symmetric grid (`1fr minmax(0, 78rem) 1fr`) with flexible gutters keeps the layout stable across navigation. The sidebar occupies the left gutter, the right gutter balances it; long articles (flagged `.multicol` by the backend render) lift the 78rem cap and take everything right of the left gutter, out to the viewport edge. The backend also splits the body into `.colseg` segments at h1/h2 headings, `.wide` elements and margin-breakout blocks (full-width separators or margin boxes, never inside columns), tagging segments that hold enough text with `.cols` — code blocks are excluded from that measure, and a `::: nocols` container opts its whole section out. The CSS then fits **at most two columns** of at least 30rem per segment, so a wider window widens the pair instead of adding columns. Margin boxes (`.margin`, `::: aside`) leave the column flow for the left gutter on wide viewports and stay in-column floats below that.
|
- The article column is sized by the **viewport, never by content**: a symmetric grid (`1fr minmax(0, 78rem) 1fr`) with flexible gutters keeps the layout stable across navigation. The sidebar occupies the left gutter, the right gutter balances it. Long articles (flagged `.multicol` by the backend render) lift the cap and become a bounded **composition**, centered in the available space with the surplus left vacant: a fluid text lane (up to 42rem) plus a 16rem **side zone at the article's left** — the region the nav sidebar overlays — which hosts margin boxes (`.margin`, `::: aside`, margin figures) at all but phone widths, without the text ever moving. On pages with a sidebar below ~110rem (where the sidebar gets its own 12rem track) the track is the left lane instead: no in-article zone, the text lane runs fluid up to 86rem, and the boxes fall into the track, sliding under the translucent sticky nav. Once two lanes fit beside the zone (≥96rem available in `main`), the text flows in two fluid lanes (36rem minimum, capped at 102rem total — technical content wants the wider lanes, and wider windows just add vacant space). The stages step by the space actually available in `main` (container queries + `cqw` units, so the docked editor's inset is automatic). `.wide` figures on multicol pages bleed to the viewport edges measured from `main` (`cqw`), sliding under the sidebar. The backend splits the body into `.colseg` segments at h1/h2 headings, `.wide` elements and margin blocks (full-width separators or margin boxes, never inside columns), tagging segments that hold enough text with `.cols` — code blocks are excluded from that measure, and a `::: nocols` container opts its whole section out. On wide single-column pages (≥104rem), margin boxes lean into the vacant left gutter as well.
|
||||||
- A gentle **scroll-reveal** of headings, figures and block-level elements (IntersectionObserver). It is layout-level: articles need no support for it, and `prefers-reduced-motion` disables all motion.
|
- A gentle **scroll-reveal** of headings, figures and block-level elements (IntersectionObserver). It is layout-level: articles need no support for it, and `prefers-reduced-motion` disables all motion.
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|||||||
+195
-105
@@ -276,14 +276,24 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Long articles (.multicol comes from the backend render, based on
|
/* Long articles (.multicol comes from the backend render, based on
|
||||||
content length — code excluded) lift the 78rem cap and scrap the right
|
content length — code excluded) lift the 78rem cap: main takes the full
|
||||||
gutter: a 1fr left gutter (which holds the overlaying sidebar and any
|
width and the article composes itself inside it — fluid, bounded text
|
||||||
margin-breakout boxes) and the article taking all the rest, out to the
|
lanes, centered, with the surplus left vacant (see the article layout
|
||||||
right viewport edge. Columns are capped at two (see the
|
rules below). The left track — main always sits in column 2 — collapses
|
||||||
`columns: 30rem 2` rule below). The .wide breakout is re-anchored to
|
to zero when the page has no sidebar; the sidebar then simply overlays
|
||||||
the left gutter below (the article is no longer viewport-centered). */
|
the vacant zone, as it does on single-column pages. */
|
||||||
body:has(.multicol) #content {
|
body:has(.multicol) #content {
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 4fr);
|
grid-template-columns: 0 minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only when the vacant zone cannot hold the sidebar does it get its own
|
||||||
|
12rem track — the same compromise single-column pages make below
|
||||||
|
102rem. Above that, a sidebar's presence changes nothing about the
|
||||||
|
article. */
|
||||||
|
@media (max-width: 110rem) {
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) #content {
|
||||||
|
grid-template-columns: 12rem minmax(0, 1fr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.editing #content {
|
body.editing #content {
|
||||||
@@ -421,6 +431,11 @@ main {
|
|||||||
/* No top padding: a leading wide image sits flush under the banner, and
|
/* 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. */
|
text-first pages get their spacing from the h1's top margin instead. */
|
||||||
padding: 0 1.25rem 3rem;
|
padding: 0 1.25rem 3rem;
|
||||||
|
/* The layout container for the article composition: the multicol stage
|
||||||
|
(lane count), the margin fall and the .wide bleed respond to the
|
||||||
|
actual available width here — editor inset included — via container
|
||||||
|
queries and cqw units. */
|
||||||
|
container-type: inline-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
article h1,
|
article h1,
|
||||||
@@ -562,19 +577,100 @@ article dd {
|
|||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Multi-column reading, but only for long articles: the backend render
|
/* The long-article composition (.multicol): a fluid but bounded text
|
||||||
splits the body into .colseg segments (separated by full-width h2s and
|
lane with a 16rem side zone at the article's left, centered in main —
|
||||||
.wide elements), tags segments with enough text as .cols (a ::: nocols
|
surplus width becomes vacant space, never endless text. (The backend
|
||||||
container opts its section out) and flags the article .multicol based
|
render splits the body into .colseg segments separated by full-width
|
||||||
on content length — code blocks excluded. Never more than two columns:
|
h2s and .wide elements, tags text-heavy segments .cols — a ::: nocols
|
||||||
`columns: 30rem 2` fits one or two columns of at least 30rem into the
|
container opts its section out — and flags the article .multicol; CSS
|
||||||
article's current width — since .multicol also uncaps the article
|
owns the geometry.) Technical content wants a wide lane: up to 42rem
|
||||||
width (see #content above), a wider window widens the two columns
|
single, or two fluid lanes (36rem minimum, never more than two) once
|
||||||
instead of adding more. */
|
they fit beside the zone, capped at 102rem total. The zone — the
|
||||||
.multicol .colseg.cols {
|
region the nav sidebar overlays — is a margin indent on the lane
|
||||||
columns: 30rem 2;
|
content; margin boxes float into it, and the text never moves. */
|
||||||
column-gap: 3.5rem;
|
article.multicol {
|
||||||
column-rule: 1px solid var(--line);
|
margin-inline: auto;
|
||||||
|
max-width: 58rem; /* 42rem lane + 16rem zone */
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (min-width: 45rem) {
|
||||||
|
/* The side zone (not on phones): lane content indents 16rem; margin
|
||||||
|
boxes ({.margin} / ::: margin blocks, ::: aside, {.margin} figures)
|
||||||
|
float at the article's left edge — the same region the nav sidebar
|
||||||
|
overlays. Scoped to direct .body children (the backend render keeps
|
||||||
|
margin blocks out of the column segments); nested ones keep the
|
||||||
|
in-column float fallback. */
|
||||||
|
.multicol .body>.colseg,
|
||||||
|
.multicol .body>h1,
|
||||||
|
.multicol .body>h2,
|
||||||
|
article.multicol>h1 {
|
||||||
|
margin-left: 16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multicol .body>.margin,
|
||||||
|
.multicol .body>.aside,
|
||||||
|
.multicol .body>figure:has(.margin) {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
width: 14rem;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0.3rem 2rem 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wide separators start below any margin box — their bleed must not
|
||||||
|
wrap around it. */
|
||||||
|
.multicol .body>figure:has(.wide),
|
||||||
|
.multicol .body>div.wide,
|
||||||
|
.multicol .body>pre.wide {
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (min-width: 96rem) {
|
||||||
|
article.multicol {
|
||||||
|
max-width: 102rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Two fluid lanes (36rem minimum) beside the zone, up to the 102rem
|
||||||
|
cap — wider windows just add vacant space. */
|
||||||
|
.multicol .colseg.cols {
|
||||||
|
columns: 36rem 2;
|
||||||
|
column-gap: 3.5rem;
|
||||||
|
column-rule: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With a sidebar, below 110rem the sidebar gets its own 12rem track (see
|
||||||
|
#content). The track IS the left lane then: no in-article zone, the
|
||||||
|
text lane runs fluid (up to 86rem), and margin boxes fall all the way
|
||||||
|
left into the track — sliding under the translucent sticky nav, which
|
||||||
|
only ever occupies its top. (The track never exists once the container
|
||||||
|
reaches 96rem, so the two-lane rules above never meet it. Not below
|
||||||
|
48rem: there the sidebar becomes a link strip above the article and
|
||||||
|
there is no track to fall into.) */
|
||||||
|
@media (min-width: 48rem) and (max-width: 110rem) {
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) article.multicol {
|
||||||
|
max-width: 86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) .multicol .body>.colseg,
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) .multicol .body>h1,
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) .multicol .body>h2,
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) article.multicol>h1 {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) .multicol .body>.margin,
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) .multicol .body>.aside,
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) .multicol .body>figure:has(.margin) {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
width: 12rem;
|
||||||
|
max-width: none;
|
||||||
|
/* From the text's left edge to the page's left edge: half the
|
||||||
|
centering difference plus the track and main's padding. */
|
||||||
|
margin: 0.3rem 0 1rem calc(50% - 50cqw - 13.25rem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A shrink-wrapped figure (explicit image width) centers in the plain
|
/* A shrink-wrapped figure (explicit image width) centers in the plain
|
||||||
@@ -735,18 +831,19 @@ blockquote p + p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Side boxes: ::: aside is a muted floated box (consecutive asides stack
|
/* Side boxes: ::: aside is a muted floated box (consecutive asides stack
|
||||||
via clear: right); {.margin} / ::: margin is a plainer margin note, and
|
via clear: left); {.margin} / ::: margin is a plainer margin note, and
|
||||||
figures take {.margin} like {.left}. All of them drop into the left
|
figures take {.margin} like {.left}. On multicol pages they float in
|
||||||
margin when the layout has room for it (the breakout rules live with
|
the composition's left side zone — or in the sidebar's track when the
|
||||||
the figure rules below); without room they stay in-column floats —
|
layout reserves one (see the article section); on wide single-column
|
||||||
asides on the right, margin boxes on the left. Headings already clear
|
pages they lean into the left gutter (with the figure rules below);
|
||||||
|
otherwise they stay in-column left floats. Headings already clear
|
||||||
floats, so boxes never bleed into the next section. */
|
floats, so boxes never bleed into the next section. */
|
||||||
.aside {
|
.aside {
|
||||||
float: right;
|
float: left;
|
||||||
clear: right;
|
clear: left;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
max-width: 20rem;
|
max-width: 20rem;
|
||||||
margin: 0.3rem 0 1rem 1.2rem;
|
margin: 0.3rem 1.2rem 1rem 0;
|
||||||
padding: 0.6rem 0.9rem;
|
padding: 0.6rem 0.9rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -930,8 +1027,8 @@ figure:has(img[width]) {
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* {.margin} figures float left like {.left} ones — until the margin
|
/* {.margin} figures float left like {.left} ones — until they fall into
|
||||||
breakout below pulls them into the left gutter. */
|
the side zone (see the composition rules up in the article section). */
|
||||||
figure:has(.margin) {
|
figure:has(.margin) {
|
||||||
float: left;
|
float: left;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@@ -939,35 +1036,14 @@ figure:has(.margin) {
|
|||||||
margin: 0.3rem 1em 1rem 0;
|
margin: 0.3rem 1em 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The left-margin breakout: margin boxes ({.margin} / ::: margin blocks,
|
/* Wide single-column pages: margin boxes lean into the vacant left
|
||||||
::: aside, {.margin} figures) leave the text column for the left
|
gutter instead (below 104rem the gutter cannot hold the box, and while
|
||||||
gutter, hugging the article's left edge (a 12rem box on a 13.25rem
|
editing the docked panel reshapes the gutters — in both they stay
|
||||||
pull: the gutter plus main's padding). The sticky sidebar shares the
|
plain floats). */
|
||||||
gutter and slides over them translucently — the same overlap full-bleed
|
|
||||||
.wide images get. Scoped to direct .body children: the backend render
|
|
||||||
keeps breakout blocks out of the column segments, and nested ones keep
|
|
||||||
the in-column float fallback. Room exists once the single-column
|
|
||||||
layout's symmetric gutters fit the box (≥104rem), and on multicol
|
|
||||||
pages whose 1fr left gutter fits it (≥65rem) — but never while editing
|
|
||||||
(the docked panel owns the left edge), and not below 102rem with a
|
|
||||||
sidebar (the fixed 12rem sidebar track, see #content, leaves no free
|
|
||||||
gutter there). */
|
|
||||||
@media (min-width: 104rem) {
|
@media (min-width: 104rem) {
|
||||||
body:not(.editing) .body>.margin,
|
body:not(.editing):not(:has(.multicol)) .body>.margin,
|
||||||
body:not(.editing) .body>.aside,
|
body:not(.editing):not(:has(.multicol)) .body>.aside,
|
||||||
body:not(.editing) .body>figure:has(.margin) {
|
body:not(.editing):not(:has(.multicol)) .body>figure:has(.margin) {
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
width: 12rem;
|
|
||||||
max-width: none;
|
|
||||||
margin: 0.3rem 0 1rem -13.25rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 65rem) {
|
|
||||||
body:not(.editing):has(.multicol):not(:has(#sidebar)) .body>.margin,
|
|
||||||
body:not(.editing):has(.multicol):not(:has(#sidebar)) .body>.aside,
|
|
||||||
body:not(.editing):has(.multicol):not(:has(#sidebar)) .body>figure:has(.margin) {
|
|
||||||
float: left;
|
float: left;
|
||||||
clear: left;
|
clear: left;
|
||||||
width: 12rem;
|
width: 12rem;
|
||||||
@@ -1004,42 +1080,53 @@ pre.wide {
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .wide on multicol pages: the article is not viewport-centered (no right
|
|
||||||
gutter), so the bleed anchors at the left gutter — the 1fr share of the
|
|
||||||
1fr + 4fr grid, i.e. 20vw — plus main's padding, and spans on to the
|
|
||||||
right viewport edge. */
|
|
||||||
body:has(.multicol) figure:has(.wide),
|
|
||||||
body:has(.multicol) pre.wide {
|
|
||||||
margin-inline: calc(-20vw - 1.25rem) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Editing: shrink the bleed to the space right of the docked editor. The
|
/* Editing: shrink the bleed to the space right of the docked editor. The
|
||||||
window keeps its overlay scrollbars while editing, so — unlike a classic
|
window keeps its overlay scrollbars while editing, so — unlike a classic
|
||||||
scrollbar — they take no layout space and the vw math stays exact. */
|
scrollbar — they take no layout space and the vw math stays exact.
|
||||||
|
Multicol pages measure the bleed from main instead (cqw rules below),
|
||||||
|
which insets for the editor automatically. */
|
||||||
body.editing figure:has(.wide),
|
body.editing figure:has(.wide),
|
||||||
body.editing pre.wide {
|
body.editing pre.wide {
|
||||||
width: calc(100vw - var(--editor-w));
|
width: calc(100vw - var(--editor-w));
|
||||||
margin-inline: calc(50% - (100vw - var(--editor-w)) / 2);
|
margin-inline: calc(50% - (100vw - var(--editor-w)) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Editing + multicol: the left gutter is 1/5 of the space right of the
|
/* Narrow single-column pages with a sidebar: below 102rem the symmetric
|
||||||
editor, and the bleed also crosses main's 1.25rem left padding. */
|
gutters can no longer both hold the 12rem sidebar, so #content reserves
|
||||||
body.editing:has(.multicol) figure:has(.wide),
|
it with a fixed left track (see the matching media query below) and the
|
||||||
body.editing:has(.multicol) pre.wide {
|
article always starts at 12rem (+ main's 1.25rem padding) — the bleed
|
||||||
margin-inline: calc((100vw - var(--editor-w)) / -5 - 1.25rem) 0;
|
margin is a plain constant. Scoped by :has(#sidebar) since the sidebar
|
||||||
|
element is omitted entirely on pages without sub-navigation, and
|
||||||
|
excluded while editing, where the editing rules above apply instead.
|
||||||
|
(Multicol pages use the same fixed track below 110rem — their rules
|
||||||
|
are below.) */
|
||||||
|
@media (max-width: 102rem) {
|
||||||
|
body:has(#sidebar):not(.editing):not(:has(.multicol)) figure:has(.wide),
|
||||||
|
body:has(#sidebar):not(.editing):not(:has(.multicol)) pre.wide {
|
||||||
|
margin-inline: -13.25rem 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Narrow windows with a sidebar: below 102rem the symmetric gutters can no
|
/* .wide on multicol pages: the same edge-to-edge bleed, but measured from
|
||||||
longer both hold the 12rem sidebar, so #content reserves it with a fixed
|
main (the container) instead of the viewport — cqw includes the editor
|
||||||
left track (see the matching media query below) and the article always
|
inset automatically, so no editing override is needed. The 2.5rem
|
||||||
starts at 12rem (+ main's 1.25rem padding) — the bleed margin is a plain
|
covers main's side padding. */
|
||||||
constant. Scoped by :has(#sidebar) since the sidebar element is omitted
|
body:has(.multicol) figure:has(.wide),
|
||||||
entirely on pages without sub-navigation, and excluded while editing,
|
body:has(.multicol) div.wide,
|
||||||
where the editing rules above apply instead. */
|
body:has(.multicol) pre.wide {
|
||||||
@media (max-width: 102rem) {
|
width: calc(100cqw + 2.5rem);
|
||||||
body:has(#sidebar):not(.editing) figure:has(.wide),
|
margin-inline: calc(50% - 50cqw - 1.25rem);
|
||||||
body:has(#sidebar):not(.editing) pre.wide {
|
}
|
||||||
margin-inline: -13.25rem 0;
|
|
||||||
|
/* Multicol with a sidebar track (≤110rem, see #content): main starts
|
||||||
|
12rem in, so the bleed extends left past the track to the true viewport
|
||||||
|
edge — sliding under the translucent sidebar. */
|
||||||
|
@media (max-width: 110rem) {
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) figure:has(.wide),
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) div.wide,
|
||||||
|
body:has(#sidebar):has(.multicol):not(.editing) pre.wide {
|
||||||
|
width: calc(100cqw + 14.5rem);
|
||||||
|
margin-inline: calc(50% - 50cqw - 13.25rem) 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,30 +1163,23 @@ article h2 {
|
|||||||
|
|
||||||
/* Narrow windows with a sidebar: below 102rem the symmetric gutters can no
|
/* Narrow windows with a sidebar: below 102rem the symmetric gutters can no
|
||||||
longer both hold the 12rem sidebar, so reserve its space with a fixed
|
longer both hold the 12rem sidebar, so reserve its space with a fixed
|
||||||
left track instead of letting it overlap the article. The article then
|
left track instead of letting it overlap the article (multicol pages use
|
||||||
always starts at 12rem (+ main's 1.25rem padding), which the matching
|
this same track at every width — see the #content rules above). The
|
||||||
.wide breakout rule in the images section relies on. Scoped by
|
article then always starts at 12rem (+ main's 1.25rem padding), which
|
||||||
:has(#sidebar) since the sidebar element is omitted entirely on pages
|
the matching .wide breakout rule in the images section relies on.
|
||||||
without sub-navigation. */
|
Scoped by :has(#sidebar) since the sidebar element is omitted entirely
|
||||||
|
on pages without sub-navigation. */
|
||||||
@media (max-width: 102rem) {
|
@media (max-width: 102rem) {
|
||||||
body:has(#sidebar):not(.editing) #content {
|
body:has(#sidebar):not(.editing) #content {
|
||||||
grid-template-columns: 12rem minmax(0, 78rem) minmax(0, 1fr);
|
grid-template-columns: 12rem minmax(0, 78rem) minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Long articles stay fluid here too: same 12rem reservation for the
|
|
||||||
sidebar, then the article takes everything to the right viewport
|
|
||||||
edge. The article's left edge stays at 12rem either way, so the
|
|
||||||
constant .wide breakout margin remains correct. */
|
|
||||||
body:has(#sidebar):has(.multicol):not(.editing) #content {
|
|
||||||
grid-template-columns: 12rem minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Phones and other narrow viewports: single-column layout with the
|
/* Phones and other narrow viewports: single-column layout with the
|
||||||
sidebar lifted above the article as a wrapping link strip, and no
|
sidebar lifted above the article as a wrapping link strip, and no
|
||||||
floated figures — .left/.right/.margin fall back to plain centered
|
floats at all — .left/.right/.margin figures fall back to plain
|
||||||
figures (explicit img widths still shrink-wrap), while .wide keeps its
|
centered figures (explicit img widths still shrink-wrap), margin boxes
|
||||||
full viewport bleed. */
|
go full width, while .wide keeps its full viewport bleed. */
|
||||||
@media (max-width: 48rem) {
|
@media (max-width: 48rem) {
|
||||||
|
|
||||||
/* Nav type shrinks fluidly as space runs out. The nav font-size is
|
/* Nav type shrinks fluidly as space runs out. The nav font-size is
|
||||||
@@ -1172,17 +1252,27 @@ article h2 {
|
|||||||
margin: 0 auto 1.5rem;
|
margin: 0 auto 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Margin boxes go full width too — no room for side floats on a
|
||||||
|
phone. */
|
||||||
|
.aside,
|
||||||
|
.margin {
|
||||||
|
float: none;
|
||||||
|
width: auto;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* An explicit img width still shrink-wraps the figure (redeclared: this
|
/* An explicit img width still shrink-wraps the figure (redeclared: this
|
||||||
block comes after the desktop rule at equal specificity). */
|
block comes after the desktop rule at equal specificity). */
|
||||||
figure:has(img[width]) {
|
figure:has(img[width]) {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The 102rem sidebar/multicol .wide margins assume a left sidebar
|
/* The single-column sidebar .wide margins assume a left sidebar column;
|
||||||
column; with the sidebar on top the article is viewport-wide and the
|
with the sidebar on top the article is viewport-wide and the plain
|
||||||
plain centered bleed applies again. */
|
centered bleed applies again. (Multicol pages need no override: their
|
||||||
body:has(#sidebar):not(.editing) figure:has(.wide),
|
cqw bleed is exact at any width.) */
|
||||||
body:has(.multicol) figure:has(.wide) {
|
body:has(#sidebar):not(.editing):not(:has(.multicol)) figure:has(.wide) {
|
||||||
margin-inline: calc(50% - 50vw);
|
margin-inline: calc(50% - 50vw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ same callout styling). ``::: name`` opens a generic container rendered
|
|||||||
as ``<div class="name">`` and closed by a matching ``:::`` (nest by
|
as ``<div class="name">`` and closed by a matching ``:::`` (nest by
|
||||||
giving the outer container more colons, e.g. `::::`); the name may be
|
giving the outer container more colons, e.g. `::::`); the name may be
|
||||||
followed by brace attributes (``::: aside {.right}``). ``::: aside``
|
followed by brace attributes (``::: aside {.right}``). ``::: aside``
|
||||||
floats as a muted side box, dropping into the left margin on wide
|
floats as a muted side box, floating in the side zone at the article's
|
||||||
viewports — the same margin breakout ``{.margin}`` (or ``::: margin``)
|
left on all but phone widths — the same margin float ``{.margin}`` (or
|
||||||
gives any block — and ``::: nocols`` opts its section out of the column
|
``::: margin``) gives any block — and ``::: nocols`` opts its section out
|
||||||
layout. A brace-attribute
|
of the column layout. A brace-attribute
|
||||||
line as a block's last line (no blank line between) applies to the whole
|
line as a block's last line (no blank line between) applies to the whole
|
||||||
block, e.g. a paragraph ending with ``{.wide}`` breaks out of the column
|
block, e.g. a paragraph ending with ``{.wide}`` breaks out of the column
|
||||||
layout as a full-width element; written after a block (code fence,
|
layout as a full-width element; written after a block (code fence,
|
||||||
@@ -352,9 +352,9 @@ _PRE_BLOCK_RE = re.compile(r"<pre\b.*?</pre>", re.S)
|
|||||||
_TAG_RE = re.compile(r"<[^>]+>")
|
_TAG_RE = re.compile(r"<[^>]+>")
|
||||||
|
|
||||||
# Classes that take their block out of the column flow: .wide is a
|
# Classes that take their block out of the column flow: .wide is a
|
||||||
# full-width separator, .margin/.aside break into the left margin (their
|
# full-width separator, .margin/.aside float in the side zone at the
|
||||||
# negative-margin breakout only works as a direct .body child, never from
|
# article's left (they must be direct .body children for that — the zone
|
||||||
# inside a column).
|
# rules key off it — never inside a column).
|
||||||
_WIDE = "wide"
|
_WIDE = "wide"
|
||||||
_BREAKOUT = ("margin", "aside")
|
_BREAKOUT = ("margin", "aside")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user