diff --git a/data/skills.tsx b/data/skills.tsx index d08a501b..6b0c1e4e 100644 --- a/data/skills.tsx +++ b/data/skills.tsx @@ -1,63 +1,37 @@ -import { IconProps } from '@iconify/react' - -const SKILLS: IconProps[] = [ - { - icon: 'logos:vue', - style: { left: '1%', top: '1%' }, - }, - { - icon: 'logos:nuxt-icon', - style: { left: '4%', top: '5%' }, - }, - - { - icon: 'logos:react', - style: { right: '2%', top: '11%' }, - }, - { - icon: 'logos:nextjs-icon', - style: { right: '8%', top: '14%' }, - }, - - { - icon: 'logos:javascript', - style: { top: '5%', left: '54%' }, - }, - { - icon: 'logos:typescript-icon', - style: { top: '9%', left: '60%' }, - }, - - { - icon: 'logos:nodejs-icon-alt', - style: { top: '14%', left: '30%' }, - }, - { - icon: 'logos:nestjs', - style: { top: '19%', left: '38%' }, - }, - { - icon: 'logos:prisma', - style: { top: '24%', left: '50%' }, - }, - { - icon: 'logos:postgresql', - style: { top: '26%', left: '60%' }, - }, - - { - icon: 'logos:tailwindcss-icon', - style: { top: '30%', left: '90%' }, - }, - - { - icon: 'logos:visual-studio-code', - style: { bottom: '25%', right: '5%' }, - }, - { - icon: 'logos:docusaurus', - style: { bottom: '1%', left: '1%' }, - }, +// To find new icons, visit https://simpleicons.org/ +const SKILLS = [ + 'docusaurus', + 'typescript', + 'javascript', + 'react', + 'vue', + 'android', + 'html5', + 'css3', + 'tailwindcss', + 'shadcnui', + 'nodedotjs', + 'nextdotjs', + 'nuxtdotjs', + 'express', + 'nestjs', + 'hono', + 'electron', + 'prisma', + 'postgresql', + 'redis', + 'nginx', + 'vercel', + 'jest', + 'cypress', + 'docker', + 'git', + 'github', + 'visualstudiocode', + 'androidstudio', + 'figma', + 'python', + 'langchain', ] export default SKILLS diff --git a/package.json b/package.json index 92d7b3a6..f07fe55e 100644 --- a/package.json +++ b/package.json @@ -37,16 +37,20 @@ "@docusaurus/theme-search-algolia": "3.4.0", "@giscus/react": "^2.4.0", "@popperjs/core": "^2.11.8", + "@radix-ui/react-slot": "^1.0.2", "autoprefixer": "^10.4.19", + "class-variance-authority": "^0.7.0", "docusaurus-plugin-baidu-tongji": "0.0.0-beta.4", "docusaurus-plugin-image-zoom": "^1.0.1", "framer-motion": "^10.18.0", + "mini-svg-data-uri": "^1.4.4", "ora": "^7.0.1", "postcss": "^8.4.38", "prism-react-renderer": "^2.3.1", "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icon-cloud": "^4.1.4", "react-popper": "^2.3.0", "tailwind-merge": "^2.3.0", "tailwindcss": "^3.4.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9665a7f5..497223aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,15 @@ importers: '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.0.2(@types/react@18.2.21)(react@18.2.0) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 docusaurus-plugin-baidu-tongji: specifier: 0.0.0-beta.4 version: 0.0.0-beta.4 @@ -44,6 +50,9 @@ importers: framer-motion: specifier: ^10.18.0 version: 10.18.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + mini-svg-data-uri: + specifier: ^1.4.4 + version: 1.4.4 ora: specifier: ^7.0.1 version: 7.0.1 @@ -62,6 +71,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-icon-cloud: + specifier: ^4.1.4 + version: 4.1.4(react@18.2.0) react-popper: specifier: ^2.3.0 version: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -812,10 +824,6 @@ packages: resolution: {integrity: sha512-SAj8oKi8UogVi6eXQXKNPu8qZ78Yzy7zawrlTr0M+IuW/g8Qe9gVDhGcF9h1S69OyACpYoLxEzpjs1M15sI5wQ==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.22.15': - resolution: {integrity: sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.24.6': resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==} engines: {node: '>=6.9.0'} @@ -889,6 +897,10 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@csstools/convert-colors@2.0.0': + resolution: {integrity: sha512-P7BVvddsP2Wl5v3drJ3ArzpdfXMqoZ/oHOV/yFiGFb3JQr9Z9UXZ9tnHAKJsO89lfprR1F9ExW3Yij21EjEBIA==} + engines: {node: '>=6.0.0'} + '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} @@ -1247,6 +1259,24 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@radix-ui/react-compose-refs@1.0.1': + resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.0.2': + resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -1972,6 +2002,9 @@ packages: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} + class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + clean-css@5.3.2: resolution: {integrity: sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==} engines: {node: '>= 10.0'} @@ -3798,6 +3831,10 @@ packages: peerDependencies: webpack: ^5.0.0 + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -4506,6 +4543,12 @@ packages: react: ^16.6.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-icon-cloud@4.1.4: + resolution: {integrity: sha512-hc8yGNU98V6ObPdeNIt75M016xGMxbTWqB4l6exo1uwE5bvFU095unMbX2K3YBKYhGKEV3c7fSmq3jD3cRWX+A==} + engines: {node: '>=10'} + peerDependencies: + react: '>=16' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -6438,10 +6481,6 @@ snapshots: core-js-pure: 3.32.1 regenerator-runtime: 0.14.0 - '@babel/runtime@7.22.15': - dependencies: - regenerator-runtime: 0.14.0 - '@babel/runtime@7.24.6': dependencies: regenerator-runtime: 0.14.0 @@ -6511,6 +6550,8 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@csstools/convert-colors@2.0.0': {} + '@discoveryjs/json-ext@0.5.7': {} '@docsearch/css@3.5.2': {} @@ -7480,6 +7521,21 @@ snapshots: '@popperjs/core@2.11.8': {} + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.21)(react@18.2.0)': + dependencies: + '@babel/runtime': 7.24.6 + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.21 + + '@radix-ui/react-slot@1.0.2(@types/react@18.2.21)(react@18.2.0)': + dependencies: + '@babel/runtime': 7.24.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.21)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.2.21 + '@rollup/plugin-babel@5.3.1(@babel/core@7.23.5)(rollup@2.79.1)': dependencies: '@babel/core': 7.23.5 @@ -8318,6 +8374,10 @@ snapshots: ci-info@3.8.0: {} + class-variance-authority@0.7.0: + dependencies: + clsx: 2.0.0 + clean-css@5.3.2: dependencies: source-map: 0.6.1 @@ -10578,6 +10638,8 @@ snapshots: schema-utils: 4.2.0 webpack: 5.88.2 + mini-svg-data-uri@1.4.4: {} + minimalistic-assert@1.0.1: {} minimatch@3.1.2: @@ -11276,7 +11338,7 @@ snapshots: react-helmet-async@1.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.24.6 invariant: 2.2.4 prop-types: 15.8.1 react: 18.2.0 @@ -11284,6 +11346,11 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 + react-icon-cloud@4.1.4(react@18.2.0): + dependencies: + '@csstools/convert-colors': 2.0.0 + react: 18.2.0 + react-is@16.13.1: {} react-is@18.2.0: {} diff --git a/src/pages/_components/BlogSection/index.tsx b/src/components/landing/BlogSection/index.tsx similarity index 84% rename from src/pages/_components/BlogSection/index.tsx rename to src/components/landing/BlogSection/index.tsx index 11a6bf23..b8ec90fd 100644 --- a/src/pages/_components/BlogSection/index.tsx +++ b/src/components/landing/BlogSection/index.tsx @@ -6,8 +6,7 @@ import { cn } from '@site/src/lib/utils' import Image from '@theme/IdealImage' import { motion, useScroll, useTransform } from 'framer-motion' import React from 'react' - -import SectionTitle from '../SectionTitle' +import { Section } from '../Section' const chunk = (arr, size) => Array.from({ length: Math.ceil(arr.length / size) }, (_, i) => arr.slice(i * size, i * size + size)) @@ -67,13 +66,10 @@ export default function BlogSection(): JSX.Element { } return ( -
- - 近期博客 - -
+
近期博客} icon="ri:quill-pen-line" href={'/blog'}> +
{posts.map((postGroup, index) => ( -
+
{postGroup.map((post, i) => ( @@ -82,6 +78,6 @@ export default function BlogSection(): JSX.Element {
))}
-
+
) } diff --git a/src/components/landing/FeaturesSection/Github.tsx b/src/components/landing/FeaturesSection/Github.tsx new file mode 100644 index 00000000..114f3fe2 --- /dev/null +++ b/src/components/landing/FeaturesSection/Github.tsx @@ -0,0 +1,20 @@ +import Translate from '@docusaurus/Translate' + +import { Icon } from '@iconify/react' + +export default function Skill({ className }: { className?: string }) { + return ( +
+

+ + Github +

+
+ kuizuo's Github chart +
+
+ ) +} diff --git a/src/components/landing/FeaturesSection/Skill.tsx b/src/components/landing/FeaturesSection/Skill.tsx new file mode 100644 index 00000000..b6122f46 --- /dev/null +++ b/src/components/landing/FeaturesSection/Skill.tsx @@ -0,0 +1,19 @@ +import Translate from '@docusaurus/Translate' + +import { Icon } from '@iconify/react' +import SKILLS from '@site/data/skills' +import IconCloud from '../../magicui/icon-cloud' + +export default function Skill({ className }: { className?: string }) { + return ( +
+

+ + 技术栈 +

+
+ +
+
+ ) +} diff --git a/src/pages/_components/FeaturesSection/index.tsx b/src/components/landing/FeaturesSection/index.tsx similarity index 62% rename from src/pages/_components/FeaturesSection/index.tsx rename to src/components/landing/FeaturesSection/index.tsx index 3f5fb04e..abc0ef89 100644 --- a/src/pages/_components/FeaturesSection/index.tsx +++ b/src/components/landing/FeaturesSection/index.tsx @@ -1,7 +1,9 @@ import Translate from '@docusaurus/Translate' import features, { type FeatureItem } from '@site/data/features' import { cn } from '@site/src/lib/utils' -import SectionTitle from '../SectionTitle' +import { Section } from '../Section' +import Github from './Github' +import Skill from './Skill' function Feature({ title, Svg, text }: FeatureItem) { return ( @@ -23,15 +25,16 @@ function Feature({ title, Svg, text }: FeatureItem) { export default function FeaturesSection() { return ( -
- - 个人特点 - -
+
个人特点} icon={'ri:map-pin-user-line'}> +
{features.map((props, idx) => ( ))}
-
+
+ + +
+
) } diff --git a/src/pages/_components/Hero/img/hero_main.svg b/src/components/landing/Hero/img/hero.svg similarity index 100% rename from src/pages/_components/Hero/img/hero_main.svg rename to src/components/landing/Hero/img/hero.svg diff --git a/src/pages/_components/Hero/index.tsx b/src/components/landing/Hero/index.tsx similarity index 90% rename from src/pages/_components/Hero/index.tsx rename to src/components/landing/Hero/index.tsx index 0d8d26a3..dd416ae8 100644 --- a/src/pages/_components/Hero/index.tsx +++ b/src/components/landing/Hero/index.tsx @@ -2,7 +2,7 @@ import { type Variants, motion } from 'framer-motion' import Translate from '@docusaurus/Translate' -import HeroMain from './img/hero_main.svg' +import HeroSvg from './img/hero.svg' import SocialLinks from '@site/src/components/SocialLinks' import styles from './styles.module.css' @@ -50,7 +50,7 @@ function Name() { > 愧怍 - 👋 + 👋 ) } @@ -60,7 +60,7 @@ export default function Hero() {
- + 在这里我会分享各类技术栈所遇到问题与解决方案,带你了解最新的技术栈以及实际开发中如何应用,并希望我的开发经历对你有所启发。 @@ -73,7 +73,7 @@ export default function Hero() {
- + diff --git a/src/pages/_components/Hero/styles.module.css b/src/components/landing/Hero/styles.module.css similarity index 99% rename from src/pages/_components/Hero/styles.module.css rename to src/components/landing/Hero/styles.module.css index 8d22242b..4022f3e4 100644 --- a/src/pages/_components/Hero/styles.module.css +++ b/src/components/landing/Hero/styles.module.css @@ -42,10 +42,6 @@ background-clip: text; } -.wave { - @apply ml-1; -} - .background { @apply relative w-full h-[90%] grid place-items-center items-start; z-index: 5; diff --git a/src/pages/_components/ProjectSection/index.tsx b/src/components/landing/ProjectSection/index.tsx similarity index 81% rename from src/pages/_components/ProjectSection/index.tsx rename to src/components/landing/ProjectSection/index.tsx index 3ed8e0f1..ff53f7b0 100644 --- a/src/pages/_components/ProjectSection/index.tsx +++ b/src/components/landing/ProjectSection/index.tsx @@ -1,8 +1,7 @@ import Translate from '@docusaurus/Translate' import { type Project, projects } from '@site/data/projects' import Marquee from '@site/src/components/magicui/marquee' -import { cn } from '@site/src/lib/utils' -import SectionTitle from '../SectionTitle' +import { Section } from '../Section' const removeHttp = (url: string) => { return url.replace(/(^\w+:|^)\/\//, '') @@ -43,13 +42,12 @@ const Slider = ({ items }: { items: Project[] }) => { export default function ProjectSection() { return ( -
- - 项目展示 - -
- -
-
+
项目展示} + icon={'ri:projector-line'} + href={'/project'} + > + +
) } diff --git a/src/components/landing/Section/index.tsx b/src/components/landing/Section/index.tsx new file mode 100644 index 00000000..acf5436e --- /dev/null +++ b/src/components/landing/Section/index.tsx @@ -0,0 +1,34 @@ +import Link from '@docusaurus/Link' +import Translate from '@docusaurus/Translate' +import { Icon } from '@iconify/react' +import type React from 'react' + +interface SectionProps { + title: string | JSX.Element + icon?: string + href?: string + children: React.ReactNode +} + +export function Section({ title, icon, href, children }: SectionProps) { + return ( +
+
+

+ {icon && } + {title} +

+ {href && ( + + 查看更多 + + + )} +
+ {children} +
+ ) +} diff --git a/src/components/magicui/icon-cloud.tsx b/src/components/magicui/icon-cloud.tsx new file mode 100644 index 00000000..0ecb7eaf --- /dev/null +++ b/src/components/magicui/icon-cloud.tsx @@ -0,0 +1,78 @@ +'use client' + +import { useColorMode } from '@docusaurus/theme-common' +import { useEffect, useMemo, useState } from 'react' +import { Cloud, ICloud, SimpleIcon, fetchSimpleIcons, renderSimpleIcon } from 'react-icon-cloud' +export const cloudProps: Omit = { + containerProps: { + style: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + }, + }, + options: { + reverse: true, + depth: 1, + wheelZoom: false, + imageScale: 2, + activeCursor: 'default', + tooltip: 'native', + initial: [0.1, -0.1], + clickToFront: 500, + tooltipDelay: 0, + outlineColour: '#0000', + maxSpeed: 0.04, + minSpeed: 0.02, + // dragControl: false, + }, +} + +export const renderCustomIcon = (icon: SimpleIcon, theme: string) => { + const bgHex = theme === 'light' ? '#f3f2ef' : '#080510' + const fallbackHex = theme === 'light' ? '#6e6e73' : '#ffffff' + const minContrastRatio = theme === 'dark' ? 2 : 1.2 + + return renderSimpleIcon({ + icon, + bgHex, + fallbackHex, + minContrastRatio, + size: 42, + aProps: { + href: undefined, + target: undefined, + rel: undefined, + onClick: (e: any) => e.preventDefault(), + }, + }) +} + +export type DynamicCloudProps = { + iconSlugs: string[] +} + +type IconData = Awaited> + +export default function IconCloud({ iconSlugs }: DynamicCloudProps) { + const [data, setData] = useState(null) + const { colorMode: theme } = useColorMode() + + useEffect(() => { + fetchSimpleIcons({ slugs: iconSlugs }).then(setData) + }, [iconSlugs]) + + const renderedIcons = useMemo(() => { + if (!data) return null + + return Object.values(data.simpleIcons).map(icon => renderCustomIcon(icon, theme || 'light')) + }, [data, theme]) + + return ( + // @ts-ignore + + <>{renderedIcons} + + ) +} diff --git a/src/components/magicui/particles.tsx b/src/components/magicui/particles.tsx new file mode 100644 index 00000000..6adf9d58 --- /dev/null +++ b/src/components/magicui/particles.tsx @@ -0,0 +1,252 @@ +'use client' + +import React, { useEffect, useRef, useState } from 'react' + +interface MousePosition { + x: number + y: number +} + +function MousePosition(): MousePosition { + const [mousePosition, setMousePosition] = useState({ + x: 0, + y: 0, + }) + + useEffect(() => { + const handleMouseMove = (event: MouseEvent) => { + setMousePosition({ x: event.clientX, y: event.clientY }) + } + + window.addEventListener('mousemove', handleMouseMove) + + return () => { + window.removeEventListener('mousemove', handleMouseMove) + } + }, []) + + return mousePosition +} + +interface ParticlesProps { + className?: string + quantity?: number + staticity?: number + ease?: number + size?: number + refresh?: boolean + color?: string + vx?: number + vy?: number +} +function hexToRgb(hex: string): number[] { + hex = hex.replace('#', '') + const hexInt = parseInt(hex, 16) + const red = (hexInt >> 16) & 255 + const green = (hexInt >> 8) & 255 + const blue = hexInt & 255 + return [red, green, blue] +} + +const Particles: React.FC = ({ + className = '', + quantity = 100, + staticity = 50, + ease = 50, + size = 0.4, + refresh = false, + color = '#ffffff', + vx = 0, + vy = 0, +}) => { + const canvasRef = useRef(null) + const canvasContainerRef = useRef(null) + const context = useRef(null) + const circles = useRef([]) + const mousePosition = MousePosition() + const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) + const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 }) + const dpr = typeof window !== 'undefined' ? window.devicePixelRatio : 1 + + useEffect(() => { + if (canvasRef.current) { + context.current = canvasRef.current.getContext('2d') + } + initCanvas() + animate() + window.addEventListener('resize', initCanvas) + + return () => { + window.removeEventListener('resize', initCanvas) + } + }, [color]) + + useEffect(() => { + onMouseMove() + }, [mousePosition.x, mousePosition.y]) + + useEffect(() => { + initCanvas() + }, [refresh]) + + const initCanvas = () => { + resizeCanvas() + drawParticles() + } + + const onMouseMove = () => { + if (canvasRef.current) { + const rect = canvasRef.current.getBoundingClientRect() + const { w, h } = canvasSize.current + const x = mousePosition.x - rect.left - w / 2 + const y = mousePosition.y - rect.top - h / 2 + const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2 + if (inside) { + mouse.current.x = x + mouse.current.y = y + } + } + } + + type Circle = { + x: number + y: number + translateX: number + translateY: number + size: number + alpha: number + targetAlpha: number + dx: number + dy: number + magnetism: number + } + + const resizeCanvas = () => { + if (canvasContainerRef.current && canvasRef.current && context.current) { + circles.current.length = 0 + canvasSize.current.w = canvasContainerRef.current.offsetWidth + canvasSize.current.h = canvasContainerRef.current.offsetHeight + canvasRef.current.width = canvasSize.current.w * dpr + canvasRef.current.height = canvasSize.current.h * dpr + canvasRef.current.style.width = `${canvasSize.current.w}px` + canvasRef.current.style.height = `${canvasSize.current.h}px` + context.current.scale(dpr, dpr) + } + } + + const circleParams = (): Circle => { + const x = Math.floor(Math.random() * canvasSize.current.w) + const y = Math.floor(Math.random() * canvasSize.current.h) + const translateX = 0 + const translateY = 0 + const pSize = Math.floor(Math.random() * 2) + size + const alpha = 0 + const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1)) + const dx = (Math.random() - 0.5) * 0.1 + const dy = (Math.random() - 0.5) * 0.1 + const magnetism = 0.1 + Math.random() * 4 + return { + x, + y, + translateX, + translateY, + size: pSize, + alpha, + targetAlpha, + dx, + dy, + magnetism, + } + } + + const rgb = hexToRgb(color) + + const drawCircle = (circle: Circle, update = false) => { + if (context.current) { + const { x, y, translateX, translateY, size, alpha } = circle + context.current.translate(translateX, translateY) + context.current.beginPath() + context.current.arc(x, y, size, 0, 2 * Math.PI) + context.current.fillStyle = `rgba(${rgb.join(', ')}, ${alpha})` + context.current.fill() + context.current.setTransform(dpr, 0, 0, dpr, 0, 0) + + if (!update) { + circles.current.push(circle) + } + } + } + + const clearContext = () => { + if (context.current) { + context.current.clearRect(0, 0, canvasSize.current.w, canvasSize.current.h) + } + } + + const drawParticles = () => { + clearContext() + const particleCount = quantity + for (let i = 0; i < particleCount; i++) { + const circle = circleParams() + drawCircle(circle) + } + } + + const remapValue = (value: number, start1: number, end1: number, start2: number, end2: number): number => { + const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2 + return remapped > 0 ? remapped : 0 + } + + const animate = () => { + clearContext() + circles.current.forEach((circle: Circle, i: number) => { + // Handle the alpha value + const edge = [ + circle.x + circle.translateX - circle.size, // distance from left edge + canvasSize.current.w - circle.x - circle.translateX - circle.size, // distance from right edge + circle.y + circle.translateY - circle.size, // distance from top edge + canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge + ] + const closestEdge = edge.reduce((a, b) => Math.min(a, b)) + const remapClosestEdge = parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2)) + if (remapClosestEdge > 1) { + circle.alpha += 0.02 + if (circle.alpha > circle.targetAlpha) { + circle.alpha = circle.targetAlpha + } + } else { + circle.alpha = circle.targetAlpha * remapClosestEdge + } + circle.x += circle.dx + vx + circle.y += circle.dy + vy + circle.translateX += (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) / ease + circle.translateY += (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) / ease + + drawCircle(circle, true) + + // circle gets out of the canvas + if ( + circle.x < -circle.size || + circle.x > canvasSize.current.w + circle.size || + circle.y < -circle.size || + circle.y > canvasSize.current.h + circle.size + ) { + // remove the circle from the array + circles.current.splice(i, 1) + // create a new circle + const newCircle = circleParams() + drawCircle(newCircle) + // update the circle position + } + }) + window.requestAnimationFrame(animate) + } + + return ( + + ) +} + +export default Particles diff --git a/src/css/custom.css b/src/css/custom.css index d635f92d..5597dfb6 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -42,9 +42,9 @@ --ifm-heading-color: hsl(214deg 78% 17%); --ifm-heading-font-weight: 500; --ifm-font-weight-bold: 520; - --ifm-toc-border-color: #f1f5f9; + --ifm-border-color: #e5e7eb; - --content-background: #f8fafc; + --content-background: #fdfdfd; --blog-item-background-color: linear-gradient(180deg, #fcfcfc, #fff); --blog-item-shadow: 0 10px 18px #f1f5f9dd, 0 0 10px 0 #e4e4e7dd; @@ -62,7 +62,7 @@ --ifm-menu-color: #eceef1; --ifm-text-color: var(--ifm-menu-color); --ifm-secondary-text-color: #eee; - --ifm-toc-border-color: #313131; + --ifm-border-color: #313131; --content-background: #18181b; --blog-item-background-color: linear-gradient(180deg, #171717, #18181b); diff --git a/src/pages/_components/SectionTitle/index.tsx b/src/pages/_components/SectionTitle/index.tsx deleted file mode 100644 index 72d1c1b2..00000000 --- a/src/pages/_components/SectionTitle/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import Link from '@docusaurus/Link' -import Translate from '@docusaurus/Translate' -import { Icon } from '@iconify/react' -import type React from 'react' - -interface Props { - icon?: string - href?: string - children: React.ReactNode -} - -export default function SectionTitle({ children, icon, href }: Props) { - return ( -
-

- {icon && } - {children} -

- {href && ( - - 查看更多 - - - )} -
- ) -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 031bfe78..5e016f84 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,11 +1,12 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext' import Layout from '@theme/Layout' -import BlogSection from './_components/BlogSection' -import FeaturesSection from './_components/FeaturesSection' -import Hero from './_components/Hero' -import ProjectSection from './_components/ProjectSection' +import BlogSection from '../components/landing/BlogSection' +import FeaturesSection from '../components/landing/FeaturesSection' +import Hero from '../components/landing/Hero' +import ProjectSection from '../components/landing/ProjectSection' +import Particles from '../components/magicui/particles' -export default function Home(): JSX.Element { +export default function Home() { const { siteConfig: { customFields, tagline }, } = useDocusaurusContext() @@ -15,10 +16,18 @@ export default function Home(): JSX.Element {
-
- - - + + +
+
+ + + +
+
diff --git a/src/pages/resources/_components/ResourceCard/styles.module.css b/src/pages/resources/_components/ResourceCard/styles.module.css index cc2b5f29..6cc150b7 100644 --- a/src/pages/resources/_components/ResourceCard/styles.module.css +++ b/src/pages/resources/_components/ResourceCard/styles.module.css @@ -1,7 +1,7 @@ .resourceCard { background-color: var(--ifm-card-background-color); border-radius: 8px; - border: 1px solid var(--ifm-toc-border-color); + border: 1px solid var(--ifm-border-color); height: 100%; display: flex; flex-direction: row; diff --git a/src/theme/MyLayout/index.tsx b/src/theme/MyLayout/index.tsx index e2a15577..18a703df 100644 --- a/src/theme/MyLayout/index.tsx +++ b/src/theme/MyLayout/index.tsx @@ -7,7 +7,7 @@ export default function MyLayout({ children, maxWidth, ...layoutProps }: Props &
{children}
diff --git a/src/theme/TOC/styles.module.css b/src/theme/TOC/styles.module.css index 2a89b383..3e674e09 100644 --- a/src/theme/TOC/styles.module.css +++ b/src/theme/TOC/styles.module.css @@ -19,7 +19,7 @@ .hr { margin: 0; border: none; - border-top: 1px solid var(--ifm-toc-border-color); + border-top: 1px solid var(--ifm-border-color); } .percent { diff --git a/tailwind.config.js b/tailwind.config.js index 338a8421..21945175 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,3 +1,10 @@ +import svgToDataUri from 'mini-svg-data-uri' +import plugin from 'tailwindcss/plugin' + +const { + default: flattenColorPalette, +} = require('tailwindcss/lib/util/flattenColorPalette') + /** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], @@ -14,6 +21,7 @@ module.exports = { primaryLight: 'var(--ifm-color-primary-light)', primaryLighter: 'var(--ifm-color-primary-lighter)', primaryLightest: 'var(--ifm-color-primary-lightest)', + border: 'var(--ifm-border-color)', }, fontFamily: { misans: ['misans'], @@ -43,5 +51,21 @@ module.exports = { corePlugins: { preflight: false, }, - plugins: [], + plugins: [ + plugin(({ matchUtilities, theme }) => { + matchUtilities( + { + 'bg-grid': (value) => ({ + backgroundImage: `url("${svgToDataUri( + ``, + )}")`, + }), + }, + { + values: flattenColorPalette(theme('backgroundColor')), + type: 'color', + }, + ) + }), + ], }