Commit f76749e h
Pat Ambrosio · 26-08-28 11:43
Mark a section heading with a rule above it
Changes: 3 files
+81
-0
Changes (Side-by-Side)
share/jenny/layout/page.sh +29 -0
cat << _EOF_ |
| 844 |
background-color: var(--bg-secondary); |
844 |
background-color: var(--bg-secondary); |
| 845 |
padding: 1em; border-radius: 3px; } |
845 |
padding: 1em; border-radius: 3px; } |
| 846 |
h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } |
846 |
h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } |
| |
847 |
/* Every heading is body size, which keeps the page even but leaves a |
| |
848 |
section heading reading as a stray line of capitals: uppercase alone |
| |
849 |
does not say "new section". A rule above says it structurally rather |
| |
850 |
than typographically, so the type stays exactly as it is. |
| |
851 |
|
| |
852 |
Top two levels only. Deeper headings sit inside a section and would |
| |
853 |
be cut off from it by a line of their own. |
| |
854 |
|
| |
855 |
Every section gets one, including the first: a rule that starts at |
| |
856 |
the second section makes the first look like part of the preamble. |
| |
857 |
The exception is a page whose body repeats its own title as an h1 -- |
| |
858 |
ruling that draws a line immediately under the title in the header. |
| |
859 |
A post has no such repeat, its title having been lifted into the |
| |
860 |
header, so its opening h2 is a real section and is ruled like the |
| |
861 |
rest. Scoped to .contents because the title itself lives in |
| |
862 |
.heading, outside it. */ |
| |
863 |
.contents :is(h1, h2) { |
| |
864 |
border-top: 1px solid var(--border-light); |
| |
865 |
padding-top: 1.5em; |
| |
866 |
} |
| |
867 |
/* The one exception: a page repeats its title as the body's first |
| |
868 |
heading, and an h1 with no heading before it is exactly that repeat. |
| |
869 |
A rule there draws a line immediately under the title in the header. |
| |
870 |
A post has no repeat -- its title is lifted into the header -- so its |
| |
871 |
opening h2 is a real section and keeps its rule. */ |
| |
872 |
.contents h1:not(:is(h1, h2) ~ *) { |
| |
873 |
border-top: 0; |
| |
874 |
padding-top: 0; |
| |
875 |
} |
| 847 |
ol, ul { padding-left: 1em; } |
876 |
ol, ul { padding-left: 1em; } |
| 848 |
/* Notes and references are two different lists, so each is labelled. |
877 |
/* Notes and references are two different lists, so each is labelled. |
| 849 |
The wording lives here rather than in the parser: md2html.awk marks |
878 |
The wording lives here rather than in the parser: md2html.awk marks |
share/jenny/layout/post.sh +29 -0
cat << _EOF_ |
| 665 |
background-color: var(--bg-secondary); |
665 |
background-color: var(--bg-secondary); |
| 666 |
padding: 1em; border-radius: 3px; } |
666 |
padding: 1em; border-radius: 3px; } |
| 667 |
h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } |
667 |
h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; } |
| |
668 |
/* Every heading is body size, which keeps the page even but leaves a |
| |
669 |
section heading reading as a stray line of capitals: uppercase alone |
| |
670 |
does not say "new section". A rule above says it structurally rather |
| |
671 |
than typographically, so the type stays exactly as it is. |
| |
672 |
|
| |
673 |
Top two levels only. Deeper headings sit inside a section and would |
| |
674 |
be cut off from it by a line of their own. |
| |
675 |
|
| |
676 |
Every section gets one, including the first: a rule that starts at |
| |
677 |
the second section makes the first look like part of the preamble. |
| |
678 |
The exception is a page whose body repeats its own title as an h1 -- |
| |
679 |
ruling that draws a line immediately under the title in the header. |
| |
680 |
A post has no such repeat, its title having been lifted into the |
| |
681 |
header, so its opening h2 is a real section and is ruled like the |
| |
682 |
rest. Scoped to .contents because the title itself lives in |
| |
683 |
.heading, outside it. */ |
| |
684 |
.contents :is(h1, h2) { |
| |
685 |
border-top: 1px solid var(--border-light); |
| |
686 |
padding-top: 1.5em; |
| |
687 |
} |
| |
688 |
/* The one exception: a page repeats its title as the body's first |
| |
689 |
heading, and an h1 with no heading before it is exactly that repeat. |
| |
690 |
A rule there draws a line immediately under the title in the header. |
| |
691 |
A post has no repeat -- its title is lifted into the header -- so its |
| |
692 |
opening h2 is a real section and keeps its rule. */ |
| |
693 |
.contents h1:not(:is(h1, h2) ~ *) { |
| |
694 |
border-top: 0; |
| |
695 |
padding-top: 0; |
| |
696 |
} |
| 668 |
ol, ul { padding-left: 1em; } |
697 |
ol, ul { padding-left: 1em; } |
| 669 |
/* Notes and references are two different lists, so each is labelled. |
698 |
/* Notes and references are two different lists, so each is labelled. |
| 670 |
The wording lives here rather than in the parser: md2html.awk marks |
699 |
The wording lives here rather than in the parser: md2html.awk marks |
test/regressions.sh +23 -0
assert_file_contains "$dg/.dist/chart.html" '.diagram { margin-block: 1.5em; ove |
| 1366 |
# 375px phone viewport that the page still does not scroll sideways. |
1366 |
# 375px phone viewport that the page still does not scroll sideways. |
| 1367 |
assert_file_contains "$dg/.dist/chart.html" 'width: auto; min-width: 0; }' |
1367 |
assert_file_contains "$dg/.dist/chart.html" 'width: auto; min-width: 0; }' |
| 1368 |
|
1368 |
|
| |
1369 |
it "a section heading is marked by a rule above it" |
| |
1370 |
# Headings are body size, which leaves a section heading reading as a stray |
| |
1371 |
# line of capitals. A rule says "new section" structurally, so the type is |
| |
1372 |
# untouched. Top two levels only -- a deeper heading sits inside a section and |
| |
1373 |
# a line of its own would cut it off from one. |
| |
1374 |
# Asserted against both layouts, which must not drift. The first version of |
| |
1375 |
# this test checked only the page layout, so mutating the post layout left it |
| |
1376 |
# green -- the mutation looked survivable when it was simply unobserved. |
| |
1377 |
assert_file_contains "$dg/.dist/chart.html" '.contents :is(h1, h2) {' |
| |
1378 |
assert_file_contains "$dg/.dist/post/charted.html" '.contents :is(h1, h2) {' |
| |
1379 |
assert_file_contains "$dg/.dist/chart.html" 'border-top: 1px solid var(--border-light);' |
| |
1380 |
|
| |
1381 |
it "the body's repeat of the page title carries no rule" |
| |
1382 |
# A page repeats its title as the body's opening h1, and a rule there draws a |
| |
1383 |
# line immediately under the title in the header. An h1 with no heading before |
| |
1384 |
# it is exactly that repeat. Two earlier selectors got this wrong: :first-child |
| |
1385 |
# missed it because a p.meta precedes it, and "only rule a heading that follows |
| |
1386 |
# another heading" fixed pages while breaking posts -- a post's title is lifted |
| |
1387 |
# into the header, so its opening h2 lost its rule and the first section read |
| |
1388 |
# as preamble. Verified in Chrome on both layouts. |
| |
1389 |
assert_file_contains "$dg/.dist/chart.html" '.contents h1:not(:is(h1, h2) ~ *) {' |
| |
1390 |
assert_file_contains "$dg/.dist/post/charted.html" '.contents h1:not(:is(h1, h2) ~ *) {' |
| |
1391 |
|
| 1369 |
it "an unrendered diagram degrades to readable source" |
1392 |
it "an unrendered diagram degrades to readable source" |
| 1370 |
# If the script is absent or blocked, the element still holds what the author |
1393 |
# If the script is absent or blocked, the element still holds what the author |
| 1371 |
# wrote. Shown as the code it is rather than as a paragraph with the newlines |
1394 |
# wrote. Shown as the code it is rather than as a paragraph with the newlines |
Raw
commit f76749ea1cc7e8ebf4c4143eea7dbb65d6307ad0
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date: Fri Aug 28 11:43:49 2026 +0800
Mark a section heading with a rule above it
Every heading is body size, which keeps the page even but leaves a
section heading reading as a stray line of capitals: uppercase alone does
not say "new section". A rule says it structurally, so the type is left
exactly as it is -- same size, same weight, same case.
Top two levels only. A deeper heading sits inside a section and a line of
its own would cut it off from one.
The one exception is a page's opening h1, which repeats the title already
shown in the header; a rule there draws a line immediately underneath it.
The selector took three attempts:
- :not(:first-child) does not identify that repeat, because a p.meta
precedes it in the body.
- "rule a heading only when another heading precedes it" fixed the page
layout and quietly broke posts. A post's title is lifted into the
header rather than repeated, so its opening h2 had no heading before
it, lost its rule while its siblings kept theirs, and the first
section read as part of the preamble.
- An h1 with no heading before it is exactly the repeat and nothing
else. A post's opening h2 is a real section and keeps its rule.
Verified in Chrome across all four layouts -- post, page, section index,
home -- and in both themes. The generated Notes and References labels are
::before content rather than headings, so they are untouched.
The tests assert both layouts. The first version checked only the page
layout, which made a mutation of the post layout look survivable when it
was merely unobserved; that is the same shape of mistake as measuring a
mutation against a stale backup. Removing the rule from either layout now
fails, as does removing the title-repeat exemption.
diff --git a/share/jenny/layout/page.sh b/share/jenny/layout/page.sh
index 33c6b07..ceabb79 100755
--- a/share/jenny/layout/page.sh
+++ b/share/jenny/layout/page.sh
@@ -844,6 +844,35 @@ cat << _EOF_
background-color: var(--bg-secondary);
padding: 1em; border-radius: 3px; }
h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
+ /* Every heading is body size, which keeps the page even but leaves a
+ section heading reading as a stray line of capitals: uppercase alone
+ does not say "new section". A rule above says it structurally rather
+ than typographically, so the type stays exactly as it is.
+
+ Top two levels only. Deeper headings sit inside a section and would
+ be cut off from it by a line of their own.
+
+ Every section gets one, including the first: a rule that starts at
+ the second section makes the first look like part of the preamble.
+ The exception is a page whose body repeats its own title as an h1 --
+ ruling that draws a line immediately under the title in the header.
+ A post has no such repeat, its title having been lifted into the
+ header, so its opening h2 is a real section and is ruled like the
+ rest. Scoped to .contents because the title itself lives in
+ .heading, outside it. */
+ .contents :is(h1, h2) {
+ border-top: 1px solid var(--border-light);
+ padding-top: 1.5em;
+ }
+ /* The one exception: a page repeats its title as the body's first
+ heading, and an h1 with no heading before it is exactly that repeat.
+ A rule there draws a line immediately under the title in the header.
+ A post has no repeat -- its title is lifted into the header -- so its
+ opening h2 is a real section and keeps its rule. */
+ .contents h1:not(:is(h1, h2) ~ *) {
+ border-top: 0;
+ padding-top: 0;
+ }
ol, ul { padding-left: 1em; }
/* Notes and references are two different lists, so each is labelled.
The wording lives here rather than in the parser: md2html.awk marks
diff --git a/share/jenny/layout/post.sh b/share/jenny/layout/post.sh
index a5bf37d..6d258b3 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -665,6 +665,35 @@ cat << _EOF_
background-color: var(--bg-secondary);
padding: 1em; border-radius: 3px; }
h1, h2, h3, h4, h5, h6 { font-size: 1em; font-weight: normal; text-transform: uppercase; margin: 2em 0 1em; }
+ /* Every heading is body size, which keeps the page even but leaves a
+ section heading reading as a stray line of capitals: uppercase alone
+ does not say "new section". A rule above says it structurally rather
+ than typographically, so the type stays exactly as it is.
+
+ Top two levels only. Deeper headings sit inside a section and would
+ be cut off from it by a line of their own.
+
+ Every section gets one, including the first: a rule that starts at
+ the second section makes the first look like part of the preamble.
+ The exception is a page whose body repeats its own title as an h1 --
+ ruling that draws a line immediately under the title in the header.
+ A post has no such repeat, its title having been lifted into the
+ header, so its opening h2 is a real section and is ruled like the
+ rest. Scoped to .contents because the title itself lives in
+ .heading, outside it. */
+ .contents :is(h1, h2) {
+ border-top: 1px solid var(--border-light);
+ padding-top: 1.5em;
+ }
+ /* The one exception: a page repeats its title as the body's first
+ heading, and an h1 with no heading before it is exactly that repeat.
+ A rule there draws a line immediately under the title in the header.
+ A post has no repeat -- its title is lifted into the header -- so its
+ opening h2 is a real section and keeps its rule. */
+ .contents h1:not(:is(h1, h2) ~ *) {
+ border-top: 0;
+ padding-top: 0;
+ }
ol, ul { padding-left: 1em; }
/* Notes and references are two different lists, so each is labelled.
The wording lives here rather than in the parser: md2html.awk marks
diff --git a/test/regressions.sh b/test/regressions.sh
index 1fe88b4..f90ead3 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -1366,6 +1366,29 @@ assert_file_contains "$dg/.dist/chart.html" '.diagram { margin-block: 1.5em; ove
# 375px phone viewport that the page still does not scroll sideways.
assert_file_contains "$dg/.dist/chart.html" 'width: auto; min-width: 0; }'
+it "a section heading is marked by a rule above it"
+# Headings are body size, which leaves a section heading reading as a stray
+# line of capitals. A rule says "new section" structurally, so the type is
+# untouched. Top two levels only -- a deeper heading sits inside a section and
+# a line of its own would cut it off from one.
+# Asserted against both layouts, which must not drift. The first version of
+# this test checked only the page layout, so mutating the post layout left it
+# green -- the mutation looked survivable when it was simply unobserved.
+assert_file_contains "$dg/.dist/chart.html" '.contents :is(h1, h2) {'
+assert_file_contains "$dg/.dist/post/charted.html" '.contents :is(h1, h2) {'
+assert_file_contains "$dg/.dist/chart.html" 'border-top: 1px solid var(--border-light);'
+
+it "the body's repeat of the page title carries no rule"
+# A page repeats its title as the body's opening h1, and a rule there draws a
+# line immediately under the title in the header. An h1 with no heading before
+# it is exactly that repeat. Two earlier selectors got this wrong: :first-child
+# missed it because a p.meta precedes it, and "only rule a heading that follows
+# another heading" fixed pages while breaking posts -- a post's title is lifted
+# into the header, so its opening h2 lost its rule and the first section read
+# as preamble. Verified in Chrome on both layouts.
+assert_file_contains "$dg/.dist/chart.html" '.contents h1:not(:is(h1, h2) ~ *) {'
+assert_file_contains "$dg/.dist/post/charted.html" '.contents h1:not(:is(h1, h2) ~ *) {'
+
it "an unrendered diagram degrades to readable source"
# If the script is absent or blocked, the element still holds what the author
# wrote. Shown as the code it is rather than as a paragraph with the newlines