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.
This commit is contained in:
tiennm99 committed 2026-05-09 09:32:21 +07:00
1 parent 2d67d3c77f
commit b9c07dbc98
7 files changed
+62 -3

No files matched your search

+32
View File
@@ -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);
-1
View File
@@ -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);
+11
View File
@@ -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"] });
}
@@ -0,0 +1,5 @@
{{- /* In-content images get loading="lazy" + decoding="async" by default. */ -}}
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" loading="lazy" decoding="async"
{{- with .Title }} title="{{ . }}"{{ end -}}
>
{{- /**/ -}}
@@ -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 "//")
-}}
<a href="{{ .Destination | safeURL }}"
{{- with .Title }} title="{{ . }}"{{ end -}}
{{- if $external }} rel="noopener noreferrer"{{ end -}}
>{{ .Text | safeHTML }}</a>
{{- /**/ -}}
+1 -1
View File
@@ -1,6 +1,6 @@
<header class="site-header">
<a class="site-title" href="{{ "/" | relURL }}">
{{- with site.Params.profile.name }}{{ . }}{{ else }}{{ site.Title }}{{ end -}}
{{- with site.Data.profile }}{{ .name | default site.Title }}{{ else }}{{ site.Title }}{{ end -}}
</a>
{{ partial "nav.html" . }}
<div class="site-header-actions">
+2 -1
View File
@@ -5,8 +5,9 @@
{{ block "head_extra" . }}{{ end }}
</head>
<body class="{{ block "body_class" . }}{{ end }}">
<a class="skip-link" href="#main">{{ i18n "skipToContent" | default "Đến nội dung chính" }}</a>
{{ partial "header.html" . }}
<main>
<main id="main">
{{ block "main" . }}{{ end }}
</main>
{{ partial "footer.html" . }}