add more react function and remove typst support
This commit is contained in:
@@ -3,6 +3,7 @@ import { defineConfig } from 'astro/config';
|
||||
import mdx from '@astrojs/mdx';
|
||||
import vue from '@astrojs/vue';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
@@ -13,6 +14,7 @@ export default defineConfig({
|
||||
optimize: true,
|
||||
}),
|
||||
vue(),
|
||||
react(),
|
||||
tailwind({
|
||||
applyBaseStyles: false, // 我们将手动控制基础样式
|
||||
}),
|
||||
|
||||
211
package-lock.json
generated
211
package-lock.json
generated
@@ -9,11 +9,16 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.13",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/tailwind": "^5.1.5",
|
||||
"@astrojs/vue": "^5.1.4",
|
||||
"@myriaddreamin/typst-ts-renderer": "^0.7.0-rc2",
|
||||
"@myriaddreamin/typst.ts": "^0.7.0-rc2",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"astro": "^5.17.1",
|
||||
"marked": "^17.0.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"vue": "^3.5.29"
|
||||
}
|
||||
@@ -119,6 +124,26 @@
|
||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/react": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.4.2.tgz",
|
||||
"integrity": "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"ultrahtml": "^1.6.0",
|
||||
"vite": "^6.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.20.8 || ^20.3.0 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0",
|
||||
"@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0",
|
||||
"react": "^17.0.2 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/tailwind": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-5.1.5.tgz",
|
||||
@@ -537,6 +562,36 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-self": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
|
||||
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-react-jsx-source": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
|
||||
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-typescript": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
|
||||
@@ -1587,6 +1642,33 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/@myriaddreamin/typst-ts-renderer": {
|
||||
"version": "0.7.0-rc2",
|
||||
"resolved": "https://registry.npmjs.org/@myriaddreamin/typst-ts-renderer/-/typst-ts-renderer-0.7.0-rc2.tgz",
|
||||
"integrity": "sha512-god1tcb2YJDkQfA8gLGcAmykVGBpNKorqqDkXVy3InC18KRbsverJhlrHoONurNIU9JuIHoWjJ2D1ntpjPgzbA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@myriaddreamin/typst.ts": {
|
||||
"version": "0.7.0-rc2",
|
||||
"resolved": "https://registry.npmjs.org/@myriaddreamin/typst.ts/-/typst.ts-0.7.0-rc2.tgz",
|
||||
"integrity": "sha512-VM8JqsRcL3AEJ5cuPBn/YvnGTXK/BRPlxdGB2bR48Of/8OIGaPiunv2QfZBIMBBrtbTygUOtAY9BZvkS1AFqgA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"idb": "^7.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@myriaddreamin/typst-ts-renderer": "^0.7.0-rc2",
|
||||
"@myriaddreamin/typst-ts-web-compiler": "^0.7.0-rc2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@myriaddreamin/typst-ts-renderer": {
|
||||
"optional": true
|
||||
},
|
||||
"@myriaddreamin/typst-ts-web-compiler": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -2090,6 +2172,47 @@
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.7",
|
||||
"@babel/types": "^7.20.7",
|
||||
"@types/babel__generator": "*",
|
||||
"@types/babel__template": "*",
|
||||
"@types/babel__traverse": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__generator": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
|
||||
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__template": {
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
|
||||
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__traverse": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
|
||||
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
@@ -2153,6 +2276,26 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
||||
@@ -2165,6 +2308,32 @@
|
||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
|
||||
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.28.0",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.27",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"react-refresh": "^0.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.27",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
|
||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
@@ -4240,6 +4409,12 @@
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/idb": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/import-meta-resolve": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
|
||||
@@ -6346,6 +6521,36 @@
|
||||
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -6826,6 +7031,12 @@
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
|
||||
@@ -10,11 +10,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.13",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/tailwind": "^5.1.5",
|
||||
"@astrojs/vue": "^5.1.4",
|
||||
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"astro": "^5.17.1",
|
||||
"marked": "^17.0.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"vue": "^3.5.29"
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
/**
|
||||
* TypstBlock 组件
|
||||
*
|
||||
* 用于在 MDX 文章中渲染 Typst 数学公式和复杂排版。
|
||||
*
|
||||
* 使用方式:
|
||||
* <TypstBlock>
|
||||
* $ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
||||
* </TypstBlock>
|
||||
*
|
||||
* 注意:此组件需要在构建时安装 Typst 编译器。
|
||||
* 如果 Typst 未安装,会显示原始代码块作为降级方案。
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { class: className = '' } = Astro.props;
|
||||
|
||||
// 获取子内容(Typst 代码)
|
||||
const content = await Astro.slots.render('default');
|
||||
const typstCode = content?.trim() || '';
|
||||
---
|
||||
|
||||
<div class:list={['typst-block', 'my-6', className]}>
|
||||
{/*
|
||||
Typst 渲染区域
|
||||
在实际实现中,这里会调用 Typst 编译器将代码渲染为 SVG
|
||||
目前作为占位符显示
|
||||
*/}
|
||||
<div class="p-4 bg-muted rounded-lg border border-border overflow-x-auto">
|
||||
{typstCode ? (
|
||||
<div class="flex flex-col items-center">
|
||||
{/* SVG 输出区域 (构建时会被替换为实际的 Typst 渲染结果) */}
|
||||
<div class="typst-output text-lg" data-typst-code={typstCode}>
|
||||
<code class="font-mono text-primary-600 dark:text-primary-400">
|
||||
{typstCode}
|
||||
</code>
|
||||
</div>
|
||||
<span class="text-xs text-foreground/40 mt-2">Typst 公式</span>
|
||||
</div>
|
||||
) : (
|
||||
<p class="text-foreground/40 text-center">请提供 Typst 代码</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.typst-block {
|
||||
/* 确保 Typst 内容居中显示 */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.typst-output {
|
||||
/* 为数学公式提供合适的样式 */
|
||||
font-family: 'Latin Modern Math', 'STIX Two Math', 'Noto Serif SC', serif;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.typst-output svg {
|
||||
/* SVG 输出样式 */
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
47
src/components/react/AnimatedCard.tsx
Normal file
47
src/components/react/AnimatedCard.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
interface AnimatedCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export default function AnimatedCard({
|
||||
title,
|
||||
description,
|
||||
color = '#3b82f6'
|
||||
}: AnimatedCardProps) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const cardStyle: React.CSSProperties = {
|
||||
padding: '1.5rem',
|
||||
background: isHovered ? color : `${color}dd`,
|
||||
borderRadius: '1rem',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
transform: isHovered ? 'translateY(-8px) scale(1.02)' : 'translateY(0) scale(1)',
|
||||
boxShadow: isHovered
|
||||
? '0 20px 40px rgba(0, 0, 0, 0.3)'
|
||||
: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={cardStyle}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<h3 style={{
|
||||
margin: '0 0 0.5rem 0',
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{title}
|
||||
</h3>
|
||||
<p style={{ margin: 0, opacity: 0.9 }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
78
src/components/react/FlipCard.tsx
Normal file
78
src/components/react/FlipCard.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
interface FlipCardProps {
|
||||
frontTitle: string;
|
||||
frontDescription: string;
|
||||
backTitle: string;
|
||||
backDescription: string;
|
||||
frontColor?: string;
|
||||
backColor?: string;
|
||||
}
|
||||
|
||||
export default function FlipCard({
|
||||
frontTitle,
|
||||
frontDescription,
|
||||
backTitle,
|
||||
backDescription,
|
||||
frontColor = '#3b82f6',
|
||||
backColor = '#10b981'
|
||||
}: FlipCardProps) {
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
perspective: '1000px',
|
||||
width: '100%',
|
||||
height: '200px',
|
||||
cursor: 'pointer',
|
||||
};
|
||||
|
||||
const innerStyle: React.CSSProperties = {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transformStyle: 'preserve-3d',
|
||||
transition: 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
|
||||
};
|
||||
|
||||
const faceStyle = (color: string, isFront: boolean): React.CSSProperties => ({
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backfaceVisibility: 'hidden',
|
||||
borderRadius: '1rem',
|
||||
padding: '1.5rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: color,
|
||||
color: 'white',
|
||||
transform: isFront ? 'rotateY(0deg)' : 'rotateY(180deg)',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={containerStyle}
|
||||
onClick={() => setIsFlipped(!isFlipped)}
|
||||
onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.02)'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
|
||||
>
|
||||
<div style={innerStyle}>
|
||||
<div style={faceStyle(frontColor, true)}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h3 style={{ margin: '0 0 0.5rem 0', fontSize: '1.25rem', fontWeight: 'bold' }}>{frontTitle}</h3>
|
||||
<p style={{ margin: 0, opacity: 0.9 }}>{frontDescription}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style={faceStyle(backColor, false)}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h3 style={{ margin: '0 0 0.5rem 0', fontSize: '1.25rem', fontWeight: 'bold' }}>{backTitle}</h3>
|
||||
<p style={{ margin: 0, opacity: 0.9 }}>{backDescription}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
159
src/components/react/ParticleBackground.tsx
Normal file
159
src/components/react/ParticleBackground.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
interface Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
size: number;
|
||||
opacity: number;
|
||||
}
|
||||
|
||||
interface ParticleBackgroundProps {
|
||||
particleCount?: number;
|
||||
color?: string;
|
||||
speed?: number;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function ParticleBackground({
|
||||
particleCount = 50,
|
||||
color = '#3b82f6',
|
||||
speed = 1,
|
||||
children
|
||||
}: ParticleBackgroundProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const particlesRef = useRef<Particle[]>([]);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
// 设置画布大小
|
||||
const resizeCanvas = () => {
|
||||
const rect = canvas.parentElement?.getBoundingClientRect();
|
||||
if (rect) {
|
||||
canvas.width = rect.width;
|
||||
canvas.height = rect.height;
|
||||
}
|
||||
};
|
||||
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// 初始化粒子
|
||||
const initParticles = () => {
|
||||
const newParticles: Particle[] = [];
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
newParticles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
vx: (Math.random() - 0.5) * speed,
|
||||
vy: (Math.random() - 0.5) * speed,
|
||||
size: Math.random() * 3 + 1,
|
||||
opacity: Math.random() * 0.5 + 0.2,
|
||||
});
|
||||
}
|
||||
particlesRef.current = newParticles;
|
||||
};
|
||||
|
||||
initParticles();
|
||||
|
||||
// 动画循环
|
||||
const animate = () => {
|
||||
if (!ctx || !canvas) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const particles = particlesRef.current;
|
||||
|
||||
particles.forEach((particle: Particle, i: number) => {
|
||||
// 更新位置
|
||||
particle.x += particle.vx;
|
||||
particle.y += particle.vy;
|
||||
|
||||
// 边界检测
|
||||
if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
|
||||
if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;
|
||||
|
||||
// 绘制粒子
|
||||
ctx.beginPath();
|
||||
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = `${color}${Math.floor(particle.opacity * 255).toString(16).padStart(2, '0')}`;
|
||||
ctx.fill();
|
||||
|
||||
// 绘制连线
|
||||
particles.slice(i + 1).forEach((other: Particle) => {
|
||||
const dx = particle.x - other.x;
|
||||
const dy = particle.y - other.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 150) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(particle.x, particle.y);
|
||||
ctx.lineTo(other.x, other.y);
|
||||
const opacity = (1 - distance / 150) * 0.2;
|
||||
ctx.strokeStyle = `${color}${Math.floor(opacity * 255).toString(16).padStart(2, '0')}`;
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
animationRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resizeCanvas);
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, [color, speed, particleCount]);
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '300px',
|
||||
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)',
|
||||
borderRadius: '1rem',
|
||||
overflow: 'hidden',
|
||||
};
|
||||
|
||||
const canvasStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
const contentStyle: React.CSSProperties = {
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
color: 'white',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<canvas ref={canvasRef} style={canvasStyle} />
|
||||
<div style={contentStyle}>
|
||||
{children || (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h3 style={{ fontSize: '2rem', marginBottom: '0.5rem' }}>✨ 粒子动效</h3>
|
||||
<p style={{ opacity: 0.8 }}>鼠标悬停查看效果</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
76
src/components/react/TypewriterText.tsx
Normal file
76
src/components/react/TypewriterText.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface TypewriterTextProps {
|
||||
text: string;
|
||||
speed?: number;
|
||||
loop?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default function TypewriterText({
|
||||
text,
|
||||
speed = 100,
|
||||
loop = false,
|
||||
style = {}
|
||||
}: TypewriterTextProps) {
|
||||
const [displayedText, setDisplayedText] = useState('');
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (!isDeleting) {
|
||||
// 打字
|
||||
if (currentIndex < text.length) {
|
||||
setDisplayedText(text.slice(0, currentIndex + 1));
|
||||
setCurrentIndex(currentIndex + 1);
|
||||
} else if (loop) {
|
||||
// 打完后等待,然后开始删除
|
||||
setTimeout(() => setIsDeleting(true), 1500);
|
||||
}
|
||||
} else {
|
||||
// 删除
|
||||
if (currentIndex > 0) {
|
||||
setDisplayedText(text.slice(0, currentIndex - 1));
|
||||
setCurrentIndex(currentIndex - 1);
|
||||
} else {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
}
|
||||
}, isDeleting ? speed / 2 : speed);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [currentIndex, isDeleting, text, speed, loop]);
|
||||
|
||||
const containerStyle: React.CSSProperties = {
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '1.5rem',
|
||||
color: '#3b82f6',
|
||||
...style
|
||||
};
|
||||
|
||||
const cursorStyle: React.CSSProperties = {
|
||||
display: 'inline-block',
|
||||
width: '3px',
|
||||
height: '1.5rem',
|
||||
background: '#3b82f6',
|
||||
marginLeft: '2px',
|
||||
animation: 'blink 1s infinite',
|
||||
verticalAlign: 'middle',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
`}</style>
|
||||
<span style={containerStyle}>
|
||||
{displayedText}
|
||||
<span style={cursorStyle} />
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -28,13 +28,13 @@ NovaBlog 是一个极简、高效的程序员博客系统,采用 **静态渲
|
||||
|
||||
上面的组件会在可见时自动加载并挂载 JavaScript。
|
||||
|
||||
### 📐 Typst 学术排版
|
||||
### 📐 数学公式排版
|
||||
|
||||
支持复杂的数学公式渲染:
|
||||
|
||||
<TypstBlock>
|
||||
```
|
||||
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
||||
</TypstBlock>
|
||||
```
|
||||
|
||||
## 代码高亮
|
||||
|
||||
|
||||
212
src/content/blog/react-animated-components.mdx
Normal file
212
src/content/blog/react-animated-components.mdx
Normal file
@@ -0,0 +1,212 @@
|
||||
---
|
||||
title: React 动效组件展示
|
||||
description: 展示 NovaBlog 中可用的 React 动效 HTML 组件,包括悬浮卡片、打字机效果、翻转卡片和粒子背景
|
||||
pubDate: 2024-01-20
|
||||
author: NovaBlog
|
||||
tags: [React, 动效, 组件, 教程]
|
||||
category: 教程
|
||||
heroImage: /images/react-components.jpg
|
||||
---
|
||||
|
||||
import AnimatedCard from '../../components/react/AnimatedCard';
|
||||
import TypewriterText from '../../components/react/TypewriterText';
|
||||
import FlipCard from '../../components/react/FlipCard';
|
||||
import ParticleBackground from '../../components/react/ParticleBackground';
|
||||
|
||||
# React 动效组件展示
|
||||
|
||||
NovaBlog 支持在 MDX 中直接使用 React 组件,实现丰富的交互动效。本文展示了一些内置的动效组件示例。
|
||||
|
||||
## 🎴 悬浮卡片 (AnimatedCard)
|
||||
|
||||
鼠标悬停时卡片会浮起并放大,配合阴影效果增强立体感。
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '1.5rem', margin: '2rem 0' }}>
|
||||
<AnimatedCard
|
||||
title="🚀 快速开发"
|
||||
description="使用 Astro + React 实现极速开发体验"
|
||||
color="#3b82f6"
|
||||
client:load
|
||||
/>
|
||||
<AnimatedCard
|
||||
title="🎨 精美设计"
|
||||
description="内置多种动效组件,轻松创建炫酷页面"
|
||||
color="#10b981"
|
||||
client:load
|
||||
/>
|
||||
<AnimatedCard
|
||||
title="⚡ 高性能"
|
||||
description="Islands 架构,按需加载,极致性能"
|
||||
color="#f59e0b"
|
||||
client:load
|
||||
/>
|
||||
</div>
|
||||
|
||||
## ⌨️ 打字机效果 (TypewriterText)
|
||||
|
||||
模拟打字机的逐字显示效果,支持循环播放。
|
||||
|
||||
<div style={{
|
||||
background: '#1a1a2e',
|
||||
padding: '2rem',
|
||||
borderRadius: '1rem',
|
||||
textAlign: 'center',
|
||||
margin: '2rem 0'
|
||||
}}>
|
||||
<TypewriterText
|
||||
text="Hello, NovaBlog! 欢迎来到你的新博客..."
|
||||
speed={80}
|
||||
loop={true}
|
||||
client:load
|
||||
/>
|
||||
</div>
|
||||
|
||||
### 使用方式
|
||||
|
||||
```tsx
|
||||
<TypewriterText
|
||||
text="你要显示的文字"
|
||||
speed={100} // 打字速度(毫秒)
|
||||
loop={true} // 是否循环播放
|
||||
client:load
|
||||
/>
|
||||
```
|
||||
|
||||
## 🔄 翻转卡片 (FlipCard)
|
||||
|
||||
点击卡片实现 3D 翻转效果,适合展示正反两面内容。
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.5rem', margin: '2rem 0' }}>
|
||||
<FlipCard
|
||||
frontTitle="💡 小提示"
|
||||
frontDescription="点击翻转查看更多"
|
||||
backTitle="✅ 详细说明"
|
||||
backDescription="这是一个翻转卡片的背面内容"
|
||||
frontColor="#8b5cf6"
|
||||
backColor="#06b6d4"
|
||||
client:load
|
||||
/>
|
||||
<FlipCard
|
||||
frontTitle="🎯 技术栈"
|
||||
frontDescription="React + TypeScript"
|
||||
backTitle="📦 组件库"
|
||||
backDescription="支持自定义样式和动画"
|
||||
frontColor="#ec4899"
|
||||
backColor="#14b8a6"
|
||||
client:load
|
||||
/>
|
||||
</div>
|
||||
|
||||
### 使用方式
|
||||
|
||||
```tsx
|
||||
<FlipCard
|
||||
frontContent={<div>正面内容</div>}
|
||||
backContent={<div>背面内容</div>}
|
||||
frontColor="#3b82f6"
|
||||
backColor="#10b981"
|
||||
client:load
|
||||
/>
|
||||
```
|
||||
|
||||
## ✨ 粒子背景 (ParticleBackground)
|
||||
|
||||
基于 Canvas 的粒子动画背景,粒子之间会自动连线,营造科技感。
|
||||
|
||||
<ParticleBackground
|
||||
particleCount={60}
|
||||
color="#60a5fa"
|
||||
speed={0.8}
|
||||
client:load
|
||||
/>
|
||||
|
||||
### 自定义内容
|
||||
|
||||
<ParticleBackground
|
||||
particleCount={30}
|
||||
color="#f472b6"
|
||||
speed={1.5}
|
||||
client:load
|
||||
>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h2 style={{ fontSize: '2.5rem', marginBottom: '1rem' }}>🎉 自定义内容</h2>
|
||||
<p>可以在粒子背景上放置任意内容</p>
|
||||
</div>
|
||||
</ParticleBackground>
|
||||
|
||||
## 📝 如何在文章中使用
|
||||
|
||||
### 1. 导入组件
|
||||
|
||||
在文章顶部添加 import 语句:
|
||||
|
||||
```mdx
|
||||
import AnimatedCard from '../../components/react/AnimatedCard';
|
||||
```
|
||||
|
||||
### 2. 使用组件
|
||||
|
||||
```mdx
|
||||
<AnimatedCard
|
||||
title="标题"
|
||||
description="描述"
|
||||
color="#3b82f6"
|
||||
client:load
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. client 指令说明
|
||||
|
||||
| 指令 | 说明 |
|
||||
|------|------|
|
||||
| `client:load` | 页面加载时立即激活组件 |
|
||||
| `client:visible` | 组件进入视口时激活 |
|
||||
| `client:idle` | 浏览器空闲时激活 |
|
||||
| `client:media="(min-width: 768px)"` | 满足媒体查询时激活 |
|
||||
|
||||
## 🎨 创建自定义组件
|
||||
|
||||
你可以在 `src/components/react/` 目录下创建自己的 React 组件:
|
||||
|
||||
```tsx
|
||||
// src/components/react/MyComponent.tsx
|
||||
import { useState } from 'react';
|
||||
|
||||
interface MyComponentProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default function MyComponent({ title }: MyComponentProps) {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div style={{ padding: '1rem', background: '#f3f4f6' }}>
|
||||
<h3>{title}</h3>
|
||||
<button onClick={() => setCount(count + 1)}>
|
||||
点击次数: {count}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
然后在文章中使用:
|
||||
|
||||
```mdx
|
||||
import MyComponent from '../../components/react/MyComponent';
|
||||
|
||||
<MyComponent title="我的组件" client:load />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
NovaBlog 提供了灵活的组件系统,让你可以在 Markdown 中嵌入丰富的交互内容。通过 React 组件,你可以实现:
|
||||
|
||||
- 🎴 **视觉效果**:悬浮、翻转、渐变等动画
|
||||
- ⌨️ **动态文字**:打字机、滚动、闪烁效果
|
||||
- ✨ **背景特效**:粒子、波浪、光效
|
||||
- 🎮 **交互功能**:计数器、表单、游戏
|
||||
|
||||
快去尝试创建属于你自己的动效组件吧! 🚀
|
||||
202
src/content/blog/typst-typesetting-showcase.mdx
Normal file
202
src/content/blog/typst-typesetting-showcase.mdx
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: Typst 学术排版展示
|
||||
description: 展示 NovaBlog 中 Typst 的高级排版能力,包括数学公式、矩阵等学术排版
|
||||
pubDate: 2024-01-25
|
||||
author: NovaBlog
|
||||
tags: [Typst, 排版,数学公式,学术写作]
|
||||
category: 教程
|
||||
heroImage: /images/hello-world.jpg
|
||||
---
|
||||
|
||||
# Typst 学术排版展示
|
||||
|
||||
Typst 是一款现代化的排版系统,专为学术写作和技术文档设计。本文将展示 NovaBlog 中 Typst 的数学公式排版能力。
|
||||
|
||||
## 📐 基础数学公式
|
||||
|
||||
### 积分公式
|
||||
|
||||
```
|
||||
$ integral_0^infinity e^(-x^2) dif x = sqrt(pi) / 2 $
|
||||
```
|
||||
|
||||
### 极限与导数
|
||||
|
||||
```
|
||||
$ lim_(x arrow 0) frac(sin x, x) = 1 $
|
||||
```
|
||||
|
||||
```
|
||||
$ frac(dif f, dif x) = lim_(h arrow 0) frac(f(x + h) - f(x), h) $
|
||||
```
|
||||
|
||||
### 微积分基本定理
|
||||
|
||||
```
|
||||
$ integral_a^b f(x) dif x = F(b) - F(a) $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔢 矩阵与线性代数
|
||||
|
||||
### 基础矩阵
|
||||
|
||||
```
|
||||
$ A = mat(1, 2, 3; 4, 5, 6; 7, 8, 9) $
|
||||
```
|
||||
|
||||
### 行列式展开
|
||||
|
||||
```
|
||||
$ det(A) = sum_(i=1)^n a_(1i) dot (-1)^(1+i) dot M_(1i) $
|
||||
```
|
||||
|
||||
### 特征值方程
|
||||
|
||||
```
|
||||
$ det(A - lambda I) = 0 $
|
||||
```
|
||||
|
||||
### 二次型
|
||||
|
||||
```
|
||||
$ Q(x) = x^T A x = sum_(i,j) a_(ij) x_i x_j $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 统计学与概率论
|
||||
|
||||
### 贝叶斯定理
|
||||
|
||||
```
|
||||
$ P(A | B) = frac(P(B | A) dot P(A), P(B)) $
|
||||
```
|
||||
|
||||
### 正态分布
|
||||
|
||||
```
|
||||
$ X tilde N(mu, sigma^2) arrow.f P(x) = frac(1, sigma sqrt(2 pi)) e^(-frac((x-mu)^2, 2 sigma^2)) $
|
||||
```
|
||||
|
||||
### 期望与方差
|
||||
|
||||
```
|
||||
$ E[X] = sum_(i=1)^n x_i p_i quad Var(X) = E[X^2] - (E[X])^2 $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧮 复杂嵌套表达式
|
||||
|
||||
### 巴塞尔问题
|
||||
|
||||
```
|
||||
$ sum_(n=1)^infinity frac(1, n^2) = frac(pi^2, 6) $
|
||||
```
|
||||
|
||||
### 欧拉恒等式
|
||||
|
||||
```
|
||||
$ e^(i pi) + 1 = 0 $
|
||||
```
|
||||
|
||||
### Gamma 函数
|
||||
|
||||
```
|
||||
$ Gamma(z) = integral_0^infinity t^(z-1) e^(-t) dif t $
|
||||
```
|
||||
|
||||
### 斯特林公式
|
||||
|
||||
```
|
||||
$ n! tilde sqrt(2 pi n) (n/e)^n $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 物理学公式
|
||||
|
||||
### 麦克斯韦方程组
|
||||
|
||||
```
|
||||
$ nabla dot E = frac(rho, epsilon_0) $
|
||||
```
|
||||
|
||||
```
|
||||
$ nabla dot B = 0 $
|
||||
```
|
||||
|
||||
```
|
||||
$ nabla times E = -frac(partial B, partial t) $
|
||||
```
|
||||
|
||||
```
|
||||
$ nabla times B = mu_0 J + mu_0 epsilon_0 frac(partial E, partial t) $
|
||||
```
|
||||
|
||||
### 狭义相对论
|
||||
|
||||
```
|
||||
$ E = m c^2 $
|
||||
```
|
||||
|
||||
```
|
||||
$ t' = frac(t, sqrt(1 - v^2/c^2)) = gamma t $
|
||||
```
|
||||
|
||||
```
|
||||
$ gamma = frac(1, sqrt(1 - v^2/c^2)) $
|
||||
```
|
||||
|
||||
### 薛定谔方程
|
||||
|
||||
```
|
||||
$ i hbar frac(partial psi, partial t) = H^ psi $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔬 化学方程式
|
||||
|
||||
```
|
||||
$ 6 CO_2 + 6 H_2 O arrow.r C_6 H_12 O_6 + 6 O_2 $
|
||||
```
|
||||
|
||||
```
|
||||
$ CH_4 + 2 O_2 arrow.r CO_2 + 2 H_2 O $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📐 集合论
|
||||
|
||||
```
|
||||
$ A union B = { x | x in A text( 或 ) x in B } $
|
||||
```
|
||||
|
||||
```
|
||||
$ A intersect B = { x | x in A text( 且 ) x in B } $
|
||||
```
|
||||
|
||||
```
|
||||
$ A setminus B = { x | x in A text( 且 ) x notin B } $
|
||||
```
|
||||
|
||||
```
|
||||
$ P(A) = { S | S subset.eq A } $
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
NovaBlog 曾经通过 TypstBlock 组件支持专业的数学公式排版,适合:
|
||||
|
||||
- 📐 **数学博客**:微积分、线性代数、概率统计
|
||||
- ⚡ **物理笔记**:经典力学、电磁学、量子力学
|
||||
- 🔬 **化学公式**:化学反应方程式
|
||||
- 📊 **学术论文**:复杂的数学推导和证明
|
||||
|
||||
由于技术原因,Typst 支持已暂时移除。
|
||||
@@ -2,12 +2,10 @@
|
||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
||||
import PostLayout from '../../layouts/PostLayout.astro';
|
||||
import Counter from '../../components/Counter.vue';
|
||||
import TypstBlock from '../../components/TypstBlock.astro';
|
||||
|
||||
// MDX 组件映射
|
||||
const mdxComponents = {
|
||||
Counter,
|
||||
TypstBlock,
|
||||
};
|
||||
|
||||
// 生成所有文章的静态路径
|
||||
|
||||
Reference in New Issue
Block a user