This section contains a summary of this file. This file contains a packed representation of the entire repository's contents. It is designed to be easily consumable by AI systems for analysis, code review, or other automated processes. The content is organized as follows: 1. This summary section 2. Repository information 3. Directory structure 4. Repository files (if enabled) 5. Multiple file entries, each consisting of: - File path as an attribute - Full contents of the file - This file should be treated as read-only. Any changes should be made to the original repository files, not this packed version. - When processing this file, use the file path to distinguish between different files in the repository. - Be aware that this file may contain sensitive information. Handle it with the same level of security as you would the original repository. - Some files may have been excluded based on .gitignore rules and Repomix's configuration - Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files - Files matching patterns in .gitignore are excluded - Files matching default ignore patterns are excluded - Files are sorted by Git change count (files with more changes are at the bottom) .gitea/ workflows/ build-image.yml debug.yml assets/ homelab.png lab.png logo.png public/ assets/ blog/ cover-brutalism.jpg cover-dreamcore.jpg cover-free-icon-library.jpg cover-neobrutalism.jpg cover-portfolio-site.jpg cover-retro.jpg cover-retrofuturism.jpg cover-spacepunk.jpg experiences/ company.jpg home/ color-picker.svg gradientshub.jpg social/ social-behance.jpg social-dribbble.jpg social-email.jpg social-figma.jpg social-gitea.png social-github.jpg social-gumroad.jpg social-linkedin.png social-twitter.jpg social-xiaohongshu.jpg stack/ astro.png bootstrap.png cloudflare.png css.png github.png html.png js.png netlify.png nextjs.png nodejs.png npm.png nuxtjs.png tailwind.png vercel.png vscode.png tools/ game/ 01-game-cassette.png 02-game-cassette.png 03-game-cassette.png 04-game-cassette.png 05-game-cassette.png logo/ ricoui.png tool-gradienthub.svg tool-inspoweb.png tool-ricoog.svg tool-todo.png tool-uiuxdeck.svg deck.png dreamcore.jpg dreamcore.mp4 keyboard.png mac.png mini-btn.png retro-computer.png retro-computer.svg spin-2.png spin-bar.png spin-bar.svg spin.png spin.svg tool-icon-bg.png works/ 3d-valentines.jpg 3d-valentines.mp4 gradientshub.jpg gradientshub.mp4 inspoweb.jpg luonmodels.jpg luonmodels.mp4 ricoog.jpg ricoog.mp4 ricoui.jpg ricoui.mp4 uiuxdeck.jpg avatar.png freelance.png hec-ia.png homelab.png logo copy.png logo.jpg logo.png fonts/ favicon.png InstrumentSerif-Italic.woff2 InstrumentSerif-Regular.woff2 Inter-Variable.woff2 og.jpg robots.txt src/ assets/ js/ main.js freelance.png homelab.png logo.jpg logo.png collections/ experiences.json featuredwork.json menu.json social.json works.json components/ cards/ BlogCard.astro SocialCard.astro WorkCard.astro elements/ AboutExperience.astro PageHeader.astro SectionHeader.astro SeparatorLine.astro home/ HeroCard.astro sections/ BlogSection.astro Explore.astro FeaturedWork.astro Footer.astro Header.astro WorksSection.astro ui/ AnimatedText.astro Button.astro Logo.astro Matter.astro Tools.astro TopBg.astro widgets/ ActionBar.astro Meta.astro OptimizedImage.astro Pagination.astro Toc.astro ToTop.astro TrackGa.astro config/ site.js layouts/ Layout.astro Meta.astro PageLayout.astro PostLayout.astro pages/ blog/ page/ [page].astro [...slug].astro index.astro work/ freelance.astro hec-ia.astro homelab.astro 404.astro about.astro index.astro infrastructure.astro rss.xml.js works.astro styles/ aos-custom.css article-enhancements.css article.css global.css types/ matter.d.ts content.config.js env.d.ts .editorconfig .env.example .gitignore astro.config.mjs CONTRIBUTING.md docker-compose.yml Dockerfile nginx.conf package.json pnpm-workspace.yaml README-zh.md README.md tailwind.config.mjs tsconfig.json This section contains the contents of the repository's files. name: Debug Context on: workflow_dispatch jobs: debug: runs-on: ubuntu-latest steps: - name: Print Gitea context run: | echo "server_url: ${{ gitea.server_url }}" echo "api_url: ${{ gitea.api_url }}" echo "repository: ${{ gitea.repository }}" User-agent: * Allow: / // Add your javascript here import AOS from 'aos'; window.darkMode = false; const stickyClasses = []; const unstickyClasses = []; const stickyClassesContainer = [ "shadow-[0px_1px_4px_0_rgba(25,33,61,0.06)]", "rounded-[20px]", "bg-[#ffffff]/65", "border-[#ffffff]/65", "dark:border-neutral-600/40", "dark:bg-neutral-900/60", "backdrop-blur-2xl", "backdrop-brightness-120", ]; const unstickyClassesContainer = [ "shadow-none", "border-transparent", "rounded-none", "bg-transparent", ]; let headerElement = null; document.addEventListener("DOMContentLoaded", () => { headerElement = document.getElementById("header"); if ( localStorage.getItem("dark_mode") && localStorage.getItem("dark_mode") === "true" ) { window.darkMode = true; showNight(); } else { showDay(); } stickyHeaderFuncionality(); applyMenuItemClasses(); evaluateHeaderPosition(); mobileMenuFunctionality(); // 初始化 AOS AOS.init({ duration: 400, easing: 'ease-out-cubic', once: true, offset: 20, delay: 0, }); }); // window.toggleDarkMode = function(){ // document.documentElement.classList.toggle('dark'); // if(document.documentElement.classList.contains('dark')){ // localStorage.setItem('dark_mode', true); // window.darkMode = true; // } else { // window.darkMode = false; // localStorage.setItem('dark_mode', false); // } // } window.stickyHeaderFuncionality = () => { window.addEventListener("scroll", () => { evaluateHeaderPosition(); }); }; window.evaluateHeaderPosition = () => { if (window.scrollY > 48) { headerElement.firstElementChild.classList.add(...stickyClassesContainer); headerElement.firstElementChild.classList.remove( ...unstickyClassesContainer, ); headerElement.classList.add(...stickyClasses); headerElement.classList.remove(...unstickyClasses); // document.getElementById("menu").classList.add("top-[75px]"); // document.getElementById("menu").classList.remove("top-[75px]"); } else { headerElement.firstElementChild.classList.remove(...stickyClassesContainer); headerElement.firstElementChild.classList.add(...unstickyClassesContainer); headerElement.classList.add(...unstickyClasses); headerElement.classList.remove(...stickyClasses); // document.getElementById("menu").classList.remove("top-[56px]"); // document.getElementById("menu").classList.add("top-[75px]"); } }; document.getElementById("darkToggle").addEventListener("click", () => { document.documentElement.classList.add("duration-300"); if (document.documentElement.classList.contains("dark")) { localStorage.removeItem("dark_mode"); showDay(true); } else { localStorage.setItem("dark_mode", true); showNight(true); } }); function showDay(animate) { document.getElementById("sun").classList.remove("setting"); document.getElementById("moon").classList.remove("rising"); let timeout = 0; if (animate) { timeout = 500; document.getElementById("moon").classList.add("setting"); } setTimeout(() => { document.getElementById("dayText").classList.remove("hidden"); document.getElementById("nightText").classList.add("hidden"); document.getElementById("moon").classList.add("hidden"); document.getElementById("sun").classList.remove("hidden"); if (animate) { document.documentElement.classList.remove("dark"); document.getElementById("sun").classList.add("rising"); } }, timeout); } function showNight(animate) { document.getElementById("moon").classList.remove("setting"); document.getElementById("sun").classList.remove("rising"); let timeout = 0; if (animate) { timeout = 500; document.getElementById("sun").classList.add("setting"); } setTimeout(() => { document.getElementById("nightText").classList.remove("hidden"); document.getElementById("dayText").classList.add("hidden"); document.getElementById("sun").classList.add("hidden"); document.getElementById("moon").classList.remove("hidden"); if (animate) { document.documentElement.classList.add("dark"); document.getElementById("moon").classList.add("rising"); } }, timeout); } window.applyMenuItemClasses = () => { const menuItems = document.querySelectorAll("#menu a"); for (let i = 0; i < menuItems.length ; i++) { if (menuItems[i].pathname=== window.location.pathname) { menuItems[i].classList.add("text-neutral-900", "dark:text-white"); } } //:class="{ 'text-neutral-900 dark:text-white': window.location.pathname == '{menu.url}', 'text-neutral-700 dark:text-neutral-400': window.location.pathname != '{menu.url}' }" }; function mobileMenuFunctionality() { document.getElementById("openMenu").addEventListener("click", () => { openMobileMenu(); }); document.getElementById("closeMenu").addEventListener("click", () => { closeMobileMenu(); }); } window.openMobileMenu = () => { document.getElementById("openMenu").classList.add("hidden"); document.getElementById("closeMenu").classList.remove("hidden"); document.getElementById("menu").classList.remove("hidden"); document.getElementById("mobileMenuBackground").classList.add("opacity-0"); document.getElementById("mobileMenuBackground").classList.remove("hidden"); setTimeout(() => { document .getElementById("mobileMenuBackground") .classList.remove("opacity-0"); }, 1); }; window.closeMobileMenu = () => { document.getElementById("closeMenu").classList.add("hidden"); document.getElementById("openMenu").classList.remove("hidden"); document.getElementById("menu").classList.add("hidden"); document.getElementById("mobileMenuBackground").classList.add("hidden"); }; --- const { content, layout = "vertical", "data-aos": dataAos, "data-aos-delay": dataAosDelay } = Astro.props; const { title, description, publishDate, tags = [] as string[], img, img_alt, slug, link } = content; const formattedDate = new Date(publishDate).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); const postLink = link || `/blog/${slug}`; // Check if horizontal layout const isHorizontal = layout === "horizontal"; --- --- import social from "@/collections/social.json"; const { displaySocialIds } = Astro.props; // 配置要显示的社交媒体 ID(留空则显示所有) //const displaySocialIds: number[] = [1, 2, 3]; // 只显示 ID 为 1, 2, 3 的社交媒体 // const displaySocialIds: number[] = []; // 显示所有(当数组为空时) const filteredSocial = social.filter(item => { if (item.isShow === false) return false; if (displaySocialIds.length > 0) { return displaySocialIds.includes(item.id); } return true; }); --- --- interface Props { name: string; // Required image: string; // Required url: string; // Required description?: string; // Optional tags?: string[]; // Optional video?: string; // Optional isShow?: boolean; // Optional, default true layout?: 'featured' | 'grid'; // featured is full width, grid is two columns index?: number; target?: string; // Optional, default "_blank" } const { name, description = '', url, image, tags = [], video, isShow = true, layout = 'grid', index = 0, target = "_blank" } = Astro.props; // Generate stable id for each video for precise playback control const videoId = `workcard-video-${index}-${name.replace(/[^a-z0-9]+/gi, '-').toLowerCase()}`; ---
{video ? ( ) : ( {name} )}

{name}

{tags && tags.length > 0 && (
{tags.map((tag) => ( {tag} ))}
)} {description && (

{description}

)}
View {name}
{video && ( )}
--- const { logo, dates, role, company, description } = Astro.props; ---
{company}

{dates}

{role}

{company}

{description}

--- import AnimatedText from "@/components/ui/AnimatedText.astro"; interface Props { title?: string ; description?: string ; tags?: string[] ; className?: string ; } const { title, description, tags = [], className = "" } = Astro.props; ---

{title && }

{description && (
)} {tags && tags.length > 0 && (
{tags.map((tag) => ( {tag} ))}
)}
--- interface Props { className?: string; // Custom CSS classes color?: string; // Color, defaults to neutral width?: "full" | "3/4" | "1/2" | "1/4"; // Width options spacing?: "sm" | "md" | "lg" | "xl"; // Spacing options thickness?: "thin" | "normal" | "thick"; // Thickness options } // Define default values const { className = "", color = "border-primary/25 dark:border-primary-light/25", width = "full", spacing = "md", thickness = "thin" } = Astro.props; // Width mapping const widthClasses = { "full": "w-full", "3/4": "w-3/4", "1/2": "w-1/2", "1/4": "w-1/4" }; // Spacing mapping const spacingClasses = { "sm": "my-2", "md": "my-4", "lg": "my-8", "xl": "my-12" }; // Thickness mapping const thicknessClasses = { "thin": "border-[0.5px]", "normal": "border-[1px]", "thick": "border-[2px]" }; --- --- // Define properties accepted by the component interface Props { imageUrl: string; // Required title?: string; // Optional, default "Home" link?: string; // Optional, can be empty } // Set default values and receive incoming properties const { imageUrl, title = "Home", link = "" } = Astro.props; ---
{title && (

{title}

)}
--- import projects from "@/collections/featuredwork.json"; import WorkCard from "@/components/cards/WorkCard.astro"; import SectionHeader from "@/components/elements/SectionHeader.astro"; import Button from "@/components/ui/Button.astro"; interface Props { title?: string; description?: string; showAll?: boolean; limit?: number; // 限制显示的项目数量 } interface Project { name: string; description?: string; image: string; url: string; tags?: string[]; video?: string; isShow?: boolean; } const { title = "Featured Work", description = "A selection of projects I've worked on. From web applications to design systems, each project represents a unique challenge and learning experience.", showAll = false, limit } = Astro.props; // 获取项目列表 let allProjects = projects as Project[]; // 根据 limit 或 showAll 来控制显示数量 if (limit !== undefined) { // 如果指定了 limit,使用 limit allProjects = projects.slice(0, limit) as Project[]; } else if (!showAll) { // 如果没有指定 limit,且 showAll 为 false,默认显示 6 个 allProjects = projects.slice(0, 6) as Project[]; } // 如果 showAll 为 true 且没有 limit,显示所有项目 // 前三个为特色项目(全宽) const featuredProjects = allProjects.slice(0, 3); // 其余为网格项目(两栏) const gridProjects = allProjects.slice(3); // 判断是否应该显示 "View All" 按钮 const shouldShowViewAll = limit !== undefined ? limit < projects.length : !showAll && projects.length> 6; ---
{featuredProjects.length > 0 && (
{featuredProjects .filter(project => project.isShow !== false) .map((project, index) => ( ))}
)} {gridProjects.length > 0 && (
{gridProjects .filter(project => project.isShow !== false) .map((project, index) => ( ))}
)} {shouldShowViewAll && (
)}
--- import Logo from "@/components/ui/Logo.astro"; import ToTop from "@/components/widgets/ToTop.astro"; import { siteConfig, socialLinks } from "@/config/site.js"; ---

© {new Date().getFullYear()} {siteConfig.author || " "}

{socialLinks.map((social) => ( {social.name} ))}
--- import works from "@/collections/works.json"; import WorkCard from "@/components/cards/WorkCard.astro"; interface Props { limit?: number; // 限制显示的项目数量 } interface Work { name: string; description?: string; image: string; url: string; tags?: string[]; video?: string; isShow?: boolean; } const { limit } = Astro.props; // 获取项目列表 let allWorks = works as Work[]; // 如果指定了 limit,使用 limit if (limit !== undefined) { allWorks = works.slice(0, limit) as Work[]; } ---
{allWorks .filter(work => work.isShow !== false) .map((work, index) => ( ))}
--- interface Props { class?: string; content: string | Promise; duration?: number; delay?: number; stagger?: number; } let { content, duration = 0.6, delay = 0, stagger = 0.1, ...rest } = Astro.props; let processedContent = ""; if (content) { // @ts-expect-error const words = content.split(" "); // Split text into words processedContent = words .map( (word: string) => ` ${word}`, ) .join(" "); } ---
--- import { siteConfig } from "@/config/site.js"; interface Props { title?: string; description?: string; keywords?: string; url?: string; image?: string; twitterHandle?: string; } const { title = siteConfig.meta.title, description = siteConfig.meta.description, keywords = siteConfig.meta.keywords, url = siteConfig.url, image = siteConfig.meta.image, twitterHandle = siteConfig.social.twitterName, } = Astro.props; // 构建完整的页面标题 const metaTitle = title === siteConfig.meta.title ? title : `${title} | ${siteConfig.meta.title}`; const metaDescription = description || siteConfig.meta.description; const ogImage = image || siteConfig.meta.image; const canonicalUrl = url || siteConfig.url; --- {metaTitle} --- /** * OptimizedImage component is a modified version of Astro's built-in Image component. * It allows to optimize images inside public, src/assets and content folders. * To optimize remote images, follow: https://docs.astro.build/en/guides/images/#authorizing-remote-images */ import { Image } from "astro:assets"; interface InlineSvgProps { src: string; inlineSvg: true; alt?: string; width?: string; height?: string; class?: string; style?: any; [key: string]: any; } interface ImageProps { src: string; alt?: string; width?: number; height?: number; loading?: "eager" | "lazy" | null | undefined; decoding?: "async" | "auto" | "sync" | null | undefined; formats?: string[] | "auto" | "avif" | "jpeg" | "png" | "svg" | "webp"; class?: string; style?: any; [key: string]: any; } type Props = InlineSvgProps | ImageProps; // Destructuring Astro.props to get the component's props let { src, alt, inlineSvg, width, height, loading, decoding, formats, style, ...rest } = Astro.props; const isRemoteImage = src.startsWith("http://") || src.startsWith("https://"); let image = src as any; let SVG = "" as any; if (!isRemoteImage) { // Regular Images const images = import.meta.glob([ "/src/assets/**/*.{jpeg,jpg,png,avif,tiff,gif,webp,svg,gif}", "/public/**/*.{jpeg,jpg,png,avif,tiff,gif,webp,svg,gif}", "/src/content/**/*.{jpeg,jpg,png,avif,tiff,gif,webp,svg,gif}", ]); // Get Raw Image For Inline SVG const imagesRaw = import.meta.glob( [ "/src/assets/**/*.svg", "/src/content/**/*.svg", "/public/**/*.svg", ], { query: "raw", import: "default", }, ); if (inlineSvg && src.includes(".svg")) { const key = Object.keys(images).find((k) => k.includes(src)); SVG = key ? await imagesRaw[key]() : null; if (SVG.length > 1) { SVG = SVG.split(" tag if (SVG.length > 1) { // Convert rest object to a string of attributes const attributes = Object.entries(rest) .map(([key, value]) => `${key}=" ${value}"`) .join(" "); // Add the attributes to the tag SVG[1] = ` data-icon=" true" ${attributes} ${SVG[1]}`; } // Join the array back into a string SVG = SVG.join(" k.includes(src)); image = key ? await images[key]() : null; image = image && image.default ? image.default : null; } --- { inlineSvg && SVG && src.includes(" .svg") ? ( ) : image ? ( // @ts-expect-error ) : null } --- const { currentPage, totalPages, collection } = Astro.props; // 生成页码数组 const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1); // 确定要显示的页码范围 const maxVisiblePages = 5; let startPage = Math.max(currentPage - Math.floor(maxVisiblePages / 2), 1); let endPage = Math.min(startPage + maxVisiblePages - 1, totalPages); if (endPage - startPage + 1 < maxVisiblePages) { startPage = Math.max(endPage - maxVisiblePages + 1, 1); } // 生成页面链接 const getPageLink = (pageNum: number): string => { if (pageNum === 1) { return `/${collection}/`; } return `/${collection}/page/${pageNum}`; }; --- --- interface Heading { depth: number; slug: string; text: string; } interface HeadingNode extends Heading { children: HeadingNode[]; } const { headings = [] } = Astro.props as { headings: Heading[] }; function buildTree(list: Heading[]): HeadingNode[] { const root: HeadingNode[] = []; const stack: HeadingNode[] = []; list.forEach(h => { const node: HeadingNode = { ...h, children: [] }; while (stack.length && stack[stack.length - 1].depth >= h.depth) { stack.pop(); } if (stack.length === 0) { root.push(node); } else { stack[stack.length - 1].children.push(node); } stack.push(node); }); return root; } const tree = buildTree(headings); ---
TOC
--- const GAID = import.meta.env.PUBLIC_GA4_ID; const UMAMIId = import.meta.env.PUBLIC_UMAMI_ID; // const GAID = ''; if (!GAID) { // console.error(" Google Analytics ID (PUBLIC_GA4_ID) is missing!"); } --- {GAID && ( <> )} {UMAMIId && ( )} --- import Meta from './Meta.astro'; import TopBg from " @/ components/ ui/ TopBg.astro"; import Footer from " @/ components/ sections/ Footer.astro"; import Header from " @/ components/ sections/ Header.astro"; import TrackGa from " @/ components/ widgets/ TrackGa.astro"; interface Props { title?: string | undefined; description?: string | undefined; keywords?: string | undefined; } const { title, description, keywords } = Astro.props; import " ../ styles/ global.css"; import " aos/ dist/ aos.css"; import " ../ styles/ aos-custom.css"; import '../styles/article.css'; import '../styles/article-enhancements.css'; ---