Commit d592093 h
Changes: 3 files +86 -100
Changes (Side-by-Side)
share/jenny/layout/page.sh +17 -54
cat << _EOF_ | |||
| 376 | relation to the 109px the pair actually spans. */ |
376 | relation to the 109px the pair actually spans. */ |
| 377 | /* No margin-bottom: post.sh's .heading has none, and the extra 1em put 48px |
377 | /* No margin-bottom: post.sh's .heading has none, and the extra 1em put 48px |
| 378 | between a page title and its body where a post has none. */ |
378 | between a page title and its body where a post has none. */ |
| 379 | /* On a post the stamp holds the left column and the title starts where |
||
| 380 | the body does. A page has no stamp, so without a spacer here the |
||
| 381 | title sat at the far left while its own body was indented past it. |
||
| 382 | The heading reserves the same column the body does, by the same |
||
| 383 | mechanism, so the two line up whatever the width. */ |
||
| 384 | /* Beside a sidebar the whole cell is already offset, so the heading |
||
| 385 | must not indent again -- the title would land a column right of its |
||
| 386 | own body. */ |
||
| 387 | .page-wrapper:has(.page-nav) .heading-page .home-spacer { display: none; } |
||
| 388 | .home { display: inline-block; text-align: right; margin-right: 1.5em; |
||
| 389 | width: 5.4em; margin-bottom: 1.5em; text-decoration: none; |
||
| 390 | color: var(--link-home); } |
||
| 391 | .home:hover { color: var(--link-home-hover); } |
||
| 392 | |
379 | |
| 393 | /* Breadcrumbs, marked the same way an index or tag page marks where |
380 | /* Breadcrumbs, marked the same way an index or tag page marks where |
| 394 | you are: a rule under the current segment rather than an |
381 | you are: a rule under the current segment rather than an |
cat << _EOF_ | |||
| 419 | the spacer beside it. */ |
406 | the spacer beside it. */ |
| 420 | .heading-page .title { display: inline-block; margin: 0 0 1em; |
407 | .heading-page .title { display: inline-block; margin: 0 0 1em; |
| 421 | vertical-align: top; max-width: 100%; } |
408 | vertical-align: top; max-width: 100%; } |
| 422 | .heading-page .home-spacer { margin-bottom: 0; } |
||
| 423 | .heading .stamp, |
||
| 424 | .home { display: inline-block; text-align:right; margin-right: 1.5em; } |
||
| 425 | .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left; color: var(--link-home); } .home:hover { color: var(--link-home-hover); } |
||
| 409 | .heading .stamp { display: inline-block; text-align: right; margin-right: 1.5em; } |
||
| 410 | /* Identical to the post layout's: the way back sits after the text, so |
||
| 411 | posts and pages end the same way. Held in a column before the body |
||
| 412 | it wrapped onto its own line on a phone, leaving a stray arrow |
||
| 413 | between the title and the first paragraph. */ |
||
| 414 | .home { display: block; margin-top: 2.5em; text-decoration: none; |
||
| 415 | color: var(--link-home); } |
||
| 416 | .home:hover { color: var(--link-home-hover); } |
||
| 426 | .heading .stamp { color: var(--text-muted); } |
417 | .heading .stamp { color: var(--text-muted); } |
| 427 | /* A nav is an ordered list of names and places; what it means is |
418 | /* A nav is an ordered list of names and places; what it means is |
| 428 | left to this template. Rendered as a left sidebar beside the body. */ |
419 | left to this template. Rendered as a left sidebar beside the body. */ |
cat << _EOF_ | |||
| 489 | .page-wrapper { display: table; table-layout: auto; width: auto; } |
480 | .page-wrapper { display: table; table-layout: auto; width: auto; } |
| 490 | .contents-cell { display: table-cell; vertical-align: top; |
481 | .contents-cell { display: table-cell; vertical-align: top; |
| 491 | padding-left: var(--page-nav-gap); } |
482 | padding-left: var(--page-nav-gap); } |
| 492 | /* With no nav, the body has to line up with a post's body, and a post |
||
| 493 | gets its indent from the back arrow sitting inline before the |
||
| 494 | contents -- not from a fixed length. That is why the indent is a |
||
| 495 | spacer of the arrow's exact size rather than a copied number: the |
||
| 496 | two are inline-blocks, so they sit side by side while they fit and |
||
| 497 | the body wraps under the spacer when the screen is too narrow, |
||
| 498 | reflowing at exactly the width a post reflows at. A hardcoded |
||
| 499 | padding could match at one width but broke at every other -- at |
||
| 500 | 735px it put the page body 97px left of the post's. Pages have no |
||
| 501 | arrow (it means nothing on a page), so the spacer is empty and |
||
| 502 | hidden from assistive tech. */ |
||
| 483 | /* With no nav there is no sidebar to sit beside, so the body starts at |
||
| 484 | the left margin -- the same place a post's body starts now that the |
||
| 485 | way back is below the text rather than beside it. */ |
||
| 503 | .page-wrapper:not(:has(.page-nav)) .contents-cell { display: block; |
486 | .page-wrapper:not(:has(.page-nav)) .contents-cell { display: block; |
| 504 | padding-left: 0; } |
487 | padding-left: 0; } |
| 505 | .home-spacer { display: inline-block; width: 5.4em; margin-right: 1.5em; |
||
| 506 | margin-bottom: 1.5em; vertical-align: top; } |
||
| 507 | .page-wrapper:has(.page-nav) .home-spacer { display: none; } |
||
| 508 | .contents { display: inline-block; max-width: 60ch; vertical-align: top; width: 100%; } |
488 | .contents { display: inline-block; max-width: 60ch; vertical-align: top; width: 100%; } |
| 509 | |
489 | |
| 510 | /* Narrow: the sidebar has nowhere to go, so it stacks above the body |
490 | /* Narrow: the sidebar has nowhere to go, so it stacks above the body |
cat << _EOF_ | |||
| 516 | .page-nav, |
496 | .page-nav, |
| 517 | .contents-cell { display: block; } |
497 | .contents-cell { display: block; } |
| 518 | .page-nav { width: auto; min-width: 0; margin-bottom: 2em; } |
498 | .page-nav { width: auto; min-width: 0; margin-bottom: 2em; } |
| 519 | /* No indent at this width: 6.82rem plus a 60ch column overflows a |
||
| 520 | phone and forces horizontal scroll. */ |
||
| 499 | /* The sidebar's gap goes with the sidebar: stacked above the body, |
||
| 500 | it is not a column to clear. Left in place it indented the body on |
||
| 501 | a phone and forced horizontal scroll. */ |
||
| 521 | .contents-cell { padding-left: 0; } |
502 | .contents-cell { padding-left: 0; } |
| 522 | /* No spacer at this width, in the heading or the body. The indent |
||
| 523 | exists to reserve a post's back-arrow column; on a phone it costs |
||
| 524 | more than it reserves -- at 375px it takes the measure from 42 |
||
| 525 | characters to 32, and at 320px down to 25. |
||
| 526 | |
||
| 527 | The body already wraps under its spacer here, because a 60ch |
||
| 528 | column cannot sit beside it. The heading did not: a two-word |
||
| 529 | title fits beside the spacer at any width, so the title stayed |
||
| 530 | indented 97px above a body that had returned to the left margin. |
||
| 531 | That is the artifact -- a title hanging to the right of its own |
||
| 532 | text on a phone. |
||
| 533 | |
||
| 534 | Removing both is what keeps them together. Matching the body's |
||
| 535 | content-driven turnover instead was tried and does not work: the |
||
| 536 | heading and the body hold different content, so they give out at |
||
| 537 | different widths, and tuning the title's column to compensate only |
||
| 538 | moved the disagreement (a 10px window at 614-622px became a 90px |
||
| 539 | one at 528-612px). */ |
||
| 540 | .home-spacer { display: none; } |
||
| 541 | } |
503 | } |
| 542 | .contents :first-child { margin-top: 0; } |
504 | .contents :first-child { margin-top: 0; } |
| 543 | /* A diagram is a figure, not a paragraph: it takes the column's width |
505 | /* A diagram is a figure, not a paragraph: it takes the column's width |
cat << _EOF_ | |||
| 649 | <div class="page-wrapper"> |
611 | <div class="page-wrapper"> |
| 650 | $(page_nav) |
612 | $(page_nav) |
| 651 | <div class="contents-cell"> |
613 | <div class="contents-cell"> |
| 652 | <div class="heading heading-page"><span class="home-spacer" aria-hidden="true"></span><h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></div> |
||
| 653 | <span class="home-spacer" aria-hidden="true"></span><div class="contents"> |
||
| 614 | <div class="heading heading-page"><h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></div> |
||
| 615 | <div class="contents"> |
||
| 654 | $(echo "${POST_CONTENTS:-}") |
616 | $(echo "${POST_CONTENTS:-}") |
| 655 | <div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div> |
617 | <div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div> |
| 618 | <a href="$URL_PREFIX/" class="home">←︎ $(html_escape "${BLOG_TITLE:-}")</a> |
||
| 656 | </div> |
619 | </div> |
| 657 | </div> |
620 | </div> |
| 658 | </div> |
621 | </div> |
share/jenny/layout/post.sh +17 -3
cat << _EOF_ | |||
| 250 | .heading .title { color: var(--text-heading); display: table-cell; margin: 0 0 1em; font-weight:bold; max-width: 500px; } |
250 | .heading .title { color: var(--text-heading); display: table-cell; margin: 0 0 1em; font-weight:bold; max-width: 500px; } |
| 251 | .heading .title:hover { text-decoration: underline; } |
251 | .heading .title:hover { text-decoration: underline; } |
| 252 | .heading .stamp, |
252 | .heading .stamp, |
| 253 | .home { display: inline-block; text-align:right; margin-right: 1.5em; } |
||
| 254 | .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left; color: var(--link-home); } .home:hover { color: var(--link-home-hover); } |
||
| 253 | /* The way back sits after the text, where a reader finishes, rather |
||
| 254 | than beside it. Held in a fixed column before the body it wrapped |
||
| 255 | onto its own line once the body no longer fit next to it, leaving a |
||
| 256 | stray arrow floating between the title and the first paragraph on a |
||
| 257 | phone -- and on wider screens it reserved a column the body then had |
||
| 258 | to be indented past. Below the text it needs no column at either |
||
| 259 | width, so the body starts at the left margin everywhere. |
||
| 260 | |
||
| 261 | It is a second route home: the site title in the header links there |
||
| 262 | from every page. Kept because the end of a long read is exactly |
||
| 263 | where that is useful, and named rather than left as a bare glyph so |
||
| 264 | it says where it goes. */ |
||
| 265 | .home { display: block; margin-top: 2.5em; text-decoration: none; |
||
| 266 | color: var(--link-home); } |
||
| 267 | .home:hover { color: var(--link-home-hover); } |
||
| 255 | .heading .stamp { color: var(--text-muted); } |
268 | .heading .stamp { color: var(--text-muted); } |
| 256 | .contents { display: inline-block; max-width: 60ch; vertical-align: top; width: 100%; } |
269 | .contents { display: inline-block; max-width: 60ch; vertical-align: top; width: 100%; } |
| 257 | .contents :first-child { margin-top: 0; } |
270 | .contents :first-child { margin-top: 0; } |
cat << _EOF_ | |||
| 361 | <header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header> |
374 | <header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header> |
| 362 | <article> |
375 | <article> |
| 363 | <div class="heading$([ -n "${IS_PAGE:-}" ] && echo ' heading-page')"><a href="${POST_URL:-}">$([ -z "${IS_PAGE:-}" ] && printf '<span class="stamp">%s</span>' "$("$DATE" -d "${POST_DATE_RFC822:-}" +%m/%d/%Y 2>/dev/null || printf '%s' "${POST_DATE:-}")")<h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></a></div> |
376 | <div class="heading$([ -n "${IS_PAGE:-}" ] && echo ' heading-page')"><a href="${POST_URL:-}">$([ -z "${IS_PAGE:-}" ] && printf '<span class="stamp">%s</span>' "$("$DATE" -d "${POST_DATE_RFC822:-}" +%m/%d/%Y 2>/dev/null || printf '%s' "${POST_DATE:-}")")<h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></a></div> |
| 364 | <a href="$URL_PREFIX/" class="home">←︎</a><div class="contents"> |
||
| 377 | <div class="contents"> |
||
| 365 | $(echo "${POST_CONTENTS:-}") |
378 | $(echo "${POST_CONTENTS:-}") |
| 366 | <div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div> |
379 | <div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div> |
| 380 | <a href="$URL_PREFIX/" class="home">←︎ $(html_escape "${BLOG_TITLE:-}")</a> |
||
| 367 | </div> |
381 | </div> |
| 368 | </article> |
382 | </article> |
| 369 | $(diagram_script) |
383 | $(diagram_script) |
test/regressions.sh +52 -43
assert_file_exists "$nop/.dist/fallback.html" | |||
| 1313 | it "and does so without complaint" |
1313 | it "and does so without complaint" |
| 1314 | assert_eq "$stderr_output" "" |
1314 | assert_eq "$stderr_output" "" |
| 1315 | |
1315 | |
| 1316 | it "a page has no back arrow" |
||
| 1317 | # A post's arrow goes back to the index it was listed on. A page is not listed |
||
| 1318 | # anywhere -- it is reached by its own stable path -- so an arrow to the index |
||
| 1319 | # points at a place the reader did not come from. The breadcrumb in the header |
||
| 1320 | # is what a page navigates by instead. |
||
| 1321 | assert_file_not_contains "$pg/.dist/study/consciousness.html" 'class="home"' |
||
| 1322 | |
||
| 1323 | it "the arrow's column is still reserved so the body lines up with a post's" |
||
| 1324 | # Removing the arrow must not move the body: a post indents by the arrow's |
||
| 1325 | # width, and a page has to match it. |
||
| 1326 | assert_file_contains "$pg/.dist/study/consciousness.html" 'class="home-spacer"' |
||
| 1316 | it "a page ends with the way back, like a post" |
||
| 1317 | # Held in a column before the body the arrow wrapped onto its own line once |
||
| 1318 | # the body no longer fit beside it, leaving a stray glyph between the title and |
||
| 1319 | # the first paragraph on a phone. Below the text it needs no column at any |
||
| 1320 | # width, and posts and pages end the same way. |
||
| 1321 | assert_file_contains "$pg/.dist/study/consciousness.html" 'class="home"' |
||
| 1322 | |
||
| 1323 | it "the way back comes after the body, not before it" |
||
| 1324 | tags_line=$("$GREP" -n 'class="tags"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1) |
||
| 1325 | home_line=$("$GREP" -n 'class="home"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1) |
||
| 1326 | [ -n "$home_line" ] && [ "$home_line" -gt "$tags_line" ] \ |
||
| 1327 | && pass "back link follows the content" \ |
||
| 1328 | || fail "back link at line $home_line must come after the body at line $tags_line" |
||
| 1327 | |
1329 | |
| 1328 | it "a blog with only pages and no posts still builds" |
1330 | it "a blog with only pages and no posts still builds" |
| 1329 | only="$TMPROOT/onlypages" |
1331 | only="$TMPROOT/onlypages" |
assert_file_not_contains "$nav_sb/.dist/test.html" '.page-nav { position: absolu | |||
| 1685 | it "a page with no nav takes the body indent, not the sidebar column" |
1687 | it "a page with no nav takes the body indent, not the sidebar column" |
| 1686 | assert_file_contains "$nav_sb/.dist/plain.html" '.page-wrapper:not(:has(.page-nav))' |
1688 | assert_file_contains "$nav_sb/.dist/plain.html" '.page-wrapper:not(:has(.page-nav))' |
| 1687 | |
1689 | |
| 1688 | it "a page with no nav reserves the arrow column the way a post does" |
||
| 1689 | # A post's indent comes from the back arrow sitting inline before the body, so |
||
| 1690 | # the two reflow together when the screen narrows. A page has no arrow, so it |
||
| 1691 | # reserves the same column with a spacer of the same size; a hardcoded padding |
||
| 1692 | # matched at one width and was 97px out at 735px. |
||
| 1693 | # Anchored to the body, not just "a spacer exists somewhere": the heading has |
||
| 1694 | # one too, and a check that matched either passed with the body's spacer |
||
| 1695 | # deleted -- which drops the body indent from 110px to 0 against a post's 110. |
||
| 1696 | assert_file_contains "$nav_sb/.dist/plain.html" '<span class="home-spacer" aria-hidden="true"></span><div class="contents">' |
||
| 1697 | assert_file_contains "$nav_sb/.dist/plain.html" '.home-spacer { display: inline-block' |
||
| 1698 | |
||
| 1699 | it "the spacer is hidden from assistive technology" |
||
| 1700 | assert_file_contains "$nav_sb/.dist/plain.html" 'class="home-spacer" aria-hidden="true"' |
||
| 1701 | |
||
| 1702 | it "a page with a sidebar drops the arrow spacer" |
||
| 1703 | assert_file_contains "$nav_sb/.dist/test.html" '.page-wrapper:has(.page-nav) .home-spacer { display: none' |
||
| 1690 | it "the body starts at the left margin, with no column reserved" |
||
| 1691 | # The indent existed only to make room for the back arrow beside the body. |
||
| 1692 | # With the arrow below the text there is nothing to clear, so the text uses the |
||
| 1693 | # full measure -- on a 375px phone the reserved column cost a quarter of the |
||
| 1694 | # line, 42 characters down to 32. |
||
| 1695 | assert_file_not_contains "$nav_sb/.dist/plain.html" 'home-spacer' |
||
| 1696 | assert_file_contains "$nav_sb/.dist/plain.html" '.home { display: block; margin-top: 2.5em;' |
||
| 1704 | |
1697 | |
| 1705 | it "a sidebar widens the article rather than narrowing the body" |
1698 | it "a sidebar widens the article rather than narrowing the body" |
| 1706 | # 74ch is sized for a post: arrow column plus a 60ch body. Letting the sidebar |
1699 | # 74ch is sized for a post: arrow column plus a 60ch body. Letting the sidebar |
post_file=$(find "$nav_sb/.dist/post" -name '*.html' | head -1) | |||
| 1719 | post_contents=$("$GREP" -o '^ \.contents { [^}]*}' "$post_file" | head -1) |
1712 | post_contents=$("$GREP" -o '^ \.contents { [^}]*}' "$post_file" | head -1) |
| 1720 | assert_eq "$page_contents" "$post_contents" |
1713 | assert_eq "$page_contents" "$post_contents" |
| 1721 | |
1714 | |
| 1722 | it "the heading reserves the same column the body does" |
||
| 1723 | # On a post the stamp holds the left column, so the title starts where the |
||
| 1724 | # body starts. A page has no stamp; without the same spacer in the heading the |
||
| 1725 | # title sat at the far left while its own body was indented past it. |
||
| 1726 | assert_file_contains "$nav_sb/.dist/plain.html" 'class="heading heading-page"><span class="home-spacer"' |
||
| 1715 | it "the heading holds no reserved column either" |
||
| 1716 | # Nothing sits beside the title any more, so it starts where its body starts |
||
| 1717 | # without a spacer to keep in step. |
||
| 1718 | assert_file_contains "$nav_sb/.dist/plain.html" 'class="heading heading-page"><h1 class="title">' |
||
| 1727 | |
1719 | |
| 1728 | it "the heading has no breakpoint of its own" |
1720 | it "the heading has no breakpoint of its own" |
| 1729 | # A post has no media query at all -- heading and body collapse together by |
1721 | # A post has no media query at all -- heading and body collapse together by |
it "the heading has no breakpoint of its own" | |||
| 1733 | narrow_head=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$nav_sb/.dist/plain.html") |
1725 | narrow_head=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$nav_sb/.dist/plain.html") |
| 1734 | assert_not_contains "$narrow_head" '.heading-page { margin-left: 0; display: block' |
1726 | assert_not_contains "$narrow_head" '.heading-page { margin-left: 0; display: block' |
| 1735 | |
1727 | |
| 1736 | it "a sidebar page does not indent its heading twice" |
||
| 1737 | # The cell is already offset by the nav column; indenting the heading again |
||
| 1738 | # would put the title a column right of its own body. |
||
| 1739 | assert_file_contains "$nav_sb/.dist/test.html" '.page-wrapper:has(.page-nav) .heading-page .home-spacer { display: none' |
||
| 1728 | it "a post ends with the way back" |
||
| 1729 | # The post side was unasserted: deleting the link from post.sh entirely broke |
||
| 1730 | # no test, because every other check here reads a page. |
||
| 1731 | post_file_bk=$(find "$nav_sb/.dist/post" -name '*.html' | head -1) |
||
| 1732 | assert_file_contains "$post_file_bk" 'class="home">' |
||
| 1733 | |
||
| 1734 | it "a post's back link comes after its body" |
||
| 1735 | tl=$("$GREP" -n 'class="tags"' "$post_file_bk" | head -1 | cut -d: -f1) |
||
| 1736 | hl=$("$GREP" -n 'class="home"' "$post_file_bk" | head -1 | cut -d: -f1) |
||
| 1737 | [ -n "$hl" ] && [ "$hl" -gt "$tl" ] \ |
||
| 1738 | && pass "post back link follows the content" \ |
||
| 1739 | || fail "post back link at line $hl must come after the body at line $tl" |
||
| 1740 | |
||
| 1741 | it "the back link names where it goes" |
||
| 1742 | # A bare arrow says only "away". Both layouts render the site title beside it. |
||
| 1743 | assert_file_contains "$post_file_bk" '←︎ ' |
||
| 1744 | |
||
| 1745 | it "posts and pages declare the same back link" |
||
| 1746 | # The whole point of moving it: both layouts end the same way. Compare the |
||
| 1747 | # declarations rather than trusting they look alike. |
||
| 1748 | page_home=$("$GREP" -o '\.home { display: block[^}]*}' "$nav_sb/.dist/plain.html" | head -1) |
||
| 1749 | post_home=$("$GREP" -o '\.home { display: block[^}]*}' "$(find "$nav_sb/.dist/post" -name '*.html' | head -1)" | head -1) |
||
| 1750 | assert_eq "$page_home" "$post_home" |
||
| 1740 | |
1751 | |
| 1741 | it "narrow screens collapse the whole table, not just one cell" |
1752 | it "narrow screens collapse the whole table, not just one cell" |
| 1742 | # A lone cell reverting to block still gets an anonymous table wrapper and |
1753 | # A lone cell reverting to block still gets an anonymous table wrapper and |
it "a page title keeps a gap above its body" | |||
| 1790 | # paragraph, 0px where a post has 1em. |
1801 | # paragraph, 0px where a post has 1em. |
| 1791 | assert_file_contains "$ts/.dist/plain.html" '.heading-page .title { display: inline-block; margin: 0 0 1em;' |
1802 | assert_file_contains "$ts/.dist/plain.html" '.heading-page .title { display: inline-block; margin: 0 0 1em;' |
| 1792 | |
1803 | |
| 1793 | it "the arrow column is dropped on narrow screens" |
||
| 1794 | # The indent reserves a post's back-arrow column. On a phone it costs more |
||
| 1795 | # than it reserves -- at 375px it takes the measure from 42 characters to 32 -- |
||
| 1796 | # and the body already wraps to the left margin there while the heading, whose |
||
| 1797 | # title always fits beside the spacer, did not. The title was left hanging |
||
| 1798 | # 97px right of its own text. |
||
| 1804 | it "narrow screens need no rule for the arrow column" |
||
| 1805 | # There is no column to drop: the back link sits below the text at every |
||
| 1806 | # width, so the narrow block only has to collapse the sidebar. |
||
| 1799 | narrow_sp=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$ts/.dist/plain.html") |
1807 | narrow_sp=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$ts/.dist/plain.html") |
| 1800 | assert_contains "$narrow_sp" '.home-spacer { display: none; }' |
||
| 1808 | assert_not_contains "$narrow_sp" 'home-spacer' |
||
| 1809 | assert_contains "$narrow_sp" '.contents-cell { padding-left: 0; }' |
||
| 1801 | |
1810 | |
| 1802 | it "the heading is not a flex row" |
1811 | it "the heading is not a flex row" |
| 1803 | # Flex items do not wrap, which is why the heading held its column at widths |
1812 | # Flex items do not wrap, which is why the heading held its column at widths |
Raw
commit d59209365b84801839410842ae52a6c7d0aabaa3
Author: Pat Ambrosio <cp.ambrosio@gmail.com>
Date: Wed Aug 26 09:06:08 2026 +0800
Move the way back below the text, in posts and pages alike
The back arrow sat in a fixed column before the body. Once the body no
longer fit beside it -- below about 736px -- it wrapped onto its own line,
leaving a stray arrow floating between the title and the first paragraph
on a phone. Above that width it was fine, but it cost a column the body
then had to be indented past.
Below the text it needs no column at either width, so the body starts at
the left margin everywhere and the title, the text and the link share one
left edge. On a 375px phone the reserved column had been taking the
measure from 42 characters to 32, and at 320px down to 25.
It is a second route home -- the site title in the header links there from
every page -- and it stays because the end of a long read is exactly where
that is useful. It now names its destination rather than rendering as a
bare glyph, which said only "away".
Pages get the same ending, which is most of the deletion here. They had no
arrow but carried a spacer of the arrow's exact width in both the heading
and the body, purely to keep their left edge matching a post's, plus rules
to suppress it beside a sidebar and again on narrow screens. With nothing
to reserve a column for, all of it goes: two spacer elements, four CSS
rules, and a media-query clause.
Tests: the spacer suite is replaced by one asserting where the link sits
relative to the body, that both layouts declare it identically, and that
no reserved column survives at any width. Verified by measuring at
320-1440px -- title, body and link share a left edge on posts, plain pages
and sidebar pages alike, with no horizontal overflow.
Checked by mutation. The first version missed one: deleting the link from
post.sh entirely broke nothing, because every assertion read a page. The
post side is now asserted directly and that mutation fails three tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnSAstgnDYdKPwLGv1YZE5
diff --git a/share/jenny/layout/page.sh b/share/jenny/layout/page.sh
index fb0ebf1..663dabb 100755
--- a/share/jenny/layout/page.sh
+++ b/share/jenny/layout/page.sh
@@ -376,19 +376,6 @@ cat << _EOF_
relation to the 109px the pair actually spans. */
/* No margin-bottom: post.sh's .heading has none, and the extra 1em put 48px
between a page title and its body where a post has none. */
- /* On a post the stamp holds the left column and the title starts where
- the body does. A page has no stamp, so without a spacer here the
- title sat at the far left while its own body was indented past it.
- The heading reserves the same column the body does, by the same
- mechanism, so the two line up whatever the width. */
- /* Beside a sidebar the whole cell is already offset, so the heading
- must not indent again -- the title would land a column right of its
- own body. */
- .page-wrapper:has(.page-nav) .heading-page .home-spacer { display: none; }
- .home { display: inline-block; text-align: right; margin-right: 1.5em;
- width: 5.4em; margin-bottom: 1.5em; text-decoration: none;
- color: var(--link-home); }
- .home:hover { color: var(--link-home-hover); }
/* Breadcrumbs, marked the same way an index or tag page marks where
you are: a rule under the current segment rather than an
@@ -419,10 +406,14 @@ cat << _EOF_
the spacer beside it. */
.heading-page .title { display: inline-block; margin: 0 0 1em;
vertical-align: top; max-width: 100%; }
- .heading-page .home-spacer { margin-bottom: 0; }
- .heading .stamp,
- .home { display: inline-block; text-align:right; margin-right: 1.5em; }
- .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left; color: var(--link-home); } .home:hover { color: var(--link-home-hover); }
+ .heading .stamp { display: inline-block; text-align: right; margin-right: 1.5em; }
+ /* Identical to the post layout's: the way back sits after the text, so
+ posts and pages end the same way. Held in a column before the body
+ it wrapped onto its own line on a phone, leaving a stray arrow
+ between the title and the first paragraph. */
+ .home { display: block; margin-top: 2.5em; text-decoration: none;
+ color: var(--link-home); }
+ .home:hover { color: var(--link-home-hover); }
.heading .stamp { color: var(--text-muted); }
/* A nav is an ordered list of names and places; what it means is
left to this template. Rendered as a left sidebar beside the body. */
@@ -489,22 +480,11 @@ cat << _EOF_
.page-wrapper { display: table; table-layout: auto; width: auto; }
.contents-cell { display: table-cell; vertical-align: top;
padding-left: var(--page-nav-gap); }
- /* With no nav, the body has to line up with a post's body, and a post
- gets its indent from the back arrow sitting inline before the
- contents -- not from a fixed length. That is why the indent is a
- spacer of the arrow's exact size rather than a copied number: the
- two are inline-blocks, so they sit side by side while they fit and
- the body wraps under the spacer when the screen is too narrow,
- reflowing at exactly the width a post reflows at. A hardcoded
- padding could match at one width but broke at every other -- at
- 735px it put the page body 97px left of the post's. Pages have no
- arrow (it means nothing on a page), so the spacer is empty and
- hidden from assistive tech. */
+ /* With no nav there is no sidebar to sit beside, so the body starts at
+ the left margin -- the same place a post's body starts now that the
+ way back is below the text rather than beside it. */
.page-wrapper:not(:has(.page-nav)) .contents-cell { display: block;
padding-left: 0; }
- .home-spacer { display: inline-block; width: 5.4em; margin-right: 1.5em;
- margin-bottom: 1.5em; vertical-align: top; }
- .page-wrapper:has(.page-nav) .home-spacer { display: none; }
.contents { display: inline-block; max-width: 60ch; vertical-align: top; width: 100%; }
/* Narrow: the sidebar has nowhere to go, so it stacks above the body
@@ -516,28 +496,10 @@ cat << _EOF_
.page-nav,
.contents-cell { display: block; }
.page-nav { width: auto; min-width: 0; margin-bottom: 2em; }
- /* No indent at this width: 6.82rem plus a 60ch column overflows a
- phone and forces horizontal scroll. */
+ /* The sidebar's gap goes with the sidebar: stacked above the body,
+ it is not a column to clear. Left in place it indented the body on
+ a phone and forced horizontal scroll. */
.contents-cell { padding-left: 0; }
- /* No spacer at this width, in the heading or the body. The indent
- exists to reserve a post's back-arrow column; on a phone it costs
- more than it reserves -- at 375px it takes the measure from 42
- characters to 32, and at 320px down to 25.
-
- The body already wraps under its spacer here, because a 60ch
- column cannot sit beside it. The heading did not: a two-word
- title fits beside the spacer at any width, so the title stayed
- indented 97px above a body that had returned to the left margin.
- That is the artifact -- a title hanging to the right of its own
- text on a phone.
-
- Removing both is what keeps them together. Matching the body's
- content-driven turnover instead was tried and does not work: the
- heading and the body hold different content, so they give out at
- different widths, and tuning the title's column to compensate only
- moved the disagreement (a 10px window at 614-622px became a 90px
- one at 528-612px). */
- .home-spacer { display: none; }
}
.contents :first-child { margin-top: 0; }
/* A diagram is a figure, not a paragraph: it takes the column's width
@@ -649,10 +611,11 @@ cat << _EOF_
<div class="page-wrapper">
$(page_nav)
<div class="contents-cell">
- <div class="heading heading-page"><span class="home-spacer" aria-hidden="true"></span><h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></div>
- <span class="home-spacer" aria-hidden="true"></span><div class="contents">
+ <div class="heading heading-page"><h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></div>
+ <div class="contents">
$(echo "${POST_CONTENTS:-}")
<div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div>
+ <a href="$URL_PREFIX/" class="home">←︎ $(html_escape "${BLOG_TITLE:-}")</a>
</div>
</div>
</div>
diff --git a/share/jenny/layout/post.sh b/share/jenny/layout/post.sh
index 364c237..ef6c18e 100755
--- a/share/jenny/layout/post.sh
+++ b/share/jenny/layout/post.sh
@@ -250,8 +250,21 @@ cat << _EOF_
.heading .title { color: var(--text-heading); display: table-cell; margin: 0 0 1em; font-weight:bold; max-width: 500px; }
.heading .title:hover { text-decoration: underline; }
.heading .stamp,
- .home { display: inline-block; text-align:right; margin-right: 1.5em; }
- .home { text-decoration: none; width: 5.4em; margin-bottom: 1.5em; text-align: left; color: var(--link-home); } .home:hover { color: var(--link-home-hover); }
+ /* The way back sits after the text, where a reader finishes, rather
+ than beside it. Held in a fixed column before the body it wrapped
+ onto its own line once the body no longer fit next to it, leaving a
+ stray arrow floating between the title and the first paragraph on a
+ phone -- and on wider screens it reserved a column the body then had
+ to be indented past. Below the text it needs no column at either
+ width, so the body starts at the left margin everywhere.
+
+ It is a second route home: the site title in the header links there
+ from every page. Kept because the end of a long read is exactly
+ where that is useful, and named rather than left as a bare glyph so
+ it says where it goes. */
+ .home { display: block; margin-top: 2.5em; text-decoration: none;
+ color: var(--link-home); }
+ .home:hover { color: var(--link-home-hover); }
.heading .stamp { color: var(--text-muted); }
.contents { display: inline-block; max-width: 60ch; vertical-align: top; width: 100%; }
.contents :first-child { margin-top: 0; }
@@ -361,9 +374,10 @@ cat << _EOF_
<header><a href="$URL_PREFIX/">$(html_escape "${BLOG_TITLE:-}")</a></header>
<article>
<div class="heading$([ -n "${IS_PAGE:-}" ] && echo ' heading-page')"><a href="${POST_URL:-}">$([ -z "${IS_PAGE:-}" ] && printf '<span class="stamp">%s</span>' "$("$DATE" -d "${POST_DATE_RFC822:-}" +%m/%d/%Y 2>/dev/null || printf '%s' "${POST_DATE:-}")")<h1 class="title">$(html_escape "${POST_TITLE:-}")</h1></a></div>
- <a href="$URL_PREFIX/" class="home">←︎</a><div class="contents">
+ <div class="contents">
$(echo "${POST_CONTENTS:-}")
<div class="tags">$(for i in $TAGS; do echo "<a href=\"../tag/$i/\">$(html_escape "$i")</a>"; done;)</div>
+ <a href="$URL_PREFIX/" class="home">←︎ $(html_escape "${BLOG_TITLE:-}")</a>
</div>
</article>
$(diagram_script)
diff --git a/test/regressions.sh b/test/regressions.sh
index 8915343..0c385ea 100755
--- a/test/regressions.sh
+++ b/test/regressions.sh
@@ -1313,17 +1313,19 @@ assert_file_exists "$nop/.dist/fallback.html"
it "and does so without complaint"
assert_eq "$stderr_output" ""
-it "a page has no back arrow"
-# A post's arrow goes back to the index it was listed on. A page is not listed
-# anywhere -- it is reached by its own stable path -- so an arrow to the index
-# points at a place the reader did not come from. The breadcrumb in the header
-# is what a page navigates by instead.
-assert_file_not_contains "$pg/.dist/study/consciousness.html" 'class="home"'
-
-it "the arrow's column is still reserved so the body lines up with a post's"
-# Removing the arrow must not move the body: a post indents by the arrow's
-# width, and a page has to match it.
-assert_file_contains "$pg/.dist/study/consciousness.html" 'class="home-spacer"'
+it "a page ends with the way back, like a post"
+# Held in a column before the body the arrow wrapped onto its own line once
+# the body no longer fit beside it, leaving a stray glyph between the title and
+# the first paragraph on a phone. Below the text it needs no column at any
+# width, and posts and pages end the same way.
+assert_file_contains "$pg/.dist/study/consciousness.html" 'class="home"'
+
+it "the way back comes after the body, not before it"
+tags_line=$("$GREP" -n 'class="tags"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1)
+home_line=$("$GREP" -n 'class="home"' "$pg/.dist/study/consciousness.html" | head -1 | cut -d: -f1)
+[ -n "$home_line" ] && [ "$home_line" -gt "$tags_line" ] \
+ && pass "back link follows the content" \
+ || fail "back link at line $home_line must come after the body at line $tags_line"
it "a blog with only pages and no posts still builds"
only="$TMPROOT/onlypages"
@@ -1685,22 +1687,13 @@ assert_file_not_contains "$nav_sb/.dist/test.html" '.page-nav { position: absolu
it "a page with no nav takes the body indent, not the sidebar column"
assert_file_contains "$nav_sb/.dist/plain.html" '.page-wrapper:not(:has(.page-nav))'
-it "a page with no nav reserves the arrow column the way a post does"
-# A post's indent comes from the back arrow sitting inline before the body, so
-# the two reflow together when the screen narrows. A page has no arrow, so it
-# reserves the same column with a spacer of the same size; a hardcoded padding
-# matched at one width and was 97px out at 735px.
-# Anchored to the body, not just "a spacer exists somewhere": the heading has
-# one too, and a check that matched either passed with the body's spacer
-# deleted -- which drops the body indent from 110px to 0 against a post's 110.
-assert_file_contains "$nav_sb/.dist/plain.html" '<span class="home-spacer" aria-hidden="true"></span><div class="contents">'
-assert_file_contains "$nav_sb/.dist/plain.html" '.home-spacer { display: inline-block'
-
-it "the spacer is hidden from assistive technology"
-assert_file_contains "$nav_sb/.dist/plain.html" 'class="home-spacer" aria-hidden="true"'
-
-it "a page with a sidebar drops the arrow spacer"
-assert_file_contains "$nav_sb/.dist/test.html" '.page-wrapper:has(.page-nav) .home-spacer { display: none'
+it "the body starts at the left margin, with no column reserved"
+# The indent existed only to make room for the back arrow beside the body.
+# With the arrow below the text there is nothing to clear, so the text uses the
+# full measure -- on a 375px phone the reserved column cost a quarter of the
+# line, 42 characters down to 32.
+assert_file_not_contains "$nav_sb/.dist/plain.html" 'home-spacer'
+assert_file_contains "$nav_sb/.dist/plain.html" '.home { display: block; margin-top: 2.5em;'
it "a sidebar widens the article rather than narrowing the body"
# 74ch is sized for a post: arrow column plus a 60ch body. Letting the sidebar
@@ -1719,11 +1712,10 @@ post_file=$(find "$nav_sb/.dist/post" -name '*.html' | head -1)
post_contents=$("$GREP" -o '^ \.contents { [^}]*}' "$post_file" | head -1)
assert_eq "$page_contents" "$post_contents"
-it "the heading reserves the same column the body does"
-# On a post the stamp holds the left column, so the title starts where the
-# body starts. A page has no stamp; without the same spacer in the heading the
-# title sat at the far left while its own body was indented past it.
-assert_file_contains "$nav_sb/.dist/plain.html" 'class="heading heading-page"><span class="home-spacer"'
+it "the heading holds no reserved column either"
+# Nothing sits beside the title any more, so it starts where its body starts
+# without a spacer to keep in step.
+assert_file_contains "$nav_sb/.dist/plain.html" 'class="heading heading-page"><h1 class="title">'
it "the heading has no breakpoint of its own"
# A post has no media query at all -- heading and body collapse together by
@@ -1733,10 +1725,29 @@ it "the heading has no breakpoint of its own"
narrow_head=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$nav_sb/.dist/plain.html")
assert_not_contains "$narrow_head" '.heading-page { margin-left: 0; display: block'
-it "a sidebar page does not indent its heading twice"
-# The cell is already offset by the nav column; indenting the heading again
-# would put the title a column right of its own body.
-assert_file_contains "$nav_sb/.dist/test.html" '.page-wrapper:has(.page-nav) .heading-page .home-spacer { display: none'
+it "a post ends with the way back"
+# The post side was unasserted: deleting the link from post.sh entirely broke
+# no test, because every other check here reads a page.
+post_file_bk=$(find "$nav_sb/.dist/post" -name '*.html' | head -1)
+assert_file_contains "$post_file_bk" 'class="home">'
+
+it "a post's back link comes after its body"
+tl=$("$GREP" -n 'class="tags"' "$post_file_bk" | head -1 | cut -d: -f1)
+hl=$("$GREP" -n 'class="home"' "$post_file_bk" | head -1 | cut -d: -f1)
+[ -n "$hl" ] && [ "$hl" -gt "$tl" ] \
+ && pass "post back link follows the content" \
+ || fail "post back link at line $hl must come after the body at line $tl"
+
+it "the back link names where it goes"
+# A bare arrow says only "away". Both layouts render the site title beside it.
+assert_file_contains "$post_file_bk" '←︎ '
+
+it "posts and pages declare the same back link"
+# The whole point of moving it: both layouts end the same way. Compare the
+# declarations rather than trusting they look alike.
+page_home=$("$GREP" -o '\.home { display: block[^}]*}' "$nav_sb/.dist/plain.html" | head -1)
+post_home=$("$GREP" -o '\.home { display: block[^}]*}' "$(find "$nav_sb/.dist/post" -name '*.html' | head -1)" | head -1)
+assert_eq "$page_home" "$post_home"
it "narrow screens collapse the whole table, not just one cell"
# A lone cell reverting to block still gets an anonymous table wrapper and
@@ -1790,14 +1801,12 @@ it "a page title keeps a gap above its body"
# paragraph, 0px where a post has 1em.
assert_file_contains "$ts/.dist/plain.html" '.heading-page .title { display: inline-block; margin: 0 0 1em;'
-it "the arrow column is dropped on narrow screens"
-# The indent reserves a post's back-arrow column. On a phone it costs more
-# than it reserves -- at 375px it takes the measure from 42 characters to 32 --
-# and the body already wraps to the left margin there while the heading, whose
-# title always fits beside the spacer, did not. The title was left hanging
-# 97px right of its own text.
+it "narrow screens need no rule for the arrow column"
+# There is no column to drop: the back link sits below the text at every
+# width, so the narrow block only has to collapse the sidebar.
narrow_sp=$("$SED" -n '/@media (max-width: 735px)/,/^ }/p' "$ts/.dist/plain.html")
-assert_contains "$narrow_sp" '.home-spacer { display: none; }'
+assert_not_contains "$narrow_sp" 'home-spacer'
+assert_contains "$narrow_sp" '.contents-cell { padding-left: 0; }'
it "the heading is not a flex row"
# Flex items do not wrap, which is why the heading held its column at widths