Merge pull request #24 from radishzzz/dev

 feat: optimize performance, enhance animations and improve accessibility

- perf: replace overlayscrollbars with native scrollbar to fix page loading lag
- feat: implement GSAP animations throughout the site with optimized curves and durations
- style: refine scrollbar styling and heading margins for better visual consistency
- fix: resolve accessibility issues with footer links and improve mobile responsiveness
- refactor: reorganize CSS files and optimize DOM selectors for better performance
- chore: update dependencies and implement astro-compress
- docs: update README and theme guides
This commit is contained in:
radishzz 2025-05-19 03:28:55 +01:00 committed by GitHub
commit 3d3e422ef2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 1697 additions and 2027 deletions

81
.vscode/settings.json vendored
View file

@ -57,84 +57,5 @@
"typescript.tsdk": "node_modules/typescript/lib",
"files.associations": {
"*.mdx": "markdown"
},
"cSpell.words": [
"abbrlink",
"antfu",
"apiflash",
"apos",
"Artículos",
"astrodotbuild",
"astrojs",
"attributify",
"backref",
"belleza",
"Beze",
"Blockquotes",
"blurhash",
"bmoji",
"canvaskit",
"Cpath",
"Csvg",
"Disqus",
"Etiquetas",
"figcaption",
"Frontmatter",
"Fuwriu",
"Giscus",
"GSAP",
"gtag",
"hètí",
"Heti",
"katex",
"Lightbox",
"Macbook",
"mdast",
"Moeyua",
"msrc",
"msvalidate",
"noopener",
"noreferrer",
"Noto",
"oklch",
"opengraph",
"overlayscrollbars",
"pagefind",
"partytown",
"photoswipe",
"pswp",
"radishzz",
"rashomon",
"rehype",
"reimagines",
"Retipografía",
"Retypeset",
"Reviviendo",
"Roundhand",
"Segoe",
"Sentimentalisme",
"Servetus",
"shiki",
"Sobre",
"srcset",
"STIX",
"stylesheet",
"tipográfica",
"titlebar",
"Twikoo",
"Umami",
"unocss",
"unpic",
"vite",
"waline",
"walinejs",
"weibo",
"Возрождая",
"красоту",
"Переверстка",
"Посты",
"себе",
"Теги",
"типографики"
]
}
}

View file

@ -42,7 +42,6 @@ Retypeset is a static blog theme based on the [Astro](https://astro.build/) fram
## Performance
<br>
<p align="center">
<a href="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fen%2F">
<img width="710" alt="Retypeset Lighthouse Score" src="images/retypeset-lighthouse-score.svg">
@ -52,15 +51,13 @@ Retypeset is a static blog theme based on the [Astro](https://astro.build/) fram
## Getting Started
1. [Fork](https://github.com/radishzzz/astro-theme-retypeset/fork) this repository, or use this template to create a new repository.
2. Click the `Code` button, copy the `HTTPS` URL, and run the following commands in your terminal:
2. Run the following commands in your terminal:
```bash
# Clone the repository
git clone repository-url
git clone <repository-url>
# Navigate to the project directory
cd repository-name
cd <repository-name>
# Install pnpm globally (if not already installed)
npm install -g pnpm
@ -71,23 +68,14 @@ Retypeset is a static blog theme based on the [Astro](https://astro.build/) fram
# Start the development server
pnpm dev
```
3. Refer to the [Theme Guide](https://retypeset.radishzz.cc/en/posts/theme-guide/) to customize your blog and create new articles.
4. Refer to the [Astro Deployment Guides](https://docs.astro.build/en/guides/deploy/) to deploy your blog to Netlify, Vercel, or other platforms.
3. Open [localhost:4321](http://localhost:4321/) in your browser to see a live preview of your website.
4. Refer to the [Theme Guide](https://retypeset.radishzz.cc/en/posts/theme-guide/) to customize your blog and create new articles.
5. Refer to the [Astro Deployment Guides](https://docs.astro.build/en/guides/deploy/) to deploy your blog to Netlify, Vercel, or other platforms.
&emsp;[![Deploy to Netlify](images/deploy-netlify.svg)](https://app.netlify.com/start)
[![Deploy to Vercel](images/deploy-vercel.svg)](https://vercel.com/new)
&emsp;[![Deploy to Netlify](images/deploy-netlify.svg)](https://app.netlify.com/start) [![Deploy to Vercel](images/deploy-vercel.svg)](https://vercel.com/new)
## Updates
- Retypeset releases [new features](https://github.com/radishzzz/astro-theme-retypeset/issues/18) from time to time, which can be updated as follows.
- Refer to the [GitHub Docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) to run `Sync fork` in your repository to sync with the latest branch.
- Do not click `Discard Changes`, or you will lose your modifications.
Retypeset releases [new features](https://github.com/radishzzz/astro-theme-retypeset/issues/18) from time to time. You can refer to the [GitHub Docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) and run `Sync fork` in your repository to sync with the latest branch. Do not click `Discard Changes`, or you will lose your modifications.
## Credits

View file

@ -2,7 +2,7 @@
<img alt="Cover Image" src="images/retypeset-zh-mobile.webp"/>
<div align="center">
<a title="en" href="README.md">
<a title="en" href="https://github.com/radishzzz/astro-theme-retypeset?tab=readme-ov-file#retypeset">
<img src="https://img.shields.io/badge/-English-545759?style=for-the-badge" alt="English">
</a>
<picture>
@ -42,7 +42,6 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主
## 性能
<br>
<p align="center">
<a href="https://pagespeed.web.dev/analysis?url=https%3A%2F%2Fretypeset.radishzz.cc%2F">
<img width="710" alt="Retypeset Lighthouse Score" src="images/retypeset-lighthouse-score.svg">
@ -52,15 +51,13 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主
## 开始
1. [Fork](https://github.com/radishzzz/astro-theme-retypeset/fork) 此仓库,或使用此模版创建新仓库。
2. 点击 `Code` 按钮,复制 `HTTPS` 地址,在终端执行:
2. 在终端执行以下指令:
```bash
# 克隆仓库
git clone 仓库地址
git clone <仓库地址>
# 进入项目目录
cd 仓库名称
cd <仓库名称>
# 全局安装 pnpm如果未安装
npm install -g pnpm
@ -71,23 +68,14 @@ Retypeset 是一款基于 [Astro](https://astro.build/) 框架的静态博客主
# 启动开发服务器
pnpm dev
```
3. 参考 [主题上手指南](https://retypeset.radishzz.cc/posts/theme-guide/),自定义你的博客并创建新文章。
4. 参考 [Astro 部署指南](https://docs.astro.build/zh-cn/guides/deploy/),将博客部署至 Netlify、Vercel 等平台。
3. 在浏览器中打开 [localhost:4321](http://localhost:4321/),查看网站的实时预览。
4. 参考 [主题上手指南](https://retypeset.radishzz.cc/posts/theme-guide/),自定义你的博客并创建新文章。
5. 参考 [Astro 部署指南](https://docs.astro.build/zh-cn/guides/deploy/),将博客部署至 Netlify、Vercel 等平台。
&emsp;[![Deploy to Netlify](images/deploy-netlify.svg)](https://app.netlify.com/start)
[![Deploy to Vercel](images/deploy-vercel.svg)](https://vercel.com/new)
&emsp;[![Deploy to Netlify](images/deploy-netlify.svg)](https://app.netlify.com/start) [![Deploy to Vercel](images/deploy-vercel.svg)](https://vercel.com/new)
## 更新
- Retypeset 会不定期发布 [新功能](https://github.com/radishzzz/astro-theme-retypeset/issues/18),更新方法如下。
- 参考 [GitHub 文档](https://docs.github.com/zh/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork),在仓库中执行 `Sync fork` 同步最新分支。
- 不要点击 `Discard Changes`,否则会丢失你的更改。
Retypeset 会不定期发布 [新功能](https://github.com/radishzzz/astro-theme-retypeset/issues/18)。你可以参考 [GitHub 文档](https://docs.github.com/zh/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork),在仓库中执行 `Sync fork` 以同步最新分支。不要点击 `Discard Changes`,否则会丢失你的更改。
## 鸣谢

View file

@ -2,6 +2,7 @@ import type { Element } from 'hast'
import mdx from '@astrojs/mdx'
import partytown from '@astrojs/partytown'
import sitemap from '@astrojs/sitemap'
import Compress from 'astro-compress'
import robotsTxt from 'astro-robots-txt'
import { defineConfig } from 'astro/config'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
@ -15,6 +16,7 @@ import UnoCSS from 'unocss/astro'
import { themeConfig } from './src/config'
import { langMap } from './src/i18n/config'
import { rehypeImgToFigure } from './src/plugins/rehype-img-to-figure.mjs'
import { rehypeUnwrapImg } from './src/plugins/rehype-unwrap-img.mjs'
import { remarkAdmonitions } from './src/plugins/remark-admonitions.mjs'
import { remarkGithubCard } from './src/plugins/remark-github-card.mjs'
import { remarkReadingTime } from './src/plugins/remark-reading-time.mjs'
@ -57,6 +59,13 @@ export default defineConfig({
}),
sitemap(),
robotsTxt(),
Compress({
CSS: false,
HTML: true, // Enable HTML compression only to remove comments
Image: false,
JavaScript: false,
SVG: false,
}),
],
markdown: {
remarkPlugins: [
@ -70,6 +79,7 @@ export default defineConfig({
rehypeKatex,
rehypeSlug,
rehypeImgToFigure,
rehypeUnwrapImg, // Must be after rehypeImgToFigure
[
rehypeAutolinkHeadings,
{

View file

@ -2,7 +2,7 @@
"name": "astro-theme-retypeset",
"type": "module",
"version": "0.0.1",
"packageManager": "pnpm@10.10.0",
"packageManager": "pnpm@10.11.0",
"repository": "https://github.com/radishzzz/astro-theme-retypeset",
"scripts": {
"dev": "astro check && astro dev",
@ -10,23 +10,24 @@
"preview": "astro preview",
"astro": "astro",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"lint:fix": "eslint . --fix",
"comments": "curl -L https://unpkg.com/@waline/client@latest/dist/waline.css -o public/assets/waline/waline.css && curl -L https://unpkg.com/@waline/client@latest/dist/waline.js -o public/assets/waline/waline.js"
},
"dependencies": {
"@astrojs/mdx": "^4.2.6",
"@astrojs/partytown": "^2.1.4",
"@astrojs/sitemap": "^3.4.0",
"@waline/client": "^3.5.7",
"astro": "^5.7.13",
"astro-compress": "^2.3.8",
"astro-og-canvas": "^0.7.0",
"astro-robots-txt": "^1.0.0",
"canvaskit-wasm": "^0.40.0",
"feed": "^5.0.1",
"gsap": "^3.13.0",
"katex": "^0.16.22",
"markdown-it": "^14.1.0",
"mdast-util-to-string": "^4.0.0",
"node-html-parser": "^7.0.1",
"overlayscrollbars": "^2.11.2",
"photoswipe": "^5.4.4",
"reading-time": "^1.5.0",
"rehype-autolink-headings": "^7.1.0",
@ -35,26 +36,26 @@
"rehype-slug": "^6.0.0",
"remark-directive": "^4.0.0",
"remark-math": "^6.0.0",
"sanitize-html": "^2.16.0",
"sanitize-html": "^2.17.0",
"sharp": "^0.34.1",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "^4.13.0",
"@antfu/eslint-config": "^4.13.1",
"@astrojs/check": "^0.9.4",
"@types/hast": "^3.0.4",
"@types/markdown-it": "^14.1.2",
"@types/node": "^22.15.17",
"@types/node": "^22.15.18",
"@types/sanitize-html": "^2.16.0",
"@unocss/eslint-plugin": "66.1.1",
"@unocss/preset-attributify": "66.1.1",
"@unocss/reset": "66.1.1",
"@unocss/eslint-plugin": "66.1.2",
"@unocss/preset-attributify": "66.1.2",
"@unocss/reset": "66.1.2",
"astro-eslint-parser": "^1.2.2",
"eslint": "^9.26.0",
"eslint": "^9.27.0",
"eslint-plugin-astro": "^1.3.1",
"lint-staged": "^16.0.0",
"typescript": "~5.8.3",
"unocss": "66.1.1",
"unocss": "66.1.2",
"unocss-preset-theme": "^0.14.1"
},
"lint-staged": {

2169
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,91 @@
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -28,9 +28,9 @@ const nextUrl = useSupportedLangs
<div
class:list={[
'absolute flex gap-6 top-14.6 right-7.25vw min-[823px]:max-[1024px]:right-[calc(50vw-22rem)]',
'absolute right-7.25vw top-14.6 flex gap-6 min-[823px]:max-[1024px]:right-[calc(50vw-22rem)]',
'[@supports(-webkit-touch-callout:none)]:top-13.6', // fix position issue on ios
'lg:(fixed w-14rem top-auto bottom-47 right-[max(5.625rem,calc(50vw-34.375rem))])',
'lg:(fixed bottom-47 right-[max(5rem,calc(50vw-35rem))] top-auto w-14rem)',
]}
>
<!-- Language Switcher -->
@ -49,8 +49,9 @@ const nextUrl = useSupportedLangs
<!-- Theme Toggle -->
<button
id="theme-toggle-button"
aria-label="Switch light/dark theme"
class="button-theme-toggle aspect-square w-4 c-secondary active:scale-90 hover:c-primary"
class="aspect-square w-4 c-secondary active:scale-90 hover:c-primary"
>
<ThemeToggleIcon
aria-hidden="true"
@ -69,7 +70,7 @@ function updateTheme() {
// Get current theme
const isDark = document.documentElement.classList.contains('dark')
// Update meta theme color
const metaThemeColor = document.querySelector('meta[name="theme-color"]')
const metaThemeColor = document.head.querySelector('meta[name="theme-color"]')
if (metaThemeColor) {
metaThemeColor.setAttribute('content', isDark ? darkMode : lightMode)
}
@ -82,10 +83,10 @@ function updateTheme() {
// Bind click event to the button
function setupThemeToggle() {
// Locate theme toggle button
const themeToggleButtons = document.querySelectorAll('.button-theme-toggle')
// Add click listener to each button
themeToggleButtons.forEach((button) => {
button.addEventListener('click', () => {
const themeToggleButton = document.getElementById('theme-toggle-button')
// Add click listener to the button
if (themeToggleButton) {
themeToggleButton.addEventListener('click', () => {
// If browser doesn't support View Transitions API, update theme directly
if (!document.startViewTransition) {
updateTheme()
@ -93,18 +94,18 @@ function setupThemeToggle() {
}
// Temporarily add markers during animation to implement view transition and disable CSS transitions
document.documentElement.style.setProperty('view-transition-name', 'theme-transition')
document.documentElement.setAttribute('data-theme-transition', '')
document.documentElement.style.setProperty('view-transition-name', 'animation-theme-toggle')
document.documentElement.setAttribute('data-theme-changing', '')
// If browser supports View Transitions API, use it to update theme
const themeTransition = document.startViewTransition(updateTheme)
// Remove markers after animation
themeTransition.finished.then(() => {
document.documentElement.style.removeProperty('view-transition-name')
document.documentElement.removeAttribute('data-theme-transition')
document.documentElement.removeAttribute('data-theme-changing')
})
})
})
}
}
// Initialize click event (on first load or page transition)

View file

@ -2,13 +2,6 @@
import { defaultLocale, themeConfig } from '@/config'
import { walineLocaleMap } from '@/i18n/config'
const {
serverURL = '',
emoji = [],
search = false,
imageUploader = false,
} = themeConfig.comment?.waline ?? {}
// Get the language code of Waline
function getWalineLang(currentPath: string, defaultLocale: string): string {
// Extract language code from path
@ -20,43 +13,46 @@ function getWalineLang(currentPath: string, defaultLocale: string): string {
return walineLocaleMap[lang as keyof typeof walineLocaleMap]
}
// Get Waline language and generate configuration json
const walineLang = getWalineLang(Astro.url.pathname, defaultLocale)
const walineConfigJson = JSON.stringify({
serverURL,
lang: walineLang,
emoji,
search,
imageUploader,
})
const { waline: { serverURL = '', emoji = [], search = false, imageUploader = false } = {} } = themeConfig.comment ?? {}
---
<div
id="waline"
class="mt-16"
data-config={walineConfigJson}
>
</div>
></div>
<!-- Not use is:inline or define:vars >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
<script>
import { init } from '@waline/client'
import '@waline/client/style'
<!-- >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
<script
is:inline
define:vars={{
walineLang,
serverURL,
emoji,
search,
imageUploader,
}}
type="module"
>
import { init } from '/assets/waline/waline.js'
function initWaline() {
const walineEl = document.getElementById('waline')
const walineConfig = JSON.parse(walineEl?.dataset.config || '{}')
init({
el: '#waline',
serverURL,
// Share comments on posts in different languages
path: window.location.pathname.replace(/^\/([a-z]{2}(-[a-z]{2})?)\//, '/'),
lang: walineLang,
emoji,
dark: 'html.dark',
requiredMeta: ['nick', 'mail'],
highlighter: false,
texRenderer: false,
imageUploader,
search,
noCopyright: true,
reaction: [],
...walineConfig,
})
}
@ -79,13 +75,13 @@ document.addEventListener('astro:page-load', initWaline)
--at-apply: 'leading-3.6 mt-1.4';
}
#waline .wl-panel {
--at-apply: 'm-0 rounded border-secondary/25'
--at-apply: 'm-0 rounded-lg border-secondary/25'
}
#waline .wl-header {
--at-apply: 'p-0';
}
#waline .wl-header-item {
border-bottom: 1px solid var(--waline-border-color);
--at-apply: 'border-b border-solid border-primary/25';
}
#waline .wl-header label {
--at-apply: 'text-3';
@ -104,14 +100,11 @@ document.addEventListener('astro:page-load', initWaline)
--at-apply: 'min-h-24';
}
#waline .wl-editor::placeholder {
color: var(--waline-light-grey);
--at-apply: 'c-primary/25';
}
#waline .wl-footer {
--at-apply: 'm-2';
}
#waline .wl-info .wl-btn {
--at-apply: 'rounded';
}
#waline .wl-text-number,
#waline .wl-action[title="Markdown Guide"],
#waline .wl-sort,
@ -119,16 +112,10 @@ document.addEventListener('astro:page-load', initWaline)
--at-apply: 'hidden';
}
#waline .wl-emoji-popup {
--at-apply: 'start-0 rounded border-secondary/25';
--at-apply: 'start-0 border-secondary/25 max-w-532px';
}
#waline .wl-emoji-popup .wl-tab-wrapper::-webkit-scrollbar {
--at-apply: 'w-1.2';
}
#waline .wl-emoji-popup .wl-tab-wrapper::-webkit-scrollbar-thumb {
background: oklch(var(--un-preset-theme-colors-secondary) / 0.25);
}
#waline .wl-emoji-popup .wl-tab-wrapper::-webkit-scrollbar-track-piece {
--at-apply: 'bg-transparent';
#waline .wl-emoji-popup .wl-tab-wrapper {
scrollbar-width: thin;
}
#waline .wl-gif-popup {
--at-apply: 'border-secondary/25';
@ -155,7 +142,7 @@ document.addEventListener('astro:page-load', initWaline)
--at-apply: 'leading-6 text-3.5';
}
#waline .wl-time {
color: oklch(var(--un-preset-theme-colors-primary) / 0.75);
--at-apply: 'c-primary/75';
}
#waline .wl-edit,
#waline .wl-delete {
@ -167,7 +154,6 @@ document.addEventListener('astro:page-load', initWaline)
</style>
<!-- Official CSS Variables >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
<!-- https://waline.js.org/reference/client/style.html -->
<style>
#waline {
/* Regular Colors */

View file

@ -10,39 +10,46 @@ const year = Number(startYear) === currentYear
? startYear
: `${startYear}-${currentYear}`
// i18n RSS Path
// i18n RSS Feed Path
const currentLang = getLangFromPath(Astro.url.pathname)
const links = socialLinks.map((link) => {
if (link.name === 'RSS') {
return {
...link,
url: currentLang === defaultLocale ? link.url : `/${currentLang}${link.url}`,
url: currentLang === defaultLocale
? link.url
: `/${currentLang}${link.url}`,
}
}
if (link.name === 'Email') {
return {
...link,
url: `mailto:${link.url}`,
}
}
return link
})
const footerLinkClass = 'highlight-hover footer-highlight-position-fix py-0.8 transition-colors after:bottom-0.15em hover:c-primary'
---
<footer
class="text-3 leading-4.75 font-navbar lg:text-3.5"
class="mb-4 text-3 leading-1.25em font-navbar lg:(mb-0 text-3.5)"
lg="uno-desktop-column bottom-20"
>
<p>
{links.map((link, index) => (
<>
<a class="highlight-hover transition-colors hover:c-primary after:bottom-0!" href={link.url} target="_blank" rel="noopener noreferrer">
{link.name}
</a>
{index < links.length - 1 && ' / '}
<a class={footerLinkClass} href={link.url} target="_blank" rel="noopener noreferrer">{link.name}</a>&nbsp;{index < links.length - 1 && '/'}
</>
))}
</p>
<p>
Powered by <a class="highlight-hover transition-colors hover:c-primary after:bottom-0!" href="https://astro.build/" target="_blank" rel="noopener noreferrer">Astro</a> and <a class="highlight-hover transition-colors hover:c-primary after:bottom-0!" href="https://github.com/radishzzz/astro-theme-retypeset" target="_blank" rel="noopener noreferrer">Retypeset</a>
© {year} {author}
</p>
<p>
© {year} {author}
Powered by <a class={footerLinkClass} href="https://astro.build/" target="_blank" rel="noopener noreferrer">Astro</a> and <a class={footerLinkClass} href="https://github.com/radishzzz/astro-theme-retypeset" target="_blank" rel="noopener noreferrer">Retypeset</a>
</p>
</footer>

View file

@ -32,7 +32,7 @@ const SubtitleTag = isPost ? 'div' : 'h2'
<div
class="box-content inline-block pr-1"
transition:name={`site-title-${currentLang}`}
data-disable-transition-on-theme
data-disable-theme-transition
>
<a
id="site-title-link"

View file

@ -10,7 +10,9 @@ const isTagActive = isTag
const isAboutActive = isAbout
function getNavItemClass(isActive: boolean) {
return isActive ? 'font-bold c-primary highlight-static' : 'hover:(c-primary font-bold) transition-all highlight-hover'
return isActive
? 'highlight-static c-primary font-bold after:bottom-0.5em'
: 'highlight-hover transition-colors transition-font-weight after:bottom-0.5em hover:(c-primary font-bold)'
}
const navItems = [
@ -36,8 +38,8 @@ const navItems = [
aria-label="Site Navigation"
class:list={[
isPost ? 'hidden lg:block' : '',
'mb-10.5 text-3.6 font-semibold leading-8.75 font-navbar',
'lg:(uno-desktop-column text-4 leading-9.72 bottom-50) cjk:tracking-0.02em',
'mb-10.5 text-3.6 font-semibold leading-2.45em font-navbar',
'lg:(uno-desktop-column text-4 bottom-50) cjk:tracking-0.02em',
]}
>
<ul>
@ -45,7 +47,7 @@ const navItems = [
<li>
<a
href={getLocalizedPath(item.href)}
class={item.className}
class={`${item.className} navbar-highlight-position-fix`}
>
{item.label}
</a>

View file

@ -37,13 +37,13 @@ function getPostPath(post: Post) {
>
{/* post title */}
<h3 class="inline">
<h3 class="inline transition-colors hover:c-primary">
<a
class="hover:c-primary cjk:tracking-0.02em"
class="cjk:tracking-0.02em"
lg={isHome ? 'font-medium text-4.5' : ''}
href={getPostPath(post)}
transition:name={`post-${post.data.abbrlink || post.id}-${lang}`}
data-disable-transition-on-theme
data-disable-theme-transition
>
{post.data.title}
</a>
@ -60,9 +60,9 @@ function getPostPath(post: Post) {
{/* mobile post time */}
<div
class="text-3.5 leading-6.875 font-time lg:hidden"
class="py-0.8 text-3.5 font-time lg:hidden"
transition:name={`time-${post.data.abbrlink || post.id}-${lang}`}
data-disable-transition-on-theme
data-disable-theme-transition
>
<PostDate
date={post.data.published}
@ -71,7 +71,7 @@ function getPostPath(post: Post) {
</div>
{/* desktop post time */}
<div class="hidden text-3.65 leading-6.875 font-time lg:(ml-2.5 inline)">
<div class="hidden text-3.65 font-time lg:(ml-2.5 inline)">
<PostDate
date={post.data.published}
minutes={post.remarkPluginFrontmatter.minutes}
@ -82,7 +82,7 @@ function getPostPath(post: Post) {
{isHome && (
<div
class="heti hidden"
lg="mt-2 block"
lg="mt-2.25 block"
>
<p>{generateDescription(post, 'list')}</p>
</div>

View file

@ -5,7 +5,7 @@ import GoBackIcon from '@/assets/icons/go-back.svg';
<button
id="back-button"
class="hidden"
lg="block absolute c-secondary/40 left--10 top-1/2 aspect-square w-4.5 translate-y--1/2 transition-colors ease-out c-secondary active:scale-90 hover:c-primary/80"
lg="block absolute c-secondary/40 left--10 top-1/2 aspect-square w-4.5 translate-y--1/2 transition-colors ease-out c-secondary active:scale-90! hover:c-secondary/80"
aria-label="Go back"
>
<GoBackIcon

View file

@ -8,7 +8,7 @@
<button
id="back-to-top-button"
aria-label="Back to top"
class="fixed bottom-8 right-8 h-10 w-10 rounded-full bg-background transition-all duration-300 ease-out"
class="fixed bottom-8 right-8 h-10 w-10 rounded-full bg-background transition-all ease-out"
>
<svg
xmlns="http://www.w3.org/2000/svg"

View file

@ -1,9 +1,10 @@
<script>
function setupGithubCards() {
const githubCards = document.querySelectorAll('.gc-container')
const githubCards = document.getElementsByClassName('gc-container')
if (githubCards.length === 0)
return
// Create an intersection observer to lazy load GitHub repo data when cards enter viewport
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
@ -13,7 +14,7 @@ function setupGithubCards() {
})
}, { rootMargin: '400px' })
githubCards.forEach(card => observer.observe(card))
Array.from(githubCards).forEach(card => observer.observe(card))
}
async function loadCardData(card: HTMLElement) {
@ -21,11 +22,11 @@ async function loadCardData(card: HTMLElement) {
if (!repo)
return
const avatarEl = card.querySelector('.gc-owner-avatar') as HTMLElement
const descEl = card.querySelector('.gc-repo-description') as HTMLElement
const starsEl = card.querySelector('.gc-stars-count') as HTMLElement
const forksEl = card.querySelector('.gc-forks-count') as HTMLElement
const licenseEl = card.querySelector('.gc-license-info') as HTMLElement
const avatarEl = card.getElementsByClassName('gc-owner-avatar')[0] as HTMLElement
const descEl = card.getElementsByClassName('gc-repo-description')[0] as HTMLElement
const starsEl = card.getElementsByClassName('gc-stars-count')[0] as HTMLElement
const forksEl = card.getElementsByClassName('gc-forks-count')[0] as HTMLElement
const licenseEl = card.getElementsByClassName('gc-license-info')[0] as HTMLElement
try {
const response = await fetch(`https://api.github.com/repos/${repo}`)

View file

@ -0,0 +1,99 @@
<script>
import { gsap } from 'gsap'
function setupPostPageAnimation() {
const allElements = Array.from(document.querySelectorAll('#gsap-post-page-content > *, #gsap-post-page-tags, #waline'))
const tocList = document.getElementById('toc-list')
const tocListChildren = tocList ? Array.from(tocList.children) : []
const dateElement = document.getElementById('gsap-post-page-date')
const backButton = document.getElementById('back-button')
const tocIcon = document.getElementById('toc-icon')
const tocContainer = document.getElementById('toc-container')
const isLargeScreen = window.matchMedia('(min-width: 1024px)').matches
const isSmallScreen = window.matchMedia('(max-width: 1535px)').matches
// Post Content + Tags + Comments
// First 15 elements
gsap.from(allElements.slice(0, 15), {
opacity: 0,
y: '2rem',
duration: 0.3,
delay: 0.1,
ease: 'power2.out',
stagger: 0.05,
})
// Rest elements as the 16 element
if (allElements.length > 15) {
gsap.from(allElements.slice(15), {
opacity: 0,
y: '2rem',
duration: 0.3,
delay: 0.1 + 0.05 * 15,
ease: 'power2.out',
})
}
// Desktop Animations
if (isLargeScreen) {
// Post Date
if (dateElement) {
gsap.from(dateElement, {
opacity: 0,
y: '1rem',
duration: 0.3,
delay: 0.1,
ease: 'power2.out',
})
}
// TOC Icon
if (tocIcon) {
gsap.from(tocIcon, {
opacity: 0,
y: '0.5rem',
duration: 0.3,
delay: 0.125,
ease: 'power2.out',
})
}
// Toc List
if (tocListChildren.length > 0) {
gsap.from(tocListChildren, {
opacity: 0,
y: '1rem',
duration: 0.3,
delay: 0.15,
ease: 'power2.out',
stagger: 0.025,
})
}
// Back Button
if (backButton) {
gsap.from(backButton, {
opacity: 0,
x: '0.5rem',
duration: 0.3,
delay: 0.15,
ease: 'power2.out',
})
}
}
// Mobile Animation (for screens smaller than 1536px)
if (isSmallScreen && tocContainer) {
gsap.from(tocContainer, {
opacity: 0,
y: '2rem',
duration: 0.3,
delay: 0.1,
ease: 'power2.out',
})
}
}
setupPostPageAnimation()
document.addEventListener('astro:after-swap', setupPostPageAnimation)
</script>

View file

@ -10,7 +10,8 @@ function setupPhotoSwipe() {
if (bodyElement.hasAttribute('data-photoswipe-initialized'))
return
const images = document.querySelectorAll('article.heti img')
const article = document.querySelector('article.heti')
const images = article ? article.getElementsByTagName('img') : []
if (images.length === 0)
return

View file

@ -1,110 +0,0 @@
<script>
import { OverlayScrollbars } from 'overlayscrollbars'
function setupScrollbar() {
// Add scrollbar to body
const bodyElement = document.body
if (!bodyElement.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({
target: bodyElement,
cancel: {
nativeScrollbarsOverlaid: true,
},
}, {
scrollbars: {
theme: 'scrollbar-body',
autoHide: 'scroll',
autoHideDelay: 800,
},
overflow: {
x: 'hidden',
},
})
bodyElement.setAttribute('data-scrollbar-initialized', 'true')
}
// Add scrollbar to TOC content
const tocElement = document.getElementById('toc-content')
if (tocElement && !tocElement.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({
target: tocElement,
}, {
scrollbars: {
theme: 'scrollbar-widget',
autoHide: 'never',
},
overflow: {
x: 'hidden',
},
})
tocElement.setAttribute('data-scrollbar-initialized', 'true')
}
// Add scrollbar to code blocks
const preElements = document.querySelectorAll('pre')
preElements.forEach((pre) => {
if (!pre.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({
target: pre,
}, {
scrollbars: {
theme: 'scrollbar-widget',
autoHide: 'leave',
autoHideDelay: 500,
},
overflow: {
y: 'hidden',
},
})
pre.setAttribute('data-scrollbar-initialized', 'true')
}
})
}
setupScrollbar()
document.addEventListener('astro:after-swap', setupScrollbar)
</script>
<style is:global>
@import 'overlayscrollbars/overlayscrollbars.css';
.scrollbar-body {
--os-size: 0.8rem;
--os-padding-perpendicular: 0.15rem;
--os-padding-axis: 0.2rem;
--os-handle-border-radius: 99rem;
--os-handle-perpendicular-size: 75%;
--os-handle-perpendicular-size-hover: 100%;
--os-handle-perpendicular-size-active: 100%;
--os-handle-interactive-area-offset: 0.2rem;
--os-handle-bg: oklch(var(--un-preset-theme-colors-secondary) / 0.25);
--os-handle-bg-hover: oklch(var(--un-preset-theme-colors-secondary) / 0.40);
--os-handle-bg-active: oklch(var(--un-preset-theme-colors-secondary) / 0.40);
--os-handle-max-size: 80%;
--os-handle-min-size: 12%;
}
.scrollbar-widget {
--os-size: 0.4rem;
--os-padding-perpendicular: 0;
--os-padding-axis: 0.05rem;
--os-handle-border-radius: 99rem;
--os-handle-perpendicular-size: 75%;
--os-handle-perpendicular-size-hover: 100%;
--os-handle-perpendicular-size-active: 100%;
--os-handle-interactive-area-offset: 0.2rem;
--os-handle-bg: oklch(var(--un-preset-theme-colors-secondary) / 0.15);
--os-handle-bg-hover: oklch(var(--un-preset-theme-colors-secondary) / 0.30);
--os-handle-bg-active: oklch(var(--un-preset-theme-colors-secondary) / 0.30);
--os-handle-min-size: 12%;
}
@media (min-width: 1536px) {
#toc-content .os-scrollbar {
--at-apply: 'hidden';
}
}
</style>

View file

@ -19,38 +19,51 @@ const filteredHeadings = headings.filter(heading =>
---
{filteredHeadings.length > 0 && (
<div class="mb-4 uno-round-border bg-secondary/5 2xl:(fixed left-0 top-43.5 max-w-[min(calc(50vw-38rem),13rem)] border-none bg-transparent)">
// TOC Container
<div
id="toc-container"
class="mb-4 uno-round-border bg-secondary/5 2xl:(fixed left-0 top-43.5 max-w-[min(calc(50vw-38rem),13rem)] border-none bg-secondary/0)"
>
{/* Hidden Checkbox */}
<input
type="checkbox"
id="toc-toggle"
class="accordion-toggle"
hidden
/>
<div class="relative h-12 w-full bg-transparent">
{/* TOC Toggle */}
<div class="relative h-12 w-full">
<label
for="toc-toggle"
class="absolute inset-0 flex cursor-pointer items-center 2xl:(static flex c-secondary/40 transition-colors ease-out hover:c-secondary/80)"
>
{/* Title on Mobile */}
<span class="toc-title">
{currentUI.toc}
</span>
{/* Icon on Desktop */}
<TocIcon
id="toc-icon"
aria-hidden="true"
class="ml-4 hidden aspect-square w-4.2 2xl:(mt-4 block origin-center active:scale-90)"
class="ml-1 hidden aspect-square w-4.2 2xl:(mt-4 block origin-center active:scale-90!)"
fill="currentColor"
/>
</label>
</div>
{/* Expandable content wrapper */}
{/* Expandable Content Wrapper with Accordion Animation */}
<div class="accordion-wrapper">
<nav
id="toc-content"
class="accordion-content"
aria-label="Table of Contents"
>
<ul class="toc-list">
{/* TOC List */}
<ul
id="toc-list"
class="toc-list"
>
{filteredHeadings.map(heading => (
<li
class:list={{
@ -80,21 +93,21 @@ const filteredHeadings = headings.filter(heading =>
<!-- Override heti default styles >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -->
<style>
.toc-title {
--at-apply: 'font-semibold ml-4 select-none 2xl:hidden';
--at-apply: 'font-semibold ml-4 2xl:hidden';
}
.toc-list {
--at-apply: 'list-none pl-0 space-y-2 mt-1 mb-4 2xl:space-y-1.2';
--at-apply: 'list-none pb-3.3 pl-0 space-y-2 mt-1 mb-4 2xl:space-y-1.2';
}
.toc-link-h2, .toc-link-h3, .toc-link-h4 {
--at-apply: 'text-sm no-underline font-normal text-balance select-none 2xl:(text-3.2 c-secondary/60 transition-colors ease-in hover:(c-secondary font-medium))';
--at-apply: 'text-sm no-underline font-normal text-balance 2xl:(text-3.2 c-secondary/60 transition-colors transition-font-weight duration-300 ease-out hover:(c-secondary font-medium))';
}
/* Initial collapsed state with zero height grid row */
.accordion-wrapper {
--at-apply: 'grid rows-[0fr] duration-300 ease-in-out';
--at-apply: 'grid rows-[0fr] transition-all duration-350 ease-in-out';
}
.accordion-content {
--at-apply: 'overflow-hidden max-h-66 2xl:(max-h-[calc(100vh-21.5rem)]) pl-4 pr-6';
--at-apply: 'max-h-66 overflow-hidden pl-4 pr-6 2xl:(max-h-[calc(100vh-26rem)] pl-1)';
}
/* When toggle is checked, expand the wrapper to show content */
@ -104,6 +117,10 @@ const filteredHeadings = headings.filter(heading =>
.accordion-toggle:checked ~ .accordion-wrapper .accordion-content {
--at-apply: 'overflow-y-auto';
}
#toc-content {
scrollbar-width: thin;
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0.15) transparent;
}
@media (min-width: 1536px) {
.accordion-wrapper {
@ -121,6 +138,12 @@ const filteredHeadings = headings.filter(heading =>
.toc-link-active {
--at-apply: 'c-secondary font-medium';
}
#toc-content {
--at-apply: 'scrollbar-hidden';
}
#toc-content::-webkit-scrollbar {
display: none;
}
}
</style>
@ -131,7 +154,7 @@ function setupTOCHighlight() {
if (!tocContent)
return
const tocLinks = tocContent.querySelectorAll('a')
const tocLinks = tocContent.getElementsByTagName('a')
if (tocLinks.length === 0)
return
@ -140,7 +163,7 @@ function setupTOCHighlight() {
// Build mapping from heading IDs to TOC links
const headingMap = new Map<string, HTMLAnchorElement>()
tocLinks.forEach((link) => {
Array.from(tocLinks).forEach((link) => {
const id = link.getAttribute('href')?.substring(1)
if (id)
headingMap.set(id, link as HTMLAnchorElement)

View file

@ -60,6 +60,8 @@ export const themeConfig: ThemeConfig = {
dateFormat: 'YYYY-MM-DD', // YYYY-MM-DD, MM-DD-YYYY, DD-MM-YYYY, MONTH DAY YYYY, DAY MONTH YYYY
// enable KaTeX for mathematical formulas rendering
katex: true, // true, false
// enable table of contents for all posts by default
toc: true, // true, false
},
// GLOBAL SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
@ -139,16 +141,16 @@ export const themeConfig: ThemeConfig = {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
],
// year of website start
startYear: 2024,
startYear: 2025,
},
// FOOTER SETTINGS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> END
@ -157,8 +159,6 @@ export const themeConfig: ThemeConfig = {
// link prefetch
// docs: https://docs.astro.build/en/guides/prefetch/#prefetch-strategies
linkPrefetch: 'viewport', // hover, tap, viewport, load
// comment server url
commentURL: 'https://retypeset-comment.radishzz.cc',
// image hosting url
imageHostURL: 'https://image.radishzz.cc',
// custom google analytics js

View file

@ -1,6 +1,6 @@
import { allLocales } from '@/config'
import { glob } from 'astro/loaders'
import { defineCollection, z } from 'astro:content'
import { allLocales, themeConfig } from '@/config'
const posts = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }),
@ -15,7 +15,7 @@ const posts = defineCollection({
// Advanced
draft: z.boolean().optional().default(false),
pin: z.number().int().min(0).max(99).optional().default(0),
toc: z.boolean().optional().default(true),
toc: z.boolean().optional().default(themeConfig.global.toc),
lang: z.enum(['', ...allLocales]).optional().default(''),
abbrlink: z.string().optional().default('').refine(
abbrlink => !abbrlink || /^[a-z0-9\-]*$/.test(abbrlink),

View file

@ -17,7 +17,7 @@ $$
t=\frac{1}{|G|}\sum_{g\in G}|\text{Fix}(g)|
$$
For each integer $n\ge2$, the quotient group $\mathbb{Z}/n\mathbb{Z}$ is a cyclic group generated by $1+n\mathbb{Z}$ and so $\color{red}{\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n}$.
For each integer $n\ge2$, the quotient group $\mathbb{Z}/n\mathbb{Z}$ is a cyclic group generated by $1+n\mathbb{Z}$ and so $\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n$.
The quotient group $\mathbb{R}/\mathbb{Z}$ is isomorphic to $([0,1),+_1)$, the group of real numbers in the interval $[0,1)$, under addition modulo 1.

View file

@ -17,7 +17,7 @@ $$
t=\frac{1}{|G|}\sum_{g\in G}|\text{Fix}(g)|
$$
Para cada entero $n\ge2$, el grupo cociente $\mathbb{Z}/n\mathbb{Z}$ es un grupo cíclico generado por $1+n\mathbb{Z}$ y por tanto $\color{red}{\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n}$.
Para cada entero $n\ge2$, el grupo cociente $\mathbb{Z}/n\mathbb{Z}$ es un grupo cíclico generado por $1+n\mathbb{Z}$ y por tanto $\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n$.
El grupo cociente $\mathbb{R}/\mathbb{Z}$ es isomorfo a $([0,1),+_1)$, el grupo de números reales en el intervalo $[0,1)$, bajo la adición módulo 1.

View file

@ -17,7 +17,7 @@ $$
t=\frac{1}{|G|}\sum_{g\in G}|\text{Fix}(g)|
$$
各整数 $n\ge2$ に対して、商群 $\mathbb{Z}/n\mathbb{Z}$ は $1+n\mathbb{Z}$ によって生成される巡回群であり、したがって $\color{red}{\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n}$ となります。
各整数 $n\ge2$ に対して、商群 $\mathbb{Z}/n\mathbb{Z}$ は $1+n\mathbb{Z}$ によって生成される巡回群であり、したがって $\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n$ となります。
商群 $\mathbb{R}/\mathbb{Z}$ は $([0,1),+_1)$ と同型です。これは区間 $[0,1)$ 上の実数のモジュロ1の加法群です。

View file

@ -17,7 +17,7 @@ $$
t=\frac{1}{|G|}\sum_{g\in G}|\text{Fix}(g)|
$$
Для каждого целого числа $n\ge2$ фактор-группа $\mathbb{Z}/n\mathbb{Z}$ является циклической группой, порождённой элементом $1+n\mathbb{Z}$, и поэтому $\color{red}{\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n}$.
Для каждого целого числа $n\ge2$ фактор-группа $\mathbb{Z}/n\mathbb{Z}$ является циклической группой, порождённой элементом $1+n\mathbb{Z}$, и поэтому $\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n$.
Фактор-группа $\mathbb{R}/\mathbb{Z}$ изоморфна $([0,1),+_1)$, группе вещественных чисел в интервале $[0,1)$ с операцией сложения по модулю 1.

View file

@ -17,7 +17,7 @@ $$
t=\frac{1}{|G|}\sum_{g\in G}|\text{Fix}(g)|
$$
對於每個整數 $n\ge2$,商群 $\mathbb{Z}/n\mathbb{Z}$ 是由 $1+n\mathbb{Z}$ 生成的循環群,因此 $\color{red}{\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n}$。
對於每個整數 $n\ge2$,商群 $\mathbb{Z}/n\mathbb{Z}$ 是由 $1+n\mathbb{Z}$ 生成的循環群,因此 $\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n$。
商群 $\mathbb{R}/\mathbb{Z}$ 同構於 $([0,1),+_1)$,即區間 $[0,1)$ 上以 1 為模的實數加法群。

View file

@ -17,7 +17,7 @@ $$
t=\frac{1}{|G|}\sum_{g\in G}|\text{Fix}(g)|
$$
对于每个整数 $n\ge2$,商群 $\mathbb{Z}/n\mathbb{Z}$ 是由 $1+n\mathbb{Z}$ 生成的循环群,因此 $\color{red}{\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n}$。
对于每个整数 $n\ge2$,商群 $\mathbb{Z}/n\mathbb{Z}$ 是由 $1+n\mathbb{Z}$ 生成的循环群,因此 $\mathbb{Z}/n\mathbb{Z}\cong\mathbb{Z}_n$。
商群 $\mathbb{R}/\mathbb{Z}$ 同构于 $([0,1),+_1)$,即区间 $[0,1)$ 上以 1 为模的实数加法群。

View file

@ -17,9 +17,9 @@ To create automatic figure captions, use the standard Markdown image syntax `![a
### Syntax
```
![Image description](./full/or/relative/path/of/image)
![Image description](https://image.example.com/image-01.webp)
![_Image description](./full/or/relative/path/of/image)
![_Image description](https://image.example.com/image-01.webp)
```
### Output

View file

@ -17,9 +17,9 @@ Para crear leyendas automáticas para figuras, utilice la sintaxis estándar de
### Sintaxis
```
![Descripción de la imagen](./full/or/relative/path/of/image)
![Descripción de la imagen](https://image.example.com/image-01.webp)
![_Descripción de la imagen](./full/or/relative/path/of/image)
![_Descripción de la imagen](https://image.example.com/image-01.webp)
```
### Resultado

View file

@ -17,9 +17,9 @@ abbrlink: markdown-extended-features
### 構文
```
![画像の説明](./full/or/relative/path/of/image)
![画像の説明](https://image.example.com/image-01.webp)
![_画像の説明](./full/or/relative/path/of/image)
![_画像の説明](https://image.example.com/image-01.webp)
```
### 効果

View file

@ -17,9 +17,9 @@ abbrlink: markdown-extended-features
### Синтаксис
```
![Описание изображения](./full/or/relative/path/of/image)
![Описание изображения](https://image.example.com/image-01.webp)
![_Описание изображения](./full/or/relative/path/of/image)
![_Описание изображения](https://image.example.com/image-01.webp)
```
### Результат

View file

@ -17,9 +17,9 @@ abbrlink: markdown-extended-features
### 語法
```
![圖片描述](./full/or/relative/path/of/image)
![圖片描述](https://image.example.com/image-01.webp)
![_圖片描述](./full/or/relative/path/of/image)
![_圖片描述](https://image.example.com/image-01.webp)
```
### 效果

View file

@ -17,9 +17,9 @@ abbrlink: markdown-extended-features
### 语法
```
![图片描述](./full/or/relative/path/of/image)
![图片描述](https://image.example.com/image-01.webp)
![_图片描述](./full/or/relative/path/of/image)
![_图片描述](https://image.example.com/image-01.webp)
```
### 效果

View file

@ -61,7 +61,9 @@ To add an image, add an exclamation mark `!`, followed by alt text in brackets `
### Syntax
```
![Image Description](./full/or/relative/path/of/image)
![Image Description](../_images/image-01.jpeg)
![Image Description](https://image.example.com/image-01.webp)
```
### Output
@ -211,51 +213,3 @@ To create a code block, add three backticks ```` ``` ```` at the start and end o
- Milk
- Cheese
```
#### Output
- Fruit
- Apple
- Orange
- Banana
- Dairy
- Milk
- Cheese
## Other Elements
Including `<sup>` superscript, `<sub>` subscript, `<abbr>` abbreviation, `<del>` strikethrough, `<u>` wavy underline, `<kbd>` keyboard input, and `<mark>` highlight.
### Syntax
```html
H<sub>2</sub>O
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
Good writers always check for <u title="spelling">speling</u> mistakes.
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
There is <del>nothing</del> no code either good or bad, but running it makes it so.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
```
### Output
H<sub>2</sub>O
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
Good writers always check for <u title="spelling">speling</u> mistakes.
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
There is <del>nothing</del> no code either good or bad, but running it makes it so.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.

View file

@ -61,7 +61,9 @@ Para agregar una imagen, añada un signo de exclamación `!`, seguido de texto a
### Sintaxis
```
![Descripción de la Imagen](./full/or/relative/path/of/image)
![Descripción de la Imagen](../_images/image-01.jpeg)
![Descripción de la Imagen](https://image.example.com/image-01.webp)
```
### Resultado

View file

@ -61,7 +61,9 @@ abbrlink: markdown-style-guide
### 構文
```
![画像の説明](./full/or/relative/path/of/image)
![画像の説明](../_images/image-01.jpeg)
![画像の説明](https://image.example.com/image-01.webp)
```
### 効果

View file

@ -61,7 +61,9 @@ Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sap
### Синтаксис
```
![Описание изображения](./full/or/relative/path/of/image)
![Описание изображения](../_images/image-01.jpeg)
![Описание изображения](https://image.example.com/image-01.webp)
```
### Результат

View file

@ -61,7 +61,9 @@ abbrlink: markdown-style-guide
### 語法
```
![圖片描述](./full/or/relative/path/of/image)
![圖片描述](../_images/image-01.jpeg)
![圖片描述](https://image.example.com/image-01.webp)
```
### 效果

View file

@ -61,7 +61,9 @@ abbrlink: markdown-style-guide
### 语法
```
![图片描述](./full/or/relative/path/of/image)
![图片描述](../_images/image-01.jpeg)
![图片描述](https://image.example.com/image-01.webp)
```
### 效果

View file

@ -86,6 +86,8 @@ global: {
dateFormat: 'YYYY-MM-DD'
// enable KaTeX for mathematical formulas rendering
katex: true // true, false
// enable table of contents for all posts by default
toc: true // true, false
}
```
@ -163,16 +165,16 @@ footer: {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
]
// year of website start
startYear: 2024
startYear: 2025
}
```
@ -182,8 +184,6 @@ footer: {
preload: {
// link prefetch strategies
linkPrefetch: 'viewport' // hover, tap, viewport, load
// comment server url
commentURL: 'https://retypeset-comment.radishzz.cc'
// image hosting url
imageHostURL: 'https://image.radishzz.cc'
// custom google analytics js
@ -236,7 +236,7 @@ const EXCERPT_LENGTHS: Record<ExcerptScene, {
### Open Graph
Open Graph social image styles.
[Open Graph social card](https://orcascan.com/tools/open-graph-validator?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fen%2Fposts%2Ftheme-guide%2F) styles.
```ts
// src/pages/og/[...image].ts
@ -265,7 +265,7 @@ getImageOptions: (_path, page) => ({
### RSS Feed
RSS feed page styles.
[RSS feed page](https://retypeset.radishzz.cc/en/rss.xml) styles.
```html
<!-- public/rss/rss-style.xsl -->
@ -317,7 +317,7 @@ Pins the article to the top. The higher the number, the higher the priority of t
#### toc
Generate table of contents. Shows h2 to h4 headings. Default is true.
Generate table of contents. Shows h2 to h4 headings. Uses the global `global.toc` configuration by default, but can be overridden individually in each article.
#### lang

View file

@ -86,6 +86,8 @@ global: {
dateFormat: 'YYYY-MM-DD'
// habilitar KaTeX para renderizar fórmulas matemáticas
katex: true // true, false
// habilitar tabla de contenidos para todos los artículos por defecto
toc: true // true, false
}
```
@ -163,16 +165,16 @@ footer: {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
]
// año de inicio del sitio web
startYear: 2024
startYear: 2025
}
```
@ -182,8 +184,6 @@ footer: {
preload: {
// estrategias de precarga de enlaces
linkPrefetch: 'viewport' // hover, tap, viewport, load
// URL del servidor de comentarios
commentURL: 'https://retypeset-comment.radishzz.cc'
// URL de alojamiento de imágenes
imageHostURL: 'https://image.radishzz.cc'
// js personalizado de google analytics
@ -236,7 +236,7 @@ const EXCERPT_LENGTHS: Record<ExcerptScene, {
### Open Graph
Estilos de imágenes sociales Open Graph.
Estilos de [tarjetas sociales Open Graph](https://orcascan.com/tools/open-graph-validator?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fes%2Fposts%2Ftheme-guide%2F).
```ts
// src/pages/og/[...image].ts
@ -265,7 +265,7 @@ getImageOptions: (_path, page) => ({
### Canal RSS
Estilos de página del feed RSS.
Estilos de [página del feed RSS](https://retypeset.radishzz.cc/es/rss.xml).
```html
<!-- public/rss/rss-style.xsl -->
@ -317,7 +317,7 @@ Fija el artículo en la parte superior. Cuanto mayor sea el número, mayor será
#### toc
Genera tabla de contenidos. Muestra encabezados h2 a h4. El valor predeterminado es true.
Genera tabla de contenidos. Muestra encabezados de h2 a h4. Utiliza la configuración global `global.toc` por defecto, pero puede ser modificada individualmente en cada artículo.
#### lang

View file

@ -86,6 +86,8 @@ global: {
dateFormat: 'YYYY-MM-DD'
// 数式表示のためのKaTeXを有効化
katex: true // true, false
// デフォルトですべての記事に目次を表示
toc: true // true, false
}
```
@ -163,16 +165,16 @@ footer: {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
]
// サイト開始年
startYear: 2024
startYear: 2025
}
```
@ -182,8 +184,6 @@ footer: {
preload: {
// リンクプリフェッチ戦略
linkPrefetch: 'viewport' // hover, tap, viewport, load
// コメントサーバー URL
commentURL: 'https://retypeset-comment.radishzz.cc'
// 画像ホスティング URL
imageHostURL: 'https://image.radishzz.cc'
// カスタム Google Analytics JS
@ -236,7 +236,7 @@ const EXCERPT_LENGTHS: Record<ExcerptScene, {
### Open Graph
Open Graphソーシャル画像スタイル。
[Open Graphソーシャルカード](https://orcascan.com/tools/open-graph-validator?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fja%2Fposts%2Ftheme-guide%2F)スタイル。
```ts
// src/pages/og/[...image].ts
@ -265,7 +265,7 @@ getImageOptions: (_path, page) => ({
### RSSフィード
RSSフィードページスタイル。
[RSSフィードページ](https://retypeset.radishzz.cc/ja/rss.xml)スタイル。
```html
<!-- public/rss/rss-style.xsl -->
@ -317,7 +317,7 @@ abbrlink: theme-guide
#### toc
目次を自動生成するかどうか。h2からh4までの見出しを表示します。デフォルトは true
目次を生成するかどうか。h2からh4までの見出しを表示します。デフォルトではグローバル設定 `global.toc` を使用しますが、記事ごとに個別に設定することも可能です
#### lang

View file

@ -86,6 +86,8 @@ global: {
dateFormat: 'YYYY-MM-DD'
// включить KaTeX для отображения математических формул
katex: true // true, false
// включить оглавление для всех статей по умолчанию
toc: true // true, false
}
```
@ -163,16 +165,16 @@ footer: {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
]
// год начала работы веб-сайта
startYear: 2024
startYear: 2025
}
```
@ -182,8 +184,6 @@ footer: {
preload: {
// стратегии предзагрузки ссылок
linkPrefetch: 'viewport' // hover, tap, viewport, load
// URL сервера комментариев
commentURL: 'https://retypeset-comment.radishzz.cc'
// URL хостинга изображений
imageHostURL: 'https://image.radishzz.cc'
// пользовательский скрипт Google Analytics
@ -236,7 +236,7 @@ const EXCERPT_LENGTHS: Record<ExcerptScene, {
### Open Graph
Стили изображений Open Graph для социальных сетей.
Стили [карточек Open Graph для социальных сетей](https://orcascan.com/tools/open-graph-validator?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fru%2Fposts%2Ftheme-guide%2F).
```ts
// src/pages/og/[...image].ts
@ -265,7 +265,7 @@ getImageOptions: (_path, page) => ({
### RSS-лента
Стили страницы RSS-ленты.
Стили [страницы RSS-ленты](https://retypeset.radishzz.cc/ru/rss.xml).
```html
<!-- public/rss/rss-style.xsl -->
@ -317,7 +317,7 @@ abbrlink: theme-guide
#### toc
Генерировать оглавление. Показывает заголовки от h2 до h4. По умолчанию — true.
Генерировать оглавление. Показывает заголовки от h2 до h4. По умолчанию использует глобальный параметр `global.toc`, но может быть изменен индивидуально в каждой статье.
#### lang

View file

@ -86,6 +86,8 @@ global: {
dateFormat: 'YYYY-MM-DD'
// 啟用 KaTeX 數學公式渲染
katex: true // true, false
// 預設為所有文章開啟目錄
toc: true // true, false
}
```
@ -163,16 +165,16 @@ footer: {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
]
// 建站年份
startYear: 2024
startYear: 2025
}
```
@ -182,8 +184,6 @@ footer: {
preload: {
// 鏈接預加載策略
linkPrefetch: 'viewport' // hover, tap, viewport, load
// 評論服務器地址
commentURL: 'https://retypeset-comment.radishzz.cc'
// 圖床地址
imageHostURL: 'https://image.radishzz.cc'
// 定制 google analytics js
@ -236,7 +236,7 @@ const EXCERPT_LENGTHS: Record<ExcerptScene, {
### Open Graph
Open Graph 社交圖片樣式。
[Open Graph 社交卡片](https://orcascan.com/tools/open-graph-validator?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fzh-tw%2Fposts%2Ftheme-guide%2F) 樣式。
```ts
// src/pages/og/[...image].ts
@ -265,7 +265,7 @@ getImageOptions: (_path, page) => ({
### RSS 訂閱
RSS 訂閱頁配色。
[RSS 訂閱頁](https://retypeset.radishzz.cc/zh-tw/rss.xml) 配色。
```html
<!-- public/rss/rss-style.xsl -->
@ -317,7 +317,7 @@ abbrlink: theme-guide
#### toc
是否生成目錄。顯示 h2 至 h4 標題。預設為 true
是否生成目錄。顯示 h2 至 h4 標題。預設為全域配置 `global.toc` 的選項,可在文章中單獨設定以覆蓋全域配置
#### lang

View file

@ -86,6 +86,8 @@ global: {
dateFormat: 'YYYY-MM-DD'
// 启用 KaTeX 数学公式渲染
katex: true // true, false
// 默认为所有文章开启目录
toc: true // true, false
}
```
@ -163,16 +165,16 @@ footer: {
url: 'https://github.com/radishzzz/astro-theme-retypeset',
},
{
name: 'X',
url: 'https://x.com/radishzz_',
},
name: 'Email',
url: 'email@radishzz.cc',
}
// {
// name: 'Email',
// url: 'https://example@gmail.com',
// }
// name: 'X',
// url: 'https://x.com/radishzz_',
// },
]
// 建站年份
startYear: 2024
startYear: 2025
}
```
@ -182,8 +184,6 @@ footer: {
preload: {
// 链接预加载策略
linkPrefetch: 'viewport' // hover, tap, viewport, load
// 评论服务器地址
commentURL: 'https://retypeset-comment.radishzz.cc'
// 图床地址
imageHostURL: 'https://image.radishzz.cc'
// 定制 google analytics js
@ -236,7 +236,7 @@ const EXCERPT_LENGTHS: Record<ExcerptScene, {
### Open Graph
Open Graph 社交图片样式。
[Open Graph 社交卡片](https://orcascan.com/tools/open-graph-validator?url=https%3A%2F%2Fretypeset.radishzz.cc%2Fposts%2Ftheme-guide%2F) 样式。
```ts
// src/pages/og/[...image].ts
@ -265,7 +265,7 @@ getImageOptions: (_path, page) => ({
### RSS 订阅
RSS 订阅页配色。
[RSS 订阅页](https://retypeset.radishzz.cc/rss.xml) 配色。
```html
<!-- public/rss/rss-style.xsl -->
@ -317,7 +317,7 @@ abbrlink: theme-guide
#### toc
是否生成目录。显示 h2 至 h4 标题。默认为 true
是否生成目录。显示 h2 至 h4 标题。默认为全局配置 `global.toc` 的选项,可在文章中单独设置以覆盖全局配置
#### lang

View file

@ -18,10 +18,11 @@ const langCode = currentLang === defaultLocale ? '' : `${currentLang}/`
const { title, subtitle, description, author, url, favicon, i18nTitle } = themeConfig.site
const { mode, light: { background: lightMode }, dark: { background: darkMode } } = themeConfig.color
const { katex } = themeConfig.global
const { katex: katexEnabled } = themeConfig.global
const { enabled: commentEnabled = false, waline: { serverURL: walineServerURL = '' } = {} } = themeConfig.comment ?? {}
const { verification = {}, twitterID = '', googleAnalyticsID = '', umamiAnalyticsID = '', apiflashKey = '' } = themeConfig.seo ?? {}
const { google = '', bing = '', yandex = '', baidu = '' } = verification
const { commentURL = '', customGoogleAnalyticsJS = '', customUmamiAnalyticsJS = '' } = themeConfig.preload
const { customGoogleAnalyticsJS = '', customUmamiAnalyticsJS = '' } = themeConfig.preload
const initMetaTheme = mode === 'dark' ? darkMode : lightMode
const siteTitle = i18nTitle ? currentUI.title : title
@ -54,9 +55,9 @@ const pageImage = postSlug
<link rel="preload" href="/fonts/EarlySummer-Subset.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Snell-Black.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Snell-Bold.woff2" as="font" type="font/woff2" crossorigin />
{katex && <link rel="stylesheet" href={katexCSS} />}
{commentURL && <link rel="preconnect" href={commentURL} crossorigin />}
{commentURL && <link rel="dns-prefetch" href={commentURL} />}
<link rel="preload" href="/fonts/STIX-Italic.woff2" as="font" type="font/woff2" crossorigin />
{commentEnabled && walineServerURL && <link rel="stylesheet" href="/assets/waline/waline.css" />}
{katexEnabled && <link rel="stylesheet" href={katexCSS} />}
<link rel="alternate" href="/rss.xml" type="application/rss+xml" title="RSS Feed" />
<link rel="alternate" href="/atom.xml" type="application/atom+xml" title="Atom Feed" />
<link rel="sitemap" href="/sitemap-index.xml" />
@ -111,7 +112,7 @@ function isCurrentDark() {
function initTheme(doc = document) {
const isDark = isCurrentDark()
doc.documentElement.classList.toggle('dark', isDark)
const metaTheme = doc.querySelector('meta[name="theme-color"]')
const metaTheme = doc.head.querySelector('meta[name="theme-color"]')
if (metaTheme) {
metaTheme.setAttribute('content', isDark ? darkMode : lightMode)
}

View file

@ -4,15 +4,16 @@ import Footer from '@/components/Footer.astro'
import Header from '@/components/Header.astro'
import Navbar from '@/components/Navbar.astro'
import GithubCard from '@/components/Widgets/GithubCard.astro'
import GsapAnimation from '@/components/Widgets/GsapAnimation.astro'
import PhotoSwipe from '@/components/Widgets/PhotoSwipe.astro'
import Scrollbar from '@/components/Widgets/Scrollbar.astro'
import themeConfig from '@/config'
import Head from '@/layouts/Head.astro'
import { getPageInfo } from '@/utils/page'
import '@/styles/global.css'
import '@/styles/font.css'
import '@/styles/heti.css'
import '@/styles/extend.css'
import '@/styles/font.css'
import '@/styles/global.css'
import '@/styles/markdown.css'
import '@/styles/transition.css'
interface Props {
postTitle?: string
@ -25,22 +26,20 @@ const { postTitle, postDescription, postSlug, supportedLangs = [] } = Astro.prop
const { isPost } = getPageInfo(Astro.url.pathname)
const fontStyle = themeConfig.global.fontStyle === 'serif' ? 'font-serif' : 'font-sans'
const MarginBottom = isPost && themeConfig.comment?.enabled
? 'mb-10' // Post page with comment system
: 'mb-12' // Other pages without comment system
? 'mb-10' // Post page with comments
: 'mb-12' // Other pages without comments
---
<html
lang={Astro.currentLocale}
class:list={[fontStyle, { 'scroll-smooth': isPost }]}
data-overlayscrollbars-initialize
>
<Head {postTitle} {postDescription} {postSlug} />
<body data-overlayscrollbars-initialize>
<body>
<div
class="mx-auto max-w-205.848 min-h-vh w-full min-h-dvh"
p="x-[min(7.25vw,3.731rem)] y-9"
lg="p-0 min-h-full max-w-[min(calc(75vw-16rem),44rem)] mx-[max(5.625rem,calc(50vw-34.375rem))] my-20"
lg="mx-[max(5rem,calc(50vw-35rem))] my-20 max-w-[min(calc(75vw-16rem),44rem)] min-h-full p-0"
>
<Header />
<Navbar />
@ -49,8 +48,8 @@ const MarginBottom = isPost && themeConfig.comment?.enabled
</main>
<Footer />
</div>
<GsapAnimation />
<Button supportedLangs={supportedLangs} />
<Scrollbar />
<GithubCard />
<PhotoSwipe />
</body>

View file

@ -6,8 +6,15 @@ import Layout from '@/layouts/Layout.astro'
<!-- Decorative Line -->
<div class="uno-decorative-line"></div>
<!-- Page Not Found -->
<h3 class="mt--1.3 text-8 text-primary font-bold leading-1.2em font-navbar lg:text-9">PAGE<br>NOT<br>FOUND</h3>
<p class="mt-3.6 text-3.6 leading-1.4em font-navbar lg:(mt-4 text-4)">It looks like the page you're looking for<br>does not exist or has been moved.</p>
<h3 class="mt--1.3 flex flex-col text-8 text-primary font-bold leading-1.2em font-navbar lg:text-9">
<span>PAGE</span>
<span>NOT</span>
<span>FOUND</span>
</h3>
<p class="mt-3.6 flex flex-col text-3.6 leading-1.4em font-navbar lg:(mt-4 text-4)">
<span>It looks like the page you're looking for</span>
<span>does not exist or has been moved.</span>
</p>
<!-- Unused Div -->
<div class=""></div>
</Layout>

View file

@ -3,7 +3,7 @@ import type { CollectionEntry } from 'astro:content'
import { getCollection, render } from 'astro:content'
import Comments from '@/components/Comments/index.astro'
import PostDate from '@/components/PostDate.astro'
import GoBack from '@/components/Widgets/GoBack.astro'
import BackButton from '@/components/Widgets/BackButton.astro'
import TOC from '@/components/Widgets/TOC.astro'
import { allLocales, defaultLocale, moreLocales } from '@/config'
import { getTagPath } from '@/i18n/path'
@ -109,12 +109,12 @@ const { Content, headings, remarkPluginFrontmatter } = await render(post)
<article class="heti mb-12.6">
<div class="relative">
<!-- Go Back Button On Desktop -->
<GoBack />
<BackButton />
<!-- Title -->
<h1 class="post-title">
<span
transition:name={`post-${post.data.abbrlink || post.id}-${lang}`}
data-disable-transition-on-theme
data-disable-theme-transition
>
{post.data.title}
</span>
@ -123,9 +123,10 @@ const { Content, headings, remarkPluginFrontmatter } = await render(post)
<!-- Date -->
<div
id="gsap-post-page-date"
class="mb-16.3 block c-primary font-time"
transition:name={`time-${post.data.abbrlink || post.id}-${lang}`}
data-disable-transition-on-theme
data-disable-theme-transition
>
<PostDate
date={post.data.published}
@ -136,21 +137,25 @@ const { Content, headings, remarkPluginFrontmatter } = await render(post)
<!-- TOC -->
{post.data.toc && <TOC headings={headings} />}
<!-- Content -->
<Content />
<div id="gsap-post-page-content">
<Content />
</div>
</article>
<!-- Tags -->
{post.data.tags && post.data.tags.length > 0 && (
<div class="uno-decorative-line"></div>
<div class="uno-tags-wrapper">
{post.data.tags.map((tag: string) => (
<a
href={getTagPath(tag, lang)}
class="uno-tags-style"
>
{tag}
</a>
))}
<div id="gsap-post-page-tags">
<div class="uno-decorative-line"></div>
<div class="uno-tags-wrapper">
{post.data.tags.map((tag: string) => (
<a
href={getTagPath(tag, lang)}
class="uno-tags-style"
>
{tag}
</a>
))}
</div>
</div>
)}
<!-- Comments -->

View file

@ -52,7 +52,7 @@ const supportedLangs = await getTagSupportedLangs(tag)
href={getTagPath(tagName, lang)}
class={`uno-tags-style ${
tag === tagName
? 'border-secondary/75 text-primary'
? 'border-secondary/80 text-primary'
: ''
}`}
>

View file

@ -0,0 +1,23 @@
import { visit } from 'unist-util-visit'
export function rehypeUnwrapImg() {
return (tree) => {
visit(tree, 'element', (node, index, parent) => {
if (
node.tagName === 'p'
&& node.children
&& parent
&& node.children.every(child =>
child.tagName === 'img'
|| (child.type === 'text' && child.value.trim() === ''),
)
) {
const imgNodes = node.children.filter(child => child.tagName === 'img')
if (imgNodes.length > 0) {
parent.children.splice(index, 1, ...imgNodes)
}
}
})
}
}

View file

@ -1,28 +1,47 @@
/* KaTeX Overflow Fix */
.katex-display {
--at-apply: 'overflow-x-auto overflow-y-hidden scrollbar-hidden';
}
.katex-display::-webkit-scrollbar {
display: none;
/* GitHub Card */
.gc-container {
--at-apply: 'block mb-4 px-5 py-4 overflow-x-auto uno-round-border bg-secondary/5';
--at-apply: 'transition-colors ease-out lg:(px-6 py-5) hover:(bg-secondary/10 c-primary)';
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0.15) transparent;
}
/* Heading Anchor Link */
.heading-anchor-link {
--at-apply: 'inline-block translate-y-0.1em c-secondary/0';
/* Title Bar */
.gc-title-bar {
--at-apply: 'flex items-center gap-2.5 lg:gap-3';
}
h1:hover .heading-anchor-link,
h2:hover .heading-anchor-link,
h3:hover .heading-anchor-link,
h4:hover .heading-anchor-link {
--at-apply: 'c-secondary/40';
.gc-owner-avatar {
--at-apply: 'flex-shrink-0 w-5.5 aspect-square rounded-full bg-secondary/20';
}
.heading-anchor-link svg {
--at-apply: 'ml-0.4em aspect-square w-0.9em transition-colors active:scale-90 hover:c-secondary/80';
.gc-repo-title {
--at-apply: 'flex items-center leading-normal lg:text-lg';
}
.gc-slash {
--at-apply: 'mx-1 op-40 lg:mx-1.2';
}
.gc-github-icon {
--at-apply: 'flex-shrink-0 ml-auto w-5.5 lg:w-6';
}
/* Video */
iframe {
--at-apply: 'mb-4 w-full aspect-video';
/* Repo Description */
.gc-repo-description.gc-repo-description {
--at-apply: 'mt-2.45 mb-3.5 text-sm text-start lg:(mt-2.8 mb-4 text-base)';
}
/* Info Bar */
.gc-info-bar {
--at-apply: 'flex items-center gap-1.75 text-xs lg:(gap-2 text-sm)';
}
.gc-info-icon {
--at-apply: 'flex-shrink-0';
}
.gc-stars-count {
--at-apply: 'mr-3 lg:mr-4';
}
.gc-forks-count {
--at-apply: 'mr-3.75 lg:mr-5';
}
.gc-license-info {
--at-apply: 'ml-0.5 mr-4';
}
/* Admonition >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
@ -93,55 +112,12 @@ iframe {
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'%3E%3C/path%3E%3C/svg%3E");
}
/* GitHub Card >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
.gc-container {
--at-apply: 'block mb-4 px-5 py-4 overflow-x-auto uno-round-border bg-secondary/5 scrollbar-hidden';
--at-apply: 'transition-colors lg:(px-6 py-5) hover:(bg-secondary/10 c-primary)';
}
.gc-container::-webkit-scrollbar {
display: none;
}
/* Title Bar */
.gc-title-bar {
--at-apply: 'flex items-center gap-2.5 lg:gap-3';
}
.gc-owner-avatar {
--at-apply: 'flex-shrink-0 w-5.5 aspect-square rounded-full bg-secondary/20';
}
.gc-repo-title {
--at-apply: 'flex items-center leading-normal lg:text-lg';
}
.gc-slash {
--at-apply: 'mx-1 op-40 lg:mx-1.2';
}
.gc-github-icon {
--at-apply: 'flex-shrink-0 ml-auto w-5.5 lg:w-6';
}
/* Repo Description */
.gc-repo-description.gc-repo-description {
--at-apply: 'mt-2.45 mb-3.5 text-sm text-start lg:(mt-2.8 mb-4 text-base)';
}
/* Info Bar */
.gc-info-bar {
--at-apply: 'flex items-center gap-1.75 text-xs lg:(gap-2 text-sm)';
}
.gc-info-icon {
--at-apply: 'flex-shrink-0';
}
.gc-stars-count {
--at-apply: 'mr-3 lg:mr-4';
}
.gc-forks-count {
--at-apply: 'mr-3.75 lg:mr-5';
}
.gc-license-info {
--at-apply: 'ml-0.5 mr-4';
}
/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
/* Video */
/* iframe {
--at-apply: 'mb-4 w-full aspect-video';
} */
/* :where(details) {
--at-apply: 'my-4 px-4 py-3 border border-solid border-secondary/25 rounded cursor-pointer';
}

View file

@ -1,3 +1,41 @@
/* Snell Roundhand Static Font */
@font-face {
font-family: "Snell-Bold";
src: url("/fonts/Snell-Bold.woff2") format("woff2");
font-display: swap;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
@font-face {
font-family: "Snell-Black";
src: url("/fonts/Snell-Black.woff2") format("woff2");
font-display: swap;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
/* STIXTwoText Variable Font */
@font-face {
font-family: "STIX";
src: url("/fonts/STIX.woff2") format("woff2-variations");
font-display: swap;
font-weight: 400 700;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
@font-face {
font-family: "STIX-Italic";
src: url("/fonts/STIX-Italic.woff2") format("woff2-variations");
font-display: swap;
font-weight: 400 700;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
/* Minimal Subset of EarlySummerSerif Variable Font */
@font-face {
font-family: "EarlySummer-Subset";
src: url("/fonts/EarlySummer-Subset.woff2") format("woff2-variations");
font-display: swap;
font-weight: 400 700;
}
/* EarlySummerSerif Variable Font */
@font-face {font-family:"EarlySummer";src:url("/fonts/EarlySummer-Split/264097d8d9da16491b480a1c46afe035.woff2") format("woff2-variations");font-display: swap;font-weight: 400 700;unicode-range:U+B7,U+9F2C,U+9F3B,U+9F3E,U+9F84,U+9F99,U+9F9F;}
@font-face {font-family:"EarlySummer";src:url("/fonts/EarlySummer-Split/c0df1afa44c13da520351e6aa9be3d9a.woff2") format("woff2-variations");font-display: swap;font-weight: 400 700;unicode-range:U+9711,U+9784,U+97A6,U+97C1,U+97C6,U+982C,U+983C,U+9854-9855,U+98B1,U+98B3,U+98BA,U+9935,U+993D,U+9951,U+99C4-99C6,U+9A12-9A13,U+9AEA,U+9AEE,U+9B06,U+9B0D,U+9B1A,U+9B28,U+9D12,U+9E78,U+9EB5,U+9EBA,U+9ED2,U+9ED9,U+9F07,U+9F15,U+9F62-9F63,U+FF01,U+FF08-FF09,U+FF0C,U+FF1A-FF1B,U+FF1F;}

View file

@ -5,9 +5,9 @@
}
html {
--at-apply: 'bg-background c-secondary antialiased';
}
::selection {
--at-apply: 'bg-mark';
scrollbar-width: thin;
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0.25) transparent;
scrollbar-gutter: stable both-edges;
}
/* Fix Flash Issue On iOS */
@ -15,6 +15,37 @@ body {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
body::selection {
--at-apply: 'bg-highlight';
}
/* Fix KaTeX Overflow Issue */
.katex-display {
--at-apply: 'overflow-x-auto overflow-y-hidden scrollbar-hidden';
}
.katex-display::-webkit-scrollbar {
display: none;
}
/* Heading Anchor Link */
.heading-anchor-link {
--at-apply: 'inline-block translate-y-0.1em';
}
h1:hover .heading-anchor-link svg,
h2:hover .heading-anchor-link svg,
h3:hover .heading-anchor-link svg,
h4:hover .heading-anchor-link svg {
--at-apply: 'op-80';
}
.heading-anchor-link svg {
--at-apply: 'ml-0.4em aspect-square w-0.9em op-0 transition-opacity ease-out active:scale-90';
}
h1:hover .heading-anchor-link svg:hover,
h2:hover .heading-anchor-link svg:hover,
h3:hover .heading-anchor-link svg:hover,
h4:hover .heading-anchor-link svg:hover {
--at-apply: 'op-80';
}
/* Highlight Hover Animation */
.highlight-static,
@ -23,85 +54,22 @@ body {
}
.highlight-static::after,
.highlight-hover::after {
--at-apply: 'content-[""] absolute bottom-0.5em left-0 z--1 h-0.5em w-full bg-mark';
--at-apply: 'content-[""] absolute left-0 z--1 h-0.5em w-full bg-highlight';
}
.highlight-static::after,
.highlight-hover:hover::after {
--at-apply: 'origin-bottom-left scale-x-100';
}
.highlight-hover::after {
--at-apply: 'origin-bottom-right scale-x-0 transition-transform duration-300 ease-out';
--at-apply: 'origin-bottom-right scale-x-0 transition-transform ease-out lg:duration-225';
}
/* View Transition with Theme Toggle >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
::view-transition-new(theme-transition) {
animation: reveal 1s cubic-bezier(0.4, 0, 0.2, 1);
clip-path: inset(0 0 0 0);
z-index: 99;
}
::view-transition-old(theme-transition) {
animation: none;
z-index: -1;
}
@keyframes reveal {
from {
clip-path: inset(var(--from));
/* Fix Highlight Position Issue on iOS */
@supports (-webkit-touch-callout: none) {
.navbar-highlight-position-fix {
--at-apply: 'after:bottom-0.68em';
}
.footer-highlight-position-fix {
--at-apply: 'after:bottom-0.39em';
}
}
html.dark {
--from: 0 0 100% 0;
}
html:not(.dark) {
--from: 100% 0 0 0;
}
/* Disable animations for other elements during theme switching */
html[data-theme-transition] [data-disable-transition-on-theme] {
view-transition-name: none !important;
}
/* Fallback animation when view-transition-name is not supported */
@supports not (view-transition-name: none) {
html:not([data-restore-theme]) {
--at-apply: 'transition-colors duration-300 ease-out';
}
}
/* Snell Roundhand Static Font >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
@font-face {
font-family: "Snell-Bold";
src: url("/fonts/Snell-Bold.woff2") format("woff2");
font-display: swap;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
@font-face {
font-family: "Snell-Black";
src: url("/fonts/Snell-Black.woff2") format("woff2");
font-display: swap;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
/* STIXTwoText Variable Font */
@font-face {
font-family: "STIX";
src: url("/fonts/STIX.woff2") format("woff2-variations");
font-display: swap;
font-weight: 400 700;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
@font-face {
font-family: "STIX-Italic";
src: url("/fonts/STIX-Italic.woff2") format("woff2-variations");
font-display: swap;
font-weight: 400 700;
unicode-range: U+0030-0039,U+0041-005A,U+0061-007A,U+00C1,U+00C9,U+00CD,U+00D3,U+00DA,U+00DC,U+00D1,U+00E1,U+00E9,U+00ED,U+00F3,U+00FA,U+00FC,U+00F1,U+0410-044F,U+0401,U+0451,U+0021-002F,U+003A-0040,U+00A9;
}
/* Minimal Subset of EarlySummerSerif Variable Font */
@font-face {
font-family: "EarlySummer-Subset";
src: url("/fonts/EarlySummer-Subset.woff2") format("woff2-variations");
font-display: swap;
font-weight: 400 700;
}

View file

@ -7,52 +7,41 @@
/* Global Styles */
.heti {
--at-apply: 'break-words leading-1.5em hyphens-auto cjk:tracking-0.02em';
--at-apply: 'break-words leading-normal hyphens-auto cjk:tracking-0.02em';
}
/* Customized Post Title */
.heti .post-title {
--at-apply: 'mb-2 text-8.6 c-primary font-bold leading-12 lg:text-9';
--at-apply: 'mb-2 text-8.6 font-bold lg:text-9';
}
/* Headings */
.heti :where(h1),
.heti :where(h2),
.heti :where(h3),
.heti :where(h4),
.heti :where(h5),
.heti :where(h6) {
--at-apply: 'mb-3 mt-6 font-semibold';
.heti :where(h1, h2, h3, h4, h5, h6) {
--at-apply: 'mb-4 font-semibold';
}
.heti :where(h1, h2, h3) {
--at-apply: 'text-balance leading-1.33em cjk:text-pretty cjk:tracking-0.05em';
}
.heti :where(h1) {
--at-apply: 'text-7 leading-12';
--at-apply: 'mt-9.6 text-7 text-primary';
}
.heti :where(h2) {
--at-apply: 'text-6 leading-9';
--at-apply: 'mt-9.6 text-6 text-primary';
}
.heti :where(h3) {
--at-apply: 'text-5 leading-9';
--at-apply: 'mt-6.5 text-5';
}
.heti :where(h4) {
--at-apply: 'text-4.5 leading-6';
--at-apply: 'mt-6 text-4.5';
}
.heti :where(h5) {
--at-apply: 'text-4 leading-6';
--at-apply: 'mt-6 text-4';
}
.heti :where(h6) {
--at-apply: 'text-3.5 leading-6';
--at-apply: 'mt-6 font-normal';
}
.heti :where(h1),
.heti :where(h2),
.heti :where(h3) {
--at-apply: 'text-balance cjk:text-pretty cjk:tracking-0.05em';
}
.heti :where(h1 + h2),
.heti :where(h2 + h3),
.heti :where(h3 + h4),
.heti :where(h4 + h5),
.heti :where(h5 + h6) {
--at-apply: 'mt-3';
.heti :where(h1 + h2, h2 + h3, h3 + h4, h4 + h5, h5 + h6) {
--at-apply: 'mt-4';
}
/* Paragraphs */
@ -62,26 +51,33 @@
/* Links */
.heti :where(a:not(.gc-container)) {
--at-apply: 'break-all font-medium tracking-0 underline underline-0.075em decoration-secondary/40 underline-offset-0.2em';
--at-apply: 'transition-colors hover:(c-primary decoration-secondary/80) lg:underline-0.1em';
--at-apply: 'break-all font-semibold tracking-0 underline underline-0.075em decoration-secondary/80 underline-offset-0.1em';
--at-apply: 'transition-colors ease-out hover:(c-primary decoration-primary/80) lg:underline-0.1em';
}
/* Images */
.heti :where(img) {
--at-apply: 'mx-auto cursor-zoom-in';
transform: translateZ(0);
-webkit-transform: translateZ(0);
--at-apply: 'mb-4 mx-auto cursor-zoom-in';
}
.heti :where(figure) {
--at-apply: 'mx-auto mb-4';
}
.heti figure img {
--at-apply: 'mb-0';
}
.heti :where(figcaption) {
--at-apply: 'mt-2 text-center text-sm text-secondary/80';
--at-apply: 'mt-3 text-center text-sm text-secondary/80';
}
/* Code Blocks */
.heti :where(pre) {
--at-apply: 'mb-4 overflow-auto uno-round-border px-4 py-3 bg-secondary/5!';
scrollbar-width: thin;
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0) transparent;
transition: scrollbar-color 0.3s ease-out;
}
.heti :where(pre:hover) {
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0.15) transparent;
}
.heti pre :where(code) {
--at-apply: 'border-none bg-transparent p-0';
@ -124,10 +120,13 @@ html.dark .heti pre :where(span) {
/* Tables */
.heti :where(table) {
--at-apply: 'mb-4 box-border block max-w-full table-fixed overflow-x-auto scrollbar-hidden';
--at-apply: 'mb-4 box-border block max-w-full table-fixed overflow-x-auto';
scrollbar-width: thin;
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0) transparent;
transition: scrollbar-color 0.3s ease-out;
}
.heti :where(table)::-webkit-scrollbar {
--at-apply: 'hidden';
.heti :where(table:hover) {
scrollbar-color: oklch(var(--un-preset-theme-colors-secondary) / 0.15) transparent;
}
.heti :where(th),
.heti :where(td) {
@ -182,7 +181,7 @@ html.dark .heti pre :where(span) {
/* Superscript and Subscript >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
.heti :where(sub),
.heti :where(sup) {
--at-apply: 'relative mx-0.1em align-baseline text-0.75em leading-1';
--at-apply: 'relative mx-0.1em align-baseline text-0.75em';
}
.heti :where(sub) {
--at-apply: 'bottom--0.25em';
@ -212,7 +211,7 @@ html.dark .heti :where(u) {
/* Highlighted Text */
.heti :where(mark) {
--at-apply: 'bg-mark py-0.65 text-inherit';
--at-apply: 'bg-highlight py-0.65 text-inherit';
}
/* Footnotes */
@ -222,7 +221,7 @@ html.dark .heti :where(u) {
}
.heti sup:target,
.heti sup a:target {
--at-apply: 'bg-mark';
--at-apply: 'bg-highlight';
}
.heti .data-footnote-backref {
--at-apply: 'font-serif no-underline';
@ -253,4 +252,3 @@ html.dark .heti :where(u) {
.heti :where(q:is(:lang(zh), :lang(ja), :lang(ko))) {
quotes: "「" "」" "『" "』";
}

34
src/styles/transition.css Normal file
View file

@ -0,0 +1,34 @@
/* View Transition */
::view-transition-new(animation-theme-toggle) {
animation: reveal 0.8s cubic-bezier(0.4, 0, 0.2, 1);
clip-path: inset(0 0 0 0);
z-index: 99;
}
::view-transition-old(animation-theme-toggle) {
animation: none;
z-index: -1;
}
@keyframes reveal {
from {
clip-path: inset(var(--from));
}
}
html.dark {
--from: 0 0 100% 0;
}
html:not(.dark) {
--from: 100% 0 0 0;
}
/* Disable animations for special elements during theme switching */
html[data-theme-changing] [data-disable-theme-transition] {
view-transition-name: none !important;
}
/* Fallback animation when view-transition-name is not supported */
@supports not (view-transition-name: none) {
html {
--at-apply: 'transition-colors duration-300 ease-out';
}
}

View file

@ -34,6 +34,7 @@ export interface ThemeConfig {
fontStyle: 'sans' | 'serif'
dateFormat: 'YYYY-MM-DD' | 'MM-DD-YYYY' | 'DD-MM-YYYY' | 'MONTH DAY YYYY' | 'DAY MONTH YYYY'
katex: boolean
toc: boolean
}
comment: {
@ -73,7 +74,6 @@ export interface ThemeConfig {
preload: {
linkPrefetch: 'hover' | 'tap' | 'viewport' | 'load'
commentURL?: string
imageHostURL?: string
customGoogleAnalyticsJS?: string
customUmamiAnalyticsJS?: string

View file

@ -54,7 +54,7 @@ const getOptimizedImageUrl = memoize(async (srcPath: string, baseUrl: string) =>
*/
async function fixRelativeImagePaths(htmlContent: string, baseUrl: string): Promise<string> {
const htmlDoc = htmlParser(htmlContent)
const images = htmlDoc.querySelectorAll('img')
const images = htmlDoc.getElementsByTagName('img')
const imagePromises = []
for (const img of images) {

View file

@ -20,7 +20,7 @@ export default defineConfig({
dark: {
colors: {
...dark,
mark: 'oklch(0.93 0.195089 103.2532 / 0.2)', // rgba(255,235,0,0.5)
highlight: 'oklch(0.93 0.195089 103.2532 / 0.2)', // rgba(255,235,0,0.5)
note: 'oklch(70.7% 0.165 254.624 / 0.8)', // blue-400
tip: 'oklch(76.5% 0.177 163.223 / 0.8)', // emerald-400
important: 'oklch(71.4% 0.203 305.504 / 0.8)', // purple-400
@ -34,7 +34,7 @@ export default defineConfig({
theme: {
colors: {
...light,
mark: 'oklch(0.93 0.195089 103.2532 / 0.5)', // rgba(255,235,0,0.5)
highlight: 'oklch(0.93 0.195089 103.2532 / 0.5)', // rgba(255,235,0,0.5)
note: 'oklch(48.8% 0.243 264.376 / 0.8)', // blue-700
tip: 'oklch(50.8% 0.118 165.612 / 0.8)', // emerald-700
important: 'oklch(49.6% 0.265 301.924 / 0.8)', // purple-700
@ -55,11 +55,11 @@ export default defineConfig({
}],
],
shortcuts: {
'uno-desktop-column': 'fixed w-14rem right-[max(5.625rem,calc(50vw-34.375rem))]',
'uno-tags-style': 'inline-block whitespace-nowrap border border-secondary/25 rounded-full px-3.2 py-0.7 c-secondary transition-colors hover:(border-secondary/75 text-primary)',
'uno-decorative-line': 'h-0.25 w-10 bg-secondary/25 mb-4.5 lg:(w-11 mb-6)',
'uno-desktop-column': 'fixed right-[max(5rem,calc(50vw-35rem))] w-14rem',
'uno-tags-style': 'inline-block whitespace-nowrap border border-secondary/25 rounded-full px-3.2 py-0.7 c-secondary transition-colors ease-out hover:(border-secondary/80 text-primary)',
'uno-decorative-line': 'mb-4.5 h-0.25 w-10 bg-secondary/25 lg:(mb-6 w-11)',
'uno-tags-wrapper': 'flex flex-wrap gap-x-3 gap-y-3.2',
'uno-round-border': 'rounded border border-solid border-secondary/5',
'uno-round-border': 'border border-secondary/5 rounded border-solid',
},
variants: [
(matcher) => {