From b9c07dbc98c2a1d522d4bff65e839adcb5175729 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 9 May 2026 09:32:21 +0700 Subject: [PATCH] feat(a11y): skip-link, focus rings, render hooks, giscus paint fix Add skip-to-content link, enhance focus indicators, implement custom render hooks for links and images, fix giscus dark mode paint issue. --- themes/tsuki/assets/css/components.css | 32 +++++++++++++++++++ themes/tsuki/assets/css/home.css | 1 - themes/tsuki/assets/js/giscus-theme.js | 11 +++++++ .../tsuki/layouts/_markup/render-image.html | 5 +++ themes/tsuki/layouts/_markup/render-link.html | 11 +++++++ themes/tsuki/layouts/_partials/header.html | 2 +- themes/tsuki/layouts/baseof.html | 3 +- 7 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 themes/tsuki/layouts/_markup/render-image.html create mode 100644 themes/tsuki/layouts/_markup/render-link.html diff --git a/themes/tsuki/assets/css/components.css b/themes/tsuki/assets/css/components.css index 83a09b2..dda9d49 100644 --- a/themes/tsuki/assets/css/components.css +++ b/themes/tsuki/assets/css/components.css @@ -1,5 +1,24 @@ /* tsuki: components — post cards, post meta, terms, pagination, code copy */ +/* a11y: visible focus + skip-link */ +:focus-visible { + outline: 2px solid var(--tsuki-accent); + outline-offset: 2px; +} +.skip-link { + position: absolute; + inset-inline-start: var(--tsuki-space-3); + inset-block-start: -3rem; + z-index: 100; + padding: var(--tsuki-space-2) var(--tsuki-space-4); + background: var(--tsuki-bg); + color: var(--tsuki-fg); + border: 1px solid var(--tsuki-border); + border-radius: var(--tsuki-radius); + transition: var(--tsuki-transition); +} +.skip-link:focus { inset-block-start: var(--tsuki-space-3); text-decoration: none; } + /* post card */ .post-card { padding-block: var(--tsuki-space-6); @@ -170,6 +189,19 @@ pre:hover .code-copy, .code-copy:focus-visible { opacity: 1; } .code-copy:hover { color: var(--tsuki-fg); } +/* related posts (single page) */ +.related-posts { + margin-block-start: var(--tsuki-space-12); + padding-block-start: var(--tsuki-space-6); + border-block-start: 1px solid var(--tsuki-border); +} +.related-heading { + font-size: var(--tsuki-fs-xl); + margin: 0 0 var(--tsuki-space-4); +} +.related-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; } +.related-list .post-card { padding-block: var(--tsuki-space-4); } + /* heading anchor */ .heading-anchor { margin-inline-end: var(--tsuki-space-2); diff --git a/themes/tsuki/assets/css/home.css b/themes/tsuki/assets/css/home.css index 5466696..fd75056 100644 --- a/themes/tsuki/assets/css/home.css +++ b/themes/tsuki/assets/css/home.css @@ -79,7 +79,6 @@ border: 1px solid var(--tsuki-border); border-radius: var(--tsuki-radius-lg); transition: var(--tsuki-transition); - view-transition-name: var(--tsuki-vt-name, none); } .project-card:hover { border-color: var(--tsuki-accent); diff --git a/themes/tsuki/assets/js/giscus-theme.js b/themes/tsuki/assets/js/giscus-theme.js index 29271bb..9c5076c 100644 --- a/themes/tsuki/assets/js/giscus-theme.js +++ b/themes/tsuki/assets/js/giscus-theme.js @@ -12,5 +12,16 @@ if (giscus) { "https://giscus.app" ); }; + // Push current theme as soon as Giscus iframe is ready so first paint matches. + // Self-remove after first valid Giscus message; later theme changes are caught by the MutationObserver below. + let primed = false; + const onFirstGiscusMessage = (e) => { + if (primed) return; + if (e.origin !== "https://giscus.app" || !e.data || !e.data.giscus) return; + primed = true; + window.removeEventListener("message", onFirstGiscusMessage); + send(); + }; + window.addEventListener("message", onFirstGiscusMessage); new MutationObserver(send).observe(root, { attributes: true, attributeFilter: ["data-theme"] }); } diff --git a/themes/tsuki/layouts/_markup/render-image.html b/themes/tsuki/layouts/_markup/render-image.html new file mode 100644 index 0000000..91b6d4a --- /dev/null +++ b/themes/tsuki/layouts/_markup/render-image.html @@ -0,0 +1,5 @@ +{{- /* In-content images get loading="lazy" + decoding="async" by default. */ -}} +{{ .Text }} +{{- /**/ -}} diff --git a/themes/tsuki/layouts/_markup/render-link.html b/themes/tsuki/layouts/_markup/render-link.html new file mode 100644 index 0000000..7b58237 --- /dev/null +++ b/themes/tsuki/layouts/_markup/render-link.html @@ -0,0 +1,11 @@ +{{- /* External markdown links get rel="noopener noreferrer". No target="_blank" — UX choice. */ -}} +{{- $external := or + (strings.HasPrefix .Destination "http://") + (strings.HasPrefix .Destination "https://") + (strings.HasPrefix .Destination "//") +-}} +{{ .Text | safeHTML }} +{{- /**/ -}} diff --git a/themes/tsuki/layouts/_partials/header.html b/themes/tsuki/layouts/_partials/header.html index 60d8836..a5683f5 100644 --- a/themes/tsuki/layouts/_partials/header.html +++ b/themes/tsuki/layouts/_partials/header.html @@ -1,6 +1,6 @@