From 828846ca40b31b85c08409913e08056ae1e94deb Mon Sep 17 00:00:00 2001 From: Hakula Chen Date: Wed, 30 Sep 2026 04:31:56 +0800 Subject: [PATCH 1/3] fix(css): keep heading anchors inside mobile article cards Match the mobile anchor gutter to the article card padding while retaining the wider-screen position. Align heading rules with the text, wrap long headings, and show keyboard-focused anchor links. --- static/css/_src/components/content/prose.css | 10 ++++++--- static/css/style.css | 22 +++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/static/css/_src/components/content/prose.css b/static/css/_src/components/content/prose.css index 8728e66..12addc2 100644 --- a/static/css/_src/components/content/prose.css +++ b/static/css/_src/components/content/prose.css @@ -23,7 +23,7 @@ /* ── Headings ── */ .prose :where(h2, h3, h4, h5, h6) { - @apply mb-6 leading-normal font-semibold text-text; + @apply mb-6 leading-normal font-semibold wrap-break-word text-text; } .prose :where(h2, h3, h4, h5, h6) + * { @@ -31,7 +31,7 @@ } .prose :where(h2) { - @apply mt-[2.25em] pb-[0.4em] text-[1.4em] font-bold after:absolute after:right-0 after:bottom-0 after:left-5 after:border-b after:border-border-overlay; + @apply mt-[2.25em] pb-[0.4em] text-[1.4em] font-bold after:absolute after:right-0 after:bottom-0 after:left-4 after:border-b after:border-border-overlay sm:after:left-5; } .prose :where(h3) { @@ -189,7 +189,7 @@ /* ── Heading Anchors ── */ .prose :where(h1, h2, h3, h4, h5, h6) { - @apply relative -ml-5 pl-5; + @apply relative -ml-3 pl-4 sm:-ml-5 sm:pl-5; } .prose .heading-anchor { @@ -200,6 +200,10 @@ @apply opacity-60; } + .prose .heading-anchor:focus-visible { + @apply opacity-100; + } + .prose :where(.callout, blockquote) .heading-anchor { @apply hidden; } diff --git a/static/css/style.css b/static/css/style.css index 4ae8523..09ddf23 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2136,6 +2136,7 @@ line-height: var(--leading-normal); --tw-font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold); + overflow-wrap: break-word; color: var(--color-text); } .prose :where(h2, h3, h4, h5, h6) + * { @@ -2161,7 +2162,7 @@ } &::after { content: var(--tw-content); - left: calc(var(--spacing) * 5); + left: calc(var(--spacing) * 4); } &::after { content: var(--tw-content); @@ -2172,6 +2173,12 @@ content: var(--tw-content); border-color: var(--color-border-overlay); } + @media (width >= 40rem) { + &::after { + content: var(--tw-content); + left: calc(var(--spacing) * 5); + } + } } .prose :where(h3) { margin-top: 2em; @@ -2324,8 +2331,14 @@ } .prose :where(h1, h2, h3, h4, h5, h6) { position: relative; - margin-left: calc(var(--spacing) * -5); - padding-left: calc(var(--spacing) * 5); + margin-left: calc(var(--spacing) * -3); + padding-left: calc(var(--spacing) * 4); + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * -5); + } + @media (width >= 40rem) { + padding-left: calc(var(--spacing) * 5); + } } .prose .heading-anchor { position: absolute; @@ -2340,6 +2353,9 @@ .prose :where(h2, h3, h4, h5, h6):hover .heading-anchor { opacity: 60%; } + .prose .heading-anchor:focus-visible { + opacity: 100%; + } .prose :where(.callout, blockquote) .heading-anchor { display: none; } From 6aa6a772200b00d146defc7c000ba0380a7b7c20 Mon Sep 17 00:00:00 2001 From: Hakula Chen Date: Thu, 1 Oct 2026 01:50:12 +0800 Subject: [PATCH 2/3] fix(content): link heading text without an anchor gutter --- static/css/_src/components/content/prose.css | 34 ++++---------- static/css/style.css | 48 ++++---------------- static/js/content/content.js | 8 +++- 3 files changed, 23 insertions(+), 67 deletions(-) diff --git a/static/css/_src/components/content/prose.css b/static/css/_src/components/content/prose.css index 12addc2..626cb4b 100644 --- a/static/css/_src/components/content/prose.css +++ b/static/css/_src/components/content/prose.css @@ -31,7 +31,7 @@ } .prose :where(h2) { - @apply mt-[2.25em] pb-[0.4em] text-[1.4em] font-bold after:absolute after:right-0 after:bottom-0 after:left-4 after:border-b after:border-border-overlay sm:after:left-5; + @apply relative mt-[2.25em] pb-[0.4em] text-[1.4em] font-bold after:absolute after:right-0 after:bottom-0 after:left-0 after:border-b after:border-border-overlay; } .prose :where(h3) { @@ -140,13 +140,17 @@ .prose :where(a:not(.not-prose, .not-prose *)) { @apply pb-0.5 no-underline; - &:hover { + &:hover, + &:focus-visible { @apply text-link-hover; } } -.prose .heading-anchor, -.prose .heading-anchor:hover, +.prose :where(.heading-anchor) { + @apply p-0 text-inherit; + font-weight: inherit; +} + .prose .footnote-reference a, .prose .footnote-reference a:hover, .prose .footnote-backref, @@ -186,28 +190,6 @@ @apply wrap-break-word; } - /* ── Heading Anchors ── */ - - .prose :where(h1, h2, h3, h4, h5, h6) { - @apply relative -ml-3 pl-4 sm:-ml-5 sm:pl-5; - } - - .prose .heading-anchor { - @apply absolute left-0 text-link no-underline opacity-0 transition-opacity; - } - - .prose :where(h2, h3, h4, h5, h6):hover .heading-anchor { - @apply opacity-60; - } - - .prose .heading-anchor:focus-visible { - @apply opacity-100; - } - - .prose :where(.callout, blockquote) .heading-anchor { - @apply hidden; - } - /* ── Images ── */ .prose figure img { diff --git a/static/css/style.css b/static/css/style.css index 09ddf23..a0c49c6 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2143,6 +2143,7 @@ margin-top: 0px; } .prose :where(h2) { + position: relative; margin-top: 2.25em; padding-bottom: 0.4em; font-size: 1.4em; @@ -2162,7 +2163,7 @@ } &::after { content: var(--tw-content); - left: calc(var(--spacing) * 4); + left: 0px; } &::after { content: var(--tw-content); @@ -2173,12 +2174,6 @@ content: var(--tw-content); border-color: var(--color-border-overlay); } - @media (width >= 40rem) { - &::after { - content: var(--tw-content); - left: calc(var(--spacing) * 5); - } - } } .prose :where(h3) { margin-top: 2em; @@ -2293,11 +2288,16 @@ .prose :where(a:not(.not-prose, .not-prose *)) { padding-bottom: calc(var(--spacing) * 0.5); text-decoration-line: none; - &:hover { + &:hover, &:focus-visible { color: var(--color-link-hover); } } -.prose .heading-anchor, .prose .heading-anchor:hover, .prose .footnote-reference a, .prose .footnote-reference a:hover, .prose .footnote-backref, .prose .footnote-backref:hover { +.prose :where(.heading-anchor) { + padding: 0px; + color: inherit; + font-weight: inherit; +} +.prose .footnote-reference a, .prose .footnote-reference a:hover, .prose .footnote-backref, .prose .footnote-backref:hover { border-bottom-style: var(--tw-border-style); border-bottom-width: 0px; } @@ -2329,36 +2329,6 @@ .prose :where(p, li) { overflow-wrap: break-word; } - .prose :where(h1, h2, h3, h4, h5, h6) { - position: relative; - margin-left: calc(var(--spacing) * -3); - padding-left: calc(var(--spacing) * 4); - @media (width >= 40rem) { - margin-left: calc(var(--spacing) * -5); - } - @media (width >= 40rem) { - padding-left: calc(var(--spacing) * 5); - } - } - .prose .heading-anchor { - position: absolute; - left: 0px; - color: var(--color-link); - text-decoration-line: none; - opacity: 0%; - transition-property: opacity; - transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); - transition-duration: var(--tw-duration, var(--default-transition-duration)); - } - .prose :where(h2, h3, h4, h5, h6):hover .heading-anchor { - opacity: 60%; - } - .prose .heading-anchor:focus-visible { - opacity: 100%; - } - .prose :where(.callout, blockquote) .heading-anchor { - display: none; - } .prose figure img { margin-inline: auto; border-radius: var(--radius-lg); diff --git a/static/js/content/content.js b/static/js/content/content.js index 90957b0..cd7896c 100644 --- a/static/js/content/content.js +++ b/static/js/content/content.js @@ -109,13 +109,17 @@ ); const jumpToPrefix = document.documentElement.dataset.i18nJumpTo; for (const heading of headings) { + if (heading.closest('.callout, blockquote') || heading.querySelector('a')) { + continue; + } + const anchor = document.createElement('a'); anchor.href = `#${heading.id}`; anchor.className = 'heading-anchor'; - anchor.textContent = '#'; const label = jumpToPrefix ? `${jumpToPrefix} ${heading.textContent}` : heading.textContent; anchor.setAttribute('aria-label', label); - heading.prepend(anchor); + anchor.append(...heading.childNodes); + heading.append(anchor); } }; From a85d4bbed24132013b1220f2212b50199a7aba27 Mon Sep 17 00:00:00 2001 From: Hakula Chen Date: Thu, 1 Oct 2026 02:09:08 +0800 Subject: [PATCH 3/3] fix(css): show heading markers only on desktop --- static/css/_src/components/content/prose.css | 26 +++++++++++++++++--- static/css/style.css | 25 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/static/css/_src/components/content/prose.css b/static/css/_src/components/content/prose.css index 626cb4b..f8fea76 100644 --- a/static/css/_src/components/content/prose.css +++ b/static/css/_src/components/content/prose.css @@ -23,7 +23,7 @@ /* ── Headings ── */ .prose :where(h2, h3, h4, h5, h6) { - @apply mb-6 leading-normal font-semibold wrap-break-word text-text; + @apply relative mb-6 leading-normal font-semibold wrap-break-word text-text; } .prose :where(h2, h3, h4, h5, h6) + * { @@ -31,7 +31,7 @@ } .prose :where(h2) { - @apply relative mt-[2.25em] pb-[0.4em] text-[1.4em] font-bold after:absolute after:right-0 after:bottom-0 after:left-0 after:border-b after:border-border-overlay; + @apply mt-[2.25em] pb-[0.4em] text-[1.4em] font-bold after:absolute after:right-0 after:bottom-0 after:left-0 after:border-b after:border-border-overlay; } .prose :where(h3) { @@ -147,8 +147,28 @@ } .prose :where(.heading-anchor) { - @apply p-0 text-inherit; + @apply border-b-0 p-0 text-inherit; font-weight: inherit; + + @variant xl { + &:hover, + &:focus-visible { + @apply text-inherit; + } + + &::before { + @apply absolute -left-5 text-link opacity-0 transition-opacity; + content: '#'; + } + + &:hover::before { + @apply opacity-60; + } + + &:focus-visible::before { + @apply opacity-100; + } + } } .prose .footnote-reference a, diff --git a/static/css/style.css b/static/css/style.css index a0c49c6..1de498e 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2131,6 +2131,7 @@ --tw-prose-captions: var(--color-text-secondary); } .prose :where(h2, h3, h4, h5, h6) { + position: relative; margin-bottom: calc(var(--spacing) * 6); --tw-leading: var(--leading-normal); line-height: var(--leading-normal); @@ -2143,7 +2144,6 @@ margin-top: 0px; } .prose :where(h2) { - position: relative; margin-top: 2.25em; padding-bottom: 0.4em; font-size: 1.4em; @@ -2293,9 +2293,32 @@ } } .prose :where(.heading-anchor) { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 0px; padding: 0px; color: inherit; font-weight: inherit; + @media (width >= 80rem) { + &:hover, &:focus-visible { + color: inherit; + } + &::before { + position: absolute; + left: calc(var(--spacing) * -5); + color: var(--color-link); + opacity: 0%; + transition-property: opacity; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + content: '#'; + } + &:hover::before { + opacity: 60%; + } + &:focus-visible::before { + opacity: 100%; + } + } } .prose .footnote-reference a, .prose .footnote-reference a:hover, .prose .footnote-backref, .prose .footnote-backref:hover { border-bottom-style: var(--tw-border-style);