diff --git a/astro.config.mjs b/astro.config.mjs index 4fb3d4e..9bb0b54 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -6,6 +6,8 @@ import remarkMath from 'remark-math'; import rehypeKatex from 'rehype-katex'; import shortcodes from './src/lib/shortcodes.ts'; import externalLinks from './src/lib/external-links.ts'; +import responsiveTables from './src/lib/responsive-tables.ts'; +import noticeShortcode from './src/lib/notice-shortcode.ts'; import { siteConfig } from './src/site.config.ts'; import { copyFileSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; @@ -46,7 +48,7 @@ export default defineConfig({ }, processor: unified({ remarkPlugins: [remarkMath, [shortcodes, { githubData, siteUrl: siteConfig.site.url }]], - rehypePlugins: [rehypeKatex, [externalLinks, { siteUrl: siteConfig.site.url }]] + rehypePlugins: [rehypeKatex, noticeShortcode, responsiveTables, [externalLinks, { siteUrl: siteConfig.site.url }]] }) } }); diff --git a/package-lock.json b/package-lock.json index f8edfd1..e823e4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,10 @@ { - "name": "mirages-astro", + "name": "eidolon", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "mirages-astro", + "name": "eidolon", "dependencies": { "@astrojs/markdown-remark": "^7.2.2", "@astrojs/rss": "^4.0.19", diff --git a/package.json b/package.json index ff78ca7..82ee1e2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "mirages-astro", + "name": "eidolon", "private": true, "type": "module", "scripts": { diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 0d24905..db99a6b 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -19,6 +19,7 @@ const blogsClubBadge = `https://www.blogsclub.org/badge/${blogDomain}/theme`; {siteConfig.footer.links.length > 0 && } +

Powered By Astro · Theme Eidolon

{siteConfig.footer.copyright &&

{siteConfig.footer.copyright}

} {siteConfig.footer.note && } {siteConfig.footer.theme &&

{siteConfig.footer.theme.label}

} diff --git a/src/content/pages/about.md b/src/content/pages/about.md index decd31a..bf2908a 100644 --- a/src/content/pages/about.md +++ b/src/content/pages/about.md @@ -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="在此之前"] diff --git a/src/content/posts/2026/08/eidolon-short-code-preview.md b/src/content/posts/2026/08/eidolon-short-code-preview.md new file mode 100644 index 0000000..bc865ef --- /dev/null +++ b/src/content/posts/2026/08/eidolon-short-code-preview.md @@ -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] \ No newline at end of file diff --git a/src/lib/notice-shortcode.ts b/src/lib/notice-shortcode.ts new file mode 100644 index 0000000..c9b4181 --- /dev/null +++ b/src/lib/notice-shortcode.ts @@ -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); +} diff --git a/src/lib/responsive-tables.ts b/src/lib/responsive-tables.ts new file mode 100644 index 0000000..961d800 --- /dev/null +++ b/src/lib/responsive-tables.ts @@ -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); +} diff --git a/src/lib/shortcodes.ts b/src/lib/shortcodes.ts index d7b798e..fed2e27 100644 --- a/src/lib/shortcodes.ts +++ b/src/lib/shortcodes.ts @@ -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 = { + info: '', + warning: '', + danger: '', + success: '', +}; 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, 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 `${escape(label || '链接不可用')}`; return `${inline(label || url)}`; } + if (name === 'file') { + const url = safeUrl(args.href ?? args.url ?? args.link); + if (!url) return `${escape(label || '文件链接不可用')}`; + const icon = ''; + return `${icon}${inline(label || url)}`; + } if (name === 'tag' || name === 'label') { const type = TAG_TYPES.has(args.type ?? '') ? args.type : 'default'; const outline = args.outline !== undefined ? ' shortcode-tag-outline' : ''; return `${inline(label)}`; } if (name === 'hint' || name === 'tip') { - const type = HINT_TYPES.has(args.type ?? '') ? args.type : 'info'; - const title = args.title ? `${inline(args.title)}` : ''; - return ``; + 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 = ``; + const heading = args.title ? `
${icon}

${inline(args.title)}

` : icon; + return ``; } if (name === 'collapse') { const open = args.open !== undefined || args.expanded !== undefined ? ' open' : ''; - return `
${inline(label || '展开内容')}
${markdown(body, true)}
`; + return `
${inline(label || '展开内容')}
${markdown(body, true, true)}
`; } if (name === 'tabs') { const parsed = parseBlocks(body); @@ -151,8 +192,28 @@ function renderBlock(name: string, args: Record, body = ''): str function parseBlocks(source: string): Array<{ name: string; args: Record; body: string }> { const result: Array<{ name: string; args: Record; 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; 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 { - replacedNotice = true; - return `${inline(body)}`; - }); - 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); diff --git a/src/styles/content.css b/src/styles/content.css index 7ba1a59..4186d7f 100644 --- a/src/styles/content.css +++ b/src/styles/content.css @@ -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; } diff --git a/src/styles/shortcodes.css b/src/styles/shortcodes.css index 4b84235..2ad5012 100644 --- a/src/styles/shortcodes.css +++ b/src/styles/shortcodes.css @@ -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; }