大更新

This commit is contained in:
RiseForever
2026-08-12 01:15:12 +08:00
parent d84b250f7a
commit 07294b9f24
11 changed files with 659 additions and 44 deletions
+1
View File
@@ -19,6 +19,7 @@ const blogsClubBadge = `https://www.blogsclub.org/badge/${blogDomain}/theme`;
</a>
</div>
{siteConfig.footer.links.length > 0 && <nav aria-label="页脚导航">{siteConfig.footer.links.map((item, index) => <Fragment>{index > 0 && ' · '}<a data-text-link href={item.href} target={item.external ? '_blank' : undefined} rel={item.external ? 'noreferrer' : undefined}><span class:list={{ 'footer-chinese': containsChinese(item.label) }} lang={containsChinese(item.label) ? 'zh-CN' : undefined}>{item.label}</span></a></Fragment>)}</nav>}
<p class="m-0">Powered By Astro · Theme <a data-text-link href="https://src.luming.cool/riseforever2026/Eidolon">Eidolon</a></p>
{siteConfig.footer.copyright && <p class="m-0">{siteConfig.footer.copyright}</p>}
{siteConfig.footer.note && <p class="footer-chinese m-0" lang="zh-CN">{siteConfig.footer.note}</p>}
{siteConfig.footer.theme && <p class="m-0"><a data-text-link href={siteConfig.footer.theme.href} target={siteConfig.footer.theme.external ? '_blank' : undefined} rel={siteConfig.footer.theme.external ? 'noreferrer' : undefined}>{siteConfig.footer.theme.label}</a></p>}
+6 -2
View File
@@ -131,11 +131,15 @@ GitHub[virelyx258](https://github.com/virelyx258)
### 主题
使用自己制作的 Astro 主题 Mirages
使用自己制作的 Astro 主题 Eidolon
它极大程度上复刻了 Typecho 主题 Mirages 的风格和功能,又在此基础上使用了更先进的 TailWind CSS,使得站点响应速度更加优秀。
因为 Mirages 是一款付费主题,如果贸然开源会影响到原主题的销售,且本站主题仍处于开发状态,所以本主题暂不开源。
~~因为 Mirages 是一款付费主题,如果贸然开源会影响到原主题的销售,且本站主题仍处于开发状态,所以本主题暂不开源。~~
仔细想想,发现我们其实不是同一个赛道。我做的是 Astro 主题,受众是 Astro 用户,不耽误 Typecho 主题的销售。况且,并不是所有 Typecho 用户都能无感切换到静态博客。
所以,本主题准备开源。
[collapse title="在此之前"]
@@ -0,0 +1,335 @@
---
title: "Eidolon 主题短代码演示"
pubDate: "2026-08-12T01:10:32.630Z"
categories: ['科技']
tags: ['主题','Astro']
draft: false
comments: true
---
Eidolon 主题支持短代码。除兼容 [Mirages](https://get233.com/archives/mirages-intro.html) 的全部短代码外,额外增加了“Tools”组件。
善于运用短代码可以使文章内容更丰富。
---
短代码的统一书写形式:
```
[shortcode param="value"]内容[/shortcode]
[shortcode param="value"/]
```
直接在 MarkDown 源代码里书写即可,博客构建时会自动将其转换为对应样式。
---
## 链接按钮
短代码名称:`button`
[tabs]
[tab name="说明"]
生成一个按钮样式的超链接。
[/tab]
[tab name="示例代码"]
```
[button href="https://src.luming.cool/riseforever2026/Eidolon"]Eidolon 主题[/button]
```
[/tab]
[tab name="效果" selected]
[button href="https://src.luming.cool/riseforever2026/Eidolon"]Eidolon 主题[/button]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -----: | :------------- |
| href | 欲跳转到的链接 |
| 内容 | 按钮标题 |
[/tab]
[/tabs]
## 选项卡
短代码名称:`tabs`
[tabs]
[tab name="说明"]
生成一个多标签页的卡片。
[/tab]
[tab name="示例代码"]
```
[tabs]
[tab name="标签页1标题"]
我是标签页1的内容
[/tab]
[tab name="标签页2标题"]
我是标签页2的内容
[/tab]
[tab name="标签页3标题" selected]
我是标签页3的内容
[/tab]
[tab name="标签页4标题"]
我是标签页4的内容
[/tab]
[/tabs]
```
[/tab]
[tab name="效果" selected]
如你所见。
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -------: | :----------------- |
| name | 标签页的名称 |
| selected | 默认选中的标签页 |
| 内容 | 标签页内显示的内容 |
[/tab]
[/tabs]
## 提示框、警告框
短代码名称:`hint``tip`
[tabs]
[tab name="说明"]
生成一个提示或警告卡片。
[/tab]
[tab name="示例代码"]
```
[hint]提示内容[/hint]
[hint warn]警告内容[/hint]
[hint type="danger" title="提示标题"]
提示内容
[/hint]
```
[/tab]
[tab name="效果" selected]
[hint]提示内容[/hint]
[hint warn]警告内容[/hint]
[hint type="danger" title="提示标题"]
提示内容
[/hint]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -----: | :----------------------------------------------------------- |
| type | 提示类型,默认为 `info`,在没有警告标题的情况下,可以直接将提示类型的值写到参数处。可选的提示类型为 `info``warning`(同 `warn`), `danger`(同 `error`)`success` |
| title | 提示标题 |
| 内容 | 提示内容 |
[/tab]
[/tabs]
## 下载文件
短代码名称:`file`
[tabs]
[tab name="说明"]
生成一个下载文件的卡片。
[/tab]
[tab name="示例代码"]
```
[file url="https://example.com/download.zip"]示例文件名[/file]
```
[/tab]
[tab name="效果" selected]
[file url="https://example.com/download.zip"]示例文件名[/file]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -----: | :------------- |
| href | 下载文件的链接 |
| 内容 | 文件名 |
[/tab]
[/tabs]
## 标签
短代码名称:`tag``label`
[tabs]
[tab name="说明"]
生成一个标签。
[/tab]
[tab name="示例代码"]
```
[tag]默认 Tag[/tag]
[tag type="primary"]文字的颜色[/tag]
[tag type="info"]文字的颜色[/tag]
[tag type="warn"]文字的颜色[/tag]
[tag type="danger"]文字的颜色[/tag]
[tag type="success"]文字的颜色[/tag]
[tag outline]文字的颜色[/tag]
[tag type="primary" outline]文字的颜色[/tag]
[tag type="info" outline]文字的颜色[/tag]
[tag type="warn" outline]文字的颜色[/tag]
[tag type="danger" outline]文字的颜色[/tag]
[tag type="success" outline="1"]文字的颜色[/tag]
```
[/tab]
[tab name="效果" selected]
[tag]默认 Tag[/tag]
[tag type="primary"]文字的颜色[/tag]
[tag type="info"]文字的颜色[/tag]
[tag type="warn"]文字的颜色[/tag]
[tag type="danger"]文字的颜色[/tag]
[tag type="success"]文字的颜色[/tag]
[tag outline]文字的颜色[/tag]
[tag type="primary" outline]文字的颜色[/tag]
[tag type="info" outline]文字的颜色[/tag]
[tag type="warn" outline]文字的颜色[/tag]
[tag type="danger" outline]文字的颜色[/tag]
[tag type="success" outline="1"]文字的颜色[/tag]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| ------: | :----------------------------------------------------------- |
| type | 默认为 `default`,可选值为 `info``warning`(同 `warn`), `danger`(同 `error`)`success``primary` `default` |
| outline | 是否使用 outline 样式的标签 |
| 内容 | 标签展示的文字 |
[/tab]
[/tabs]
## 快速输入警告符: [!/]
短代码名称:`!`
[tabs]
[tab name="说明"]
快速输入: [!/]
[/tab]
[tab name="示例代码"]
```
[!/]
```
[/tab]
[tab name="效果" selected]
[!/]
[/tab]
[/tabs]
## 折叠框
短代码名称:`collapse`
[tabs]
[tab name="说明"]
可以点击标题展开 / 折叠的内容。
[/tab]
[tab name="示例代码"]
```
[collapse title="标题"]
折叠内容
[/collapse]
```
[/tab]
[tab name="效果" selected]
[collapse title="标题"]
折叠内容
[/collapse]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -----: | :--------- |
| title | 折叠框标题 |
| 内容 | 折叠框内容 |
[/tab]
[/tabs]
## GitHub
短代码名称:`github`
[tabs]
[tab name="说明"]
放置一个 Github 仓库控件。
[/tab]
[tab name="示例代码"]
```
[github repo="virelyx258/Astro-Theme-Eidolon" /]
```
[/tab]
[tab name="效果" selected]
[github repo="virelyx258/Astro-Theme-Eidolon" /]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -----: | :----------------------------------------------- |
| repo | Github 仓库所属用户及仓库名。格式:用户名/仓库名 |
[/tab]
[/tabs]
## 工具列表
短代码名称:`tools`
[tabs]
[tab name="说明"]
放置一个工具列表卡片。
[/tab]
[tab name="示例代码"]
```
[tools title = "设计"]
[Canva](https://www.canva.cn)+(https://image.luming.cool/i/2026/08/07/6a75ba231df9b.webp)/(图像设计)
[即时设计](https://js.design)+(https://image.luming.cool/i/2026/08/07/6a75c0b9bbf29.webp)/(UI 设计)
[Microsoft 365](https://m365.cloud.microsoft/)+(https://image.luming.cool/i/2026/08/07/6a75bd0318e8e.webp)/(文档处理)
[剪映](https://www.capcut.cn/)+(https://image.luming.cool/i/2026/08/07/6a75bf6a337d1.webp)/(视频剪辑)
[/tools]
```
[/tab]
[tab name="效果" selected]
[tools title = "设计"]
[Canva](https://www.canva.cn)+(https://image.luming.cool/i/2026/08/07/6a75ba231df9b.webp)/(图像设计)
[即时设计](https://js.design)+(https://image.luming.cool/i/2026/08/07/6a75c0b9bbf29.webp)/(UI 设计)
[Microsoft 365](https://m365.cloud.microsoft/)+(https://image.luming.cool/i/2026/08/07/6a75bd0318e8e.webp)/(文档处理)
[剪映](https://www.capcut.cn/)+(https://image.luming.cool/i/2026/08/07/6a75bf6a337d1.webp)/(视频剪辑)
[/tools]
[/tab]
[tab name="参数"]
| 参数名 | 含义 |
| -----: | :--------- |
| title | 工具集名称 |
[/tab]
[/tabs]
+52
View File
@@ -0,0 +1,52 @@
const NOTICE_ICON = {
type: 'element',
tagName: 'span',
properties: { className: ['shortcode-notice'], ariaLabel: '警告' },
children: [{
type: 'element',
tagName: 'svg',
properties: {
ariaHidden: 'true',
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
},
children: [
{ type: 'element', tagName: 'circle', properties: { cx: 12, cy: 12, r: 10 }, children: [] },
{ type: 'element', tagName: 'path', properties: { d: 'M12 8v4' }, children: [] },
{ type: 'element', tagName: 'path', properties: { d: 'M12 16h.01' }, children: [] },
],
}],
};
function cloneNoticeIcon() {
return structuredClone(NOTICE_ICON);
}
export function applyNoticeShortcodes(tree: any) {
const transform = (node: any) => {
if (!Array.isArray(node.children) || node.tagName === 'code' || node.tagName === 'pre') return;
const output: any[] = [];
for (const child of node.children) {
if (child.type !== 'text' || !child.value.includes('[!/]')) {
transform(child);
output.push(child);
continue;
}
const parts = child.value.split('[!/]');
parts.forEach((part: string, index: number) => {
if (part) output.push({ type: 'text', value: part });
if (index < parts.length - 1) output.push({ type: 'text', value: '\u00a0' }, cloneNoticeIcon(), { type: 'text', value: '\u00a0' });
});
}
node.children = output;
};
transform(tree);
}
export default function noticeShortcode() {
return (tree: any) => applyNoticeShortcodes(tree);
}
+25
View File
@@ -0,0 +1,25 @@
import { SKIP, visit } from 'unist-util-visit';
export function applyResponsiveTables(tree: any) {
visit(tree, 'element', (node: any, index: number | undefined, parent: any) => {
if (node.tagName !== 'table' || index === undefined || !parent) return;
const classes = Array.isArray(node.properties?.className) ? node.properties.className : [];
if (classes.includes('responsive-table')) return;
const section = node.children?.find((child: any) => child.tagName === 'thead' || child.tagName === 'tbody');
const row = section?.children?.find((child: any) => child.tagName === 'tr');
const columns = Math.max(1, row?.children?.filter((child: any) => child.tagName === 'th' || child.tagName === 'td').length ?? 1);
node.properties = { ...node.properties, className: [...classes, 'responsive-table'] };
parent.children[index] = {
type: 'element',
tagName: 'div',
properties: { className: ['table-scroll'], style: `--table-columns:${columns}` },
children: [node],
};
return SKIP;
});
}
export default function responsiveTables() {
return (tree: any) => applyResponsiveTables(tree);
}
+72 -16
View File
@@ -5,10 +5,18 @@ import { toHast } from 'mdast-util-to-hast';
import { gfm } from 'micromark-extension-gfm';
import { visit } from 'unist-util-visit';
import { applyExternalLinks } from './external-links';
import { applyResponsiveTables } from './responsive-tables';
import { applyNoticeShortcodes } from './notice-shortcode';
const URL_SCHEME = /^(https?:|mailto:|tel:)/i;
const TAG_TYPES = new Set(['primary', 'success', 'warning', 'danger', 'info', 'default']);
const HINT_TYPES = new Set(['warn', 'warning', 'error', 'danger', 'success', 'info']);
const HINT_ICONS: Record<string, string> = {
info: '<circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path>',
warning: '<circle cx="12" cy="12" r="10"></circle><path d="M12 8v4"></path><path d="M12 16h.01"></path>',
danger: '<path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 8.54 21h6.92a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path>',
success: '<path d="m9 12 2 2 4-4"></path><circle cx="12" cy="12" r="10"></circle>',
};
const BLOCK_NAMES = new Set(['hint', 'tip', 'collapse', 'tabs', 'tools']);
const INLINE_NAMES = new Set(['button', 'btn', 'file', 'tag', 'label']);
type GithubData = { stars?: string; forks?: string; description?: string; commitDate?: string; htmlUrl?: string; defaultBranch?: string };
@@ -61,12 +69,37 @@ function normalizeMarkdownHeadings(source: string): string {
}).join('\n');
}
function markdown(source: string, preserveHtml = false): string {
function markdown(source: string, preserveHtml = false, lineNumbers = false): string {
const normalized = normalizeMarkdownHeadings(source);
const tree = fromMarkdown(normalized, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] });
transform(tree, normalized);
if (!preserveHtml) visit(tree, 'html', (node: any) => { if (!node.data?.shortcode) node.value = ''; });
const hast = toHast(tree, { allowDangerousHtml: true }) as any;
if (lineNumbers) {
visit(hast, 'element', (node: any) => {
if (node.tagName !== 'pre') return;
const code = node.children?.find((child: any) => child.type === 'element' && child.tagName === 'code');
if (!code || code.children?.some((child: any) => child.type !== 'text')) return;
const value = code.children.map((child: any) => child.value).join('');
const lines = value.endsWith('\n') ? value.slice(0, -1).split('\n') : value.split('\n');
const classes = Array.isArray(node.properties?.className) ? node.properties.className : [];
node.properties = { ...node.properties, className: [...classes, 'shortcode-code'] };
code.children = lines.flatMap((line: string, index: number) => [
{
type: 'element',
tagName: 'span',
properties: { className: ['line'] },
children: [
{ type: 'element', tagName: 'span', properties: { className: ['shortcode-line-number'], ariaHidden: 'true' }, children: [{ type: 'text', value: String(index + 1) }] },
...(line ? [{ type: 'text', value: line }] : []),
],
},
...(index < lines.length - 1 ? [{ type: 'text', value: '\n' }] : []),
]);
});
}
applyNoticeShortcodes(hast);
applyResponsiveTables(hast);
if (siteUrl) applyExternalLinks(hast, siteUrl);
return toHtml(hast, { allowDangerousHtml: true });
}
@@ -77,24 +110,32 @@ export function renderMarkdown(source: string): string {
function renderBlock(name: string, args: Record<string, string>, body = ''): string {
const label = args.title ?? args.name ?? args.text ?? body.trim().split('\n')[0] ?? '';
if (name === 'button' || name === 'btn' || name === 'file') {
if (name === 'button' || name === 'btn') {
const url = safeUrl(args.href ?? args.url ?? args.link);
if (!url) return `<span class="shortcode-invalid">${escape(label || '链接不可用')}</span>`;
return `<a class="shortcode-button shortcode-${name}" href="${escape(url)}" data-no-text-link>${inline(label || url)}</a>`;
}
if (name === 'file') {
const url = safeUrl(args.href ?? args.url ?? args.link);
if (!url) return `<span class="shortcode-invalid">${escape(label || '文件链接不可用')}</span>`;
const icon = '<svg class="content-file-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><path d="M10 12v-1"></path><path d="M10 18v-2"></path><path d="M10 7v1"></path><path d="M10 15h.01"></path></svg>';
return `<a class="shortcode-file content-file" href="${escape(url)}" target="_blank" rel="noopener noreferrer" data-no-text-link>${icon}<span class="content-file-filename">${inline(label || url)}</span></a>`;
}
if (name === 'tag' || name === 'label') {
const type = TAG_TYPES.has(args.type ?? '') ? args.type : 'default';
const outline = args.outline !== undefined ? ' shortcode-tag-outline' : '';
return `<span class="shortcode-tag shortcode-tag-${type}${outline}">${inline(label)}</span>`;
}
if (name === 'hint' || name === 'tip') {
const type = HINT_TYPES.has(args.type ?? '') ? args.type : 'info';
const title = args.title ? `<strong>${inline(args.title)}</strong>` : '';
return `<aside class="shortcode-hint shortcode-hint-${type}" role="note">${title}${markdown(body)}</aside>`;
const requestedType = args.type ?? [...HINT_TYPES].find((candidate) => args[candidate] !== undefined);
const type = requestedType === 'warn' ? 'warning' : requestedType === 'error' ? 'danger' : HINT_TYPES.has(requestedType ?? '') ? requestedType : 'info';
const icon = `<svg class="content-hint-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${HINT_ICONS[type]}</svg>`;
const heading = args.title ? `<div class="content-hint-heading">${icon}<p class="content-hint-title">${inline(args.title)}</p></div>` : icon;
return `<aside class="shortcode-hint content-hint shortcode-${name} shortcode-hint-${type} hint-${type}${args.title ? ' content-hint-has-title' : ''}" role="note">${heading}${markdown(body)}</aside>`;
}
if (name === 'collapse') {
const open = args.open !== undefined || args.expanded !== undefined ? ' open' : '';
return `<details class="shortcode-collapse"${open}><summary><span class="shortcode-collapse-marker" aria-hidden="true"></span><span>${inline(label || '展开内容')}</span></summary><div class="shortcode-body"><div class="shortcode-body-inner">${markdown(body, true)}</div></div></details>`;
return `<details class="shortcode-collapse"${open}><summary><span class="shortcode-collapse-marker" aria-hidden="true"></span><span>${inline(label || '展开内容')}</span></summary><div class="shortcode-body"><div class="shortcode-body-inner">${markdown(body, true, true)}</div></div></details>`;
}
if (name === 'tabs') {
const parsed = parseBlocks(body);
@@ -151,8 +192,28 @@ function renderBlock(name: string, args: Record<string, string>, body = ''): str
function parseBlocks(source: string): Array<{ name: string; args: Record<string, string>; body: string }> {
const result: Array<{ name: string; args: Record<string, string>; body: string }> = [];
const pattern = /^\[tab([^\]]*)\]\s*\n?([\s\S]*?)^\[\/tab\]\s*$/gm;
for (const match of source.matchAll(pattern)) result.push({ name: 'tab', args: attrs(match[1]), body: match[2] });
let fence: string | null = null;
let opening: { args: Record<string, string>; bodyStart: number } | null = null;
for (const line of source.matchAll(/^[^\n]*(?:\n|$)/gm)) {
const text = line[0].trim();
const start = line.index!;
const fenceMatch = text.match(/^(`{3,}|~{3,})/);
if (fenceMatch) {
if (!fence) fence = fenceMatch[1][0];
else if (fenceMatch[1][0] === fence) fence = null;
continue;
}
if (fence) continue;
const closing = text.match(/^\[\/tab\]\s*$/i);
if (closing && opening) {
result.push({ name: 'tab', args: opening.args, body: source.slice(opening.bodyStart, start) });
opening = null;
continue;
}
const openingMatch = text.match(/^\[tab([^\]]*)\]\s*$/i);
if (openingMatch && !opening) opening = { args: attrs(openingMatch[1]), bodyStart: start + line[0].length };
}
return result;
}
@@ -220,15 +281,9 @@ function findBlocks(source: string): Array<{ name: string; args: Record<string,
}
function replaceInline(source: string): string | null {
const noticePattern = /^\[!(?:\/\s*)?\]\s*(.+)$/gm;
let replacedNotice = false;
source = source.replace(noticePattern, (_match, body) => {
replacedNotice = true;
return `<span class="shortcode-notice" role="note">${inline(body)}</span>`;
});
const shortcode = /\[(button|btn|file|tag|label)([^\]]*)\]([\s\S]*?)\[\/\1\]/gi;
const shortcode = /\[(button|btn|file|tag|label|hint|tip)([^\]]*)\]([\s\S]*?)\[\/\1\]/gi;
const replacedShortcode = shortcode.test(source);
if (!replacedNotice && !replacedShortcode) return null;
if (!replacedShortcode) return null;
return source.replace(shortcode, (_match, name, rawArgs, body) => renderBlock(name.toLowerCase(), attrs(rawArgs), body));
}
@@ -268,6 +323,7 @@ function transform(tree: any, raw = '') {
const output: any[] = [];
for (const node of root.children) {
if (node.type !== 'paragraph' && node.type !== 'html') { output.push(node); continue; }
if (node.type === 'html' && node.data?.shortcode) { output.push(node); continue; }
const source = node.type === 'html' ? node.value : node.position ? raw.slice(node.position.start.offset, node.position.end.offset) : '';
const replaced = replaceInline(source.trim());
output.push(replaced ? { type: 'html', value: replaced, data: { shortcode: true } } : node);
+60
View File
@@ -210,14 +210,66 @@
background: var(--color-code);
}
.shortcode-code {
--shortcode-code-gutter: color-mix(in srgb, var(--color-code) 78%, var(--color-accent));
padding: 0;
}
.shortcode-code code {
padding-block: 1rem;
font-size: 0;
}
.shortcode-code .line {
display: flex;
min-height: 1.45em;
font-size: .875rem;
line-height: 1.45;
}
.shortcode-code .shortcode-line-number {
display: inline-block;
box-sizing: border-box;
min-width: 3rem;
flex: 0 0 3rem;
margin-right: 1rem;
padding-inline: 1rem;
background: var(--shortcode-code-gutter);
color: var(--color-muted);
font-size: .875rem;
font-variant-numeric: tabular-nums;
opacity: 0.72;
text-align: right;
user-select: none;
}
.shortcode-code .line:first-of-type .shortcode-line-number { box-shadow: 0 -1rem var(--shortcode-code-gutter); }
.shortcode-code .line:last-of-type .shortcode-line-number { box-shadow: 0 1rem var(--shortcode-code-gutter); }
.shortcode-code .line:only-of-type .shortcode-line-number { box-shadow: 0 -1rem var(--shortcode-code-gutter), 0 1rem var(--shortcode-code-gutter); }
.prose .katex-display { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding-block: .35em; }
.prose .mermaid { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding: 1rem 0; }
.prose .mermaid svg { max-width: none; height: auto; }
.prose .table-scroll {
width: 100%;
max-width: 100%;
margin: 1.25em 0;
overflow-x: auto;
overscroll-behavior-inline: contain;
scrollbar-color: color-mix(in srgb, var(--color-muted) 60%, transparent) transparent;
scrollbar-width: thin;
}
.prose .table-scroll::-webkit-scrollbar { height: 8px; }
.prose .table-scroll::-webkit-scrollbar-thumb { border-radius: 4px; background: color-mix(in srgb, var(--color-muted) 60%, transparent); }
.prose table {
width: 100%;
margin: 0;
border-collapse: collapse;
table-layout: fixed;
}
.prose thead {
@@ -239,6 +291,14 @@
.prose :where(th, td) {
padding: 0.55em 0.8em;
border: 1px solid var(--color-border);
overflow-wrap: anywhere;
word-break: break-word;
}
@media (max-width: 48rem) {
.prose .responsive-table {
width: max(100%, calc(var(--table-columns, 1) * 8rem));
}
}
.prose :where(th, td)[align='left'] { text-align: left; }
+102 -22
View File
@@ -14,9 +14,54 @@
color: var(--color-bg);
}
.prose .shortcode-file {
display: inline-flex;
max-width: 100%;
min-height: 3.5rem;
align-items: center;
gap: 1rem;
margin: 0.25rem 0 0.25rem 0.5rem;
overflow: hidden;
padding: 0 1.5rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: transparent;
color: var(--color-text);
font-family: var(--mirages-font-ui);
text-decoration: none;
white-space: nowrap;
transition: border-color 0.6s ease;
}
.prose .shortcode-file:first-child {
margin-left: 0;
}
.prose .content-file-icon {
width: 1.5rem;
height: 1.5rem;
flex: 0 0 1.5rem;
color: var(--color-text);
transition: color 0.3s ease;
}
.prose .content-file-filename {
min-width: 0;
overflow: hidden;
color: var(--color-text);
font-weight: 700;
text-overflow: ellipsis;
transition: color 0.3s ease;
}
.prose .shortcode-file:hover {
border-color: var(--color-accent);
color: var(--color-accent);
}
.prose .shortcode-file:hover .content-file-icon,
.prose .shortcode-file:hover .content-file-filename {
color: var(--color-accent);
}
@media (prefers-reduced-motion: reduce) {
.prose .shortcode-file,
.prose .content-file-icon,
.prose .content-file-filename { transition: none; }
}
.shortcode-tag {
display: inline-block;
margin: 0.15em 0.3em 0.15em 0;
@@ -44,29 +89,60 @@
background: #35769b;
}
.shortcode-hint {
--hint-color: var(--color-accent);
position: relative;
margin: 1.2em 0;
padding: 0.7em 1em;
border-left: 4px solid var(--color-accent);
background: var(--color-surface);
padding: 1.5rem 1.25rem 1.5rem 3.5rem;
border: 1px solid color-mix(in srgb, var(--hint-color) 28%, var(--color-border));
border-left: 0.25rem solid var(--hint-color);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--hint-color) 7%, var(--color-surface));
}
.shortcode-hint strong {
.content-hint-icon {
position: absolute;
top: 50%;
left: 1rem;
width: 1.5rem;
height: 1.5rem;
transform: translateY(-50%);
color: var(--hint-color);
}
.content-hint-heading {
display: grid;
min-height: 1.5rem;
grid-template-columns: 1.5rem minmax(0, 1fr);
align-items: center;
gap: 1rem;
margin: 0 0 0.5rem -2.5rem;
}
.content-hint-heading .content-hint-icon {
position: static;
transform: none;
}
.prose .content-hint-title {
display: block;
margin-bottom: 0.2em;
font-family: var(--mirages-font-ui);
margin: 0;
color: var(--hint-color);
font-size: 1.2em;
line-height: 1.5rem;
font-weight: 600;
}
.shortcode-hint p {
.shortcode-hint p:not(.content-hint-title) {
margin: 0.25em 0;
}
.shortcode-hint-warning,
.shortcode-hint-warn {
border-color: #a36b16;
.shortcode-hint-warn,
.hint-warning {
--hint-color: #d99b00;
}
.shortcode-hint-error,
.shortcode-hint-danger {
border-color: #ad3d48;
.shortcode-hint-danger,
.hint-danger {
--hint-color: #d34b50;
}
.shortcode-hint-success {
border-color: #287d5b;
.shortcode-hint-success,
.hint-success {
--hint-color: #209b61;
}
.shortcode-collapse {
margin: 1.2em 0;
@@ -132,10 +208,6 @@
@media (prefers-reduced-motion: reduce) {
.shortcode-body { transition: none; }
}
.shortcode-body-inner table {
width: 100%;
border-collapse: collapse;
}
.shortcode-body .shortcode-collapse {
margin: 1rem 0 0.25rem;
}
@@ -420,9 +492,17 @@
font-style: italic;
}
.shortcode-notice {
display: inline-block;
padding: 0.1em 0.45em;
border-left: 2px solid var(--color-accent);
background: color-mix(in srgb, var(--color-accent) 8%, transparent);
color: var(--color-accent);
display: inline-flex;
width: 1em;
height: 1em;
align-items: center;
justify-content: center;
color: inherit;
line-height: 1;
vertical-align: -0.125em;
}
.shortcode-notice svg {
display: block;
width: 1em;
height: 1em;
}