Files
NebulaShell/website/blog/skills/index.html
2026-04-10 20:00:25 +08:00

2892 lines
191 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html><html lang="zh-CN" class="bg-[var(--page-bg)] text-[14px] md:text-[16px]" data-overlayscrollbars-initialize data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"> <head><title>技能展示 - FutureOSS Docs</title><meta charset="UTF-8"><meta name="description" content="我的技术技能和专业知识"><meta name="author" content="FutureOSS"><meta property="og:site_name" content="FutureOSS Docs"><meta property="og:url" content="https://oss-runtime.dev/blog/skills/"><meta property="og:title" content="技能展示 - FutureOSS Docs"><meta property="og:description" content="我的技术技能和专业知识"><meta property="og:type" content="website"><meta name="twitter:card" content="summary_large_image"><meta property="twitter:url" content="https://oss-runtime.dev/blog/skills/"><meta name="twitter:title" content="技能展示 - FutureOSS Docs"><meta name="twitter:description" content="我的技术技能和专业知识"><meta name="viewport" content="width=device-width"><meta name="generator" content="Astro v5.13.2"><link rel="icon" href="/blog/favicon/favicon-light-32.png" sizes="32x32" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-light-128.png" sizes="128x128" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-light-180.png" sizes="180x180" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-light-192.png" sizes="192x192" media="(prefers-color-scheme: light)"><link rel="icon" href="/blog/favicon/favicon-dark-32.png" sizes="32x32" media="(prefers-color-scheme: dark)"><link rel="icon" href="/blog/favicon/favicon-dark-128.png" sizes="128x128" media="(prefers-color-scheme: dark)"><link rel="icon" href="/blog/favicon/favicon-dark-180.png" sizes="180x180" media="(prefers-color-scheme: dark)"><link rel="icon" href="/blog/favicon/favicon-dark-192.png" sizes="192x192" media="(prefers-color-scheme: dark)"><!-- Set the theme before the page is rendered to avoid a flash --><script>(function(){const DEFAULT_THEME = "auto";
const LIGHT_MODE = "light";
const DARK_MODE = "dark";
const AUTO_MODE = "auto";
const BANNER_HEIGHT_EXTEND = 30;
const PAGE_WIDTH = 90;
const configHue = 210;
// Load the theme from local storage
const theme = localStorage.getItem('theme') || DEFAULT_THEME;
switch (theme) {
case LIGHT_MODE:
document.documentElement.classList.remove('dark');
break
case DARK_MODE:
document.documentElement.classList.add('dark');
break
case AUTO_MODE:
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
// Load the hue from local storage
const hue = localStorage.getItem('hue') || configHue;
document.documentElement.style.setProperty('--hue', hue);
// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
offset = offset - offset % 4;
document.documentElement.style.setProperty('--banner-height-extend', `${offset}px`);
})();</script><!-- defines global css variables. This will be applied to <html> <body> and some other elements idk why --><link rel="alternate" type="application/rss+xml" title="FutureOSS" href="https://oss-runtime.dev/rss.xml"><link rel="stylesheet" href="/blog/_astro/Layout.DSulWsr7.css">
<link rel="stylesheet" href="/blog/_astro/Layout.CKPtf-VR.css">
<link rel="stylesheet" href="/blog/_astro/_page_.BQ6XwRvy.css">
<style>.line-clamp-2[data-astro-cid-xahix5fp]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
</style>
<link rel="stylesheet" href="/blog/_astro/_page_.QUsIdifj.css">
<link rel="stylesheet" href="/blog/_astro/_page_.D6v8Siar.css">
<link rel="stylesheet" href="/blog/_astro/about.DhJwTaWu.css">
<style>.card-base[data-astro-cid-fepk5tcl]{background:var(--card-bg);border:1px solid var(--line-divider)}
.card-base[data-astro-cid-pyfuvgrx]{background:var(--card-bg);border:1px solid var(--line-divider);transition:all .3s ease}.moments-header[data-astro-cid-pyfuvgrx]{padding:1rem;border-radius:8px;background:linear-gradient(135deg,var(--primary) 0%,var(--primary-dark, var(--primary)) 100%);color:#fff;position:relative;overflow:hidden}.header-content[data-astro-cid-pyfuvgrx]{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}.moments-title[data-astro-cid-pyfuvgrx]{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.1)}.moments-subtitle[data-astro-cid-pyfuvgrx]{color:#ffffffe6}.stat-number[data-astro-cid-pyfuvgrx]{color:#fff}.stat-label[data-astro-cid-pyfuvgrx]{color:#fffc}.image-item[data-astro-cid-pyfuvgrx] img[data-astro-cid-pyfuvgrx]{transition:transform .3s ease}.image-item[data-astro-cid-pyfuvgrx]:hover img[data-astro-cid-pyfuvgrx]{transform:scale(1.05)}.action-btn[data-astro-cid-pyfuvgrx]{background:none;border:none;cursor:pointer;color:var(--text-muted)}.action-btn[data-astro-cid-pyfuvgrx]:hover{color:var(--primary)}@media(max-width:640px){.moments-header[data-astro-cid-pyfuvgrx]{padding:.75rem}.header-content[data-astro-cid-pyfuvgrx]{flex-direction:column;text-align:center;gap:.75rem}.moment-images[data-astro-cid-pyfuvgrx]{grid-template-columns:repeat(2,1fr)}.moment-footer[data-astro-cid-pyfuvgrx]{flex-direction:column;align-items:flex-start;gap:.5rem}}@media(min-width:641px)and (max-width:900px){.moments-header[data-astro-cid-pyfuvgrx]{padding:1.25rem}.header-content[data-astro-cid-pyfuvgrx]{display:flex;justify-content:space-between;align-items:center}.moment-item[data-astro-cid-pyfuvgrx]{padding:1.5rem}.moment-images[data-astro-cid-pyfuvgrx]{grid-template-columns:repeat(3,1fr);gap:.75rem;max-width:500px}.moment-text[data-astro-cid-pyfuvgrx]{font-size:1rem;line-height:1.7}.moment-footer[data-astro-cid-pyfuvgrx]{margin-top:1rem}}@media(min-width:901px){.moments-header[data-astro-cid-pyfuvgrx]{padding:1.5rem}.moment-item[data-astro-cid-pyfuvgrx]{padding:2rem}.moment-images[data-astro-cid-pyfuvgrx]{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));max-width:600px;gap:1rem}.moment-text[data-astro-cid-pyfuvgrx]{font-size:1.1rem;line-height:1.8}}@media(max-width:480px){.card-base[data-astro-cid-pyfuvgrx]{margin:0 -.5rem}.moment-item[data-astro-cid-pyfuvgrx]{border-radius:8px}.moments-header[data-astro-cid-pyfuvgrx]{border-radius:6px}}
.card-base[data-astro-cid-sahthylw]{border-radius:var(--radius-large);border-width:1px;border-color:var(--line-divider);background-color:var(--card-bg);--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.gallery-group[data-astro-cid-sahthylw].expanded{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.gallery-content[data-astro-cid-sahthylw]{max-height:0;overflow:hidden;transition:max-height .3s ease-out}.gallery-group[data-astro-cid-sahthylw].expanded .gallery-content[data-astro-cid-sahthylw]{max-height:2000px}.gallery-image[data-astro-cid-sahthylw]:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}@media(max-width:768px){.gallery-content[data-astro-cid-sahthylw] .grid[data-astro-cid-sahthylw]{grid-template-columns:repeat(2,1fr)}}@media(max-width:480px){.gallery-content[data-astro-cid-sahthylw] .grid[data-astro-cid-sahthylw]{grid-template-columns:repeat(1,1fr)}}
.line-clamp-1[data-astro-cid-aid3sr62]{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.line-clamp-2[data-astro-cid-aid3sr62]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.line-clamp-2[data-astro-cid-qlh7ngej]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
</style>
<link rel="stylesheet" href="/blog/_astro/_page_.C0I9pl0C.css"><script type="module" src="/blog/_astro/page.OjjRkIOC.js"></script></head> <body class=" min-h-screen enable-banner" data-overlayscrollbars-initialize data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"> <!-- 页面顶部渐变高光效果 - 只在full和semifull模式下显示 --> <div class="top-gradient-highlight" data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"></div> <div id="config-carrier" data-hue="210"></div> <!-- Iconify图标库加载器 --><script>(function(){const preloadIcons = [];
const timeout = 10000;
const retryCount = 3;
// 全局图标加载逻辑
(function() {
'use strict';
// 避免重复加载
if (window.__iconifyLoaderInitialized) {
return;
}
window.__iconifyLoaderInitialized = true;
// 图标加载器类
class IconifyLoader {
constructor() {
this.isLoaded = false;
this.isLoading = false;
this.loadPromise = null;
this.observers = new Set();
this.preloadQueue = new Set();
}
async load(options = {}) {
const { timeout: loadTimeout = timeout, retryCount: maxRetries = retryCount } = options;
if (this.isLoaded) {
return Promise.resolve();
}
if (this.isLoading && this.loadPromise) {
return this.loadPromise;
}
this.isLoading = true;
this.loadPromise = this.loadWithRetry(loadTimeout, maxRetries);
try {
await this.loadPromise;
this.isLoaded = true;
this.notifyObservers();
await this.processPreloadQueue();
} catch (error) {
console.error('Failed to load Iconify:', error);
throw error;
} finally {
this.isLoading = false;
}
}
async loadWithRetry(timeout, retryCount) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
await this.loadScript(timeout);
return;
} catch (error) {
console.warn(`Iconify load attempt ${attempt} failed:`, error);
if (attempt === retryCount) {
throw new Error(`Failed to load Iconify after ${retryCount} attempts`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
loadScript(timeout) {
return new Promise((resolve, reject) => {
// 检查是否已经存在
if (this.isIconifyReady()) {
resolve();
return;
}
const existingScript = document.querySelector('script[src*="iconify-icon"]');
if (existingScript) {
this.waitForIconifyReady().then(resolve).catch(reject);
return;
}
const script = document.createElement('script');
script.src = 'https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js';
script.async = true;
script.crossOrigin = 'anonymous';
const timeoutId = setTimeout(() => {
script.remove();
reject(new Error('Script load timeout'));
}, timeout);
script.onload = () => {
clearTimeout(timeoutId);
this.waitForIconifyReady().then(resolve).catch(reject);
};
script.onerror = () => {
clearTimeout(timeoutId);
script.remove();
reject(new Error('Script load error'));
};
document.head.appendChild(script);
});
}
waitForIconifyReady(maxWait = 5000) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const checkReady = () => {
if (this.isIconifyReady()) {
resolve();
return;
}
if (Date.now() - startTime > maxWait) {
reject(new Error('Iconify initialization timeout'));
return;
}
setTimeout(checkReady, 50);
};
checkReady();
});
}
isIconifyReady() {
return typeof window !== 'undefined' &&
'customElements' in window &&
customElements.get('iconify-icon') !== undefined;
}
onLoad(callback) {
if (this.isLoaded) {
callback();
} else {
this.observers.add(callback);
}
}
notifyObservers() {
this.observers.forEach(callback => {
try {
callback();
} catch (error) {
console.error('Error in icon load observer:', error);
}
});
this.observers.clear();
}
addToPreloadQueue(icons) {
if (Array.isArray(icons)) {
icons.forEach(icon => this.preloadQueue.add(icon));
} else {
this.preloadQueue.add(icons);
}
if (this.isLoaded) {
this.processPreloadQueue();
}
}
async processPreloadQueue() {
if (this.preloadQueue.size === 0) return;
const iconsToLoad = Array.from(this.preloadQueue);
this.preloadQueue.clear();
await this.preloadIcons(iconsToLoad);
}
async preloadIcons(icons) {
if (!this.isLoaded || icons.length === 0) return;
return new Promise((resolve) => {
let loadedCount = 0;
const totalIcons = icons.length;
const tempElements = [];
const cleanup = () => {
tempElements.forEach(el => {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
});
};
const checkComplete = () => {
loadedCount++;
if (loadedCount >= totalIcons) {
cleanup();
resolve();
}
};
icons.forEach(icon => {
const tempIcon = document.createElement('iconify-icon');
tempIcon.setAttribute('icon', icon);
tempIcon.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;';
tempIcon.addEventListener('load', checkComplete);
tempIcon.addEventListener('error', checkComplete);
tempElements.push(tempIcon);
document.body.appendChild(tempIcon);
});
// 设置超时清理
setTimeout(() => {
cleanup();
resolve();
}, 3000);
});
}
}
// 创建全局实例
window.__iconifyLoader = new IconifyLoader();
// 立即开始加载
window.__iconifyLoader.load().catch(error => {
console.error('Failed to initialize Iconify:', error);
});
// 如果有预加载图标,添加到队列
if (preloadIcons && preloadIcons.length > 0) {
window.__iconifyLoader.addToPreloadQueue(preloadIcons);
}
// 导出便捷函数到全局
window.loadIconify = () => window.__iconifyLoader.load();
window.preloadIcons = (icons) => window.__iconifyLoader.addToPreloadQueue(icons);
window.onIconifyReady = (callback) => window.__iconifyLoader.onLoad(callback);
// 页面可见性变化时重新检查
document.addEventListener('visibilitychange', () => {
if (!document.hidden && !window.__iconifyLoader.isLoaded) {
window.__iconifyLoader.load().catch(console.error);
}
});
})();
})();</script> <!-- 为不支持JavaScript的情况提供备用方案 --><noscript><style>
iconify-icon {
display: none;
}
.icon-fallback {
display: inline-block;
}
</style></noscript> <div id="top-row" class="z-50 pointer-events-none relative transition-all duration-700 max-w-[var(--page-width)] px-0 md:px-4 mx-auto" data-astro-cid-haiuh7kc> <div id="navbar-wrapper" class="pointer-events-auto sticky top-0 transition-all" data-astro-cid-haiuh7kc> <div id="navbar" class="z-50 onload-animation" data-transparent-mode="semifull" data-is-home="false"> <div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation --> <div class="!overflow-visible max-w-[var(--page-width)] h-[4.5rem] mx-auto flex items-center justify-between px-4"> <a href="/blog/" class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95"> <div class="flex flex-row text-[var(--primary)] items-center text-md"> <svg width="1em" height="1em" class="text-[1.75rem] mb-1 mr-2" data-icon="material-symbols:home-outline-rounded"> <symbol id="ai:material-symbols:home-outline-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M6 19h3v-5q0-.425.288-.712T10 13h4q.425 0 .713.288T15 14v5h3v-9l-6-4.5L6 10zm-2 0v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-4q-.425 0-.712-.288T13 20v-5h-2v5q0 .425-.288.713T10 21H6q-.825 0-1.412-.587T4 19m8-6.75"/></symbol><use href="#ai:material-symbols:home-outline-rounded"></use> </svg> FutureOSS Docs </div> </a> <div class="hidden md:flex items-center space-x-1"> <div class="dropdown-container" data-dropdown data-astro-cid-qou34bit> <a aria-label="主页" href="/blog/" class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" class="text-[1.1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="material-symbols:home"> <symbol id="ai:material-symbols:home" viewBox="0 0 24 24"><path fill="currentColor" d="M4 21V9l8-6l8 6v12h-6v-7h-4v7z"/></symbol><use href="#ai:material-symbols:home"></use> </svg> 主页 </div> </a> </div> <script type="module">document.addEventListener("DOMContentLoaded",function(){const i=document.querySelectorAll("[data-dropdown]");i.forEach(t=>{const e=t.querySelector("[data-dropdown-trigger]"),o=t.querySelector("[data-dropdown-menu]"),n=t.querySelectorAll(".dropdown-item");!e||!o||(e.addEventListener("keydown",function(r){r.key==="Enter"||r.key===" "?(r.preventDefault(),d(t,e,o)):r.key==="ArrowDown"?(r.preventDefault(),u(t,e,o),n.length>0&&n[0].focus()):r.key==="Escape"&&a(t,e,o)}),n.forEach((r,l)=>{r.addEventListener("keydown",function(s){if(s.key==="ArrowDown"){s.preventDefault();const c=(l+1)%n.length;n[c].focus()}else if(s.key==="ArrowUp"){s.preventDefault();const c=(l-1+n.length)%n.length;n[c].focus()}else s.key==="Escape"&&(a(t,e,o),e.focus())})}))}),document.addEventListener("click",function(t){i.forEach(e=>{if(!e.contains(t.target)){const o=e.querySelector("[data-dropdown-trigger]"),n=e.querySelector("[data-dropdown-menu]");o&&n&&a(e,o,n)}})})});function d(i,t,e){t.getAttribute("aria-expanded")==="true"?a(i,t,e):u(i,t,e)}function u(i,t,e){t.setAttribute("aria-expanded","true"),e.classList.remove("opacity-0","invisible","pointer-events-none","translate-y-[-8px]"),e.classList.add("opacity-100","visible","pointer-events-auto","translate-y-0")}function a(i,t,e){t.setAttribute("aria-expanded","false"),e.classList.add("opacity-0","invisible","pointer-events-none","translate-y-[-8px]"),e.classList.remove("opacity-100","visible","pointer-events-auto","translate-y-0")}</script><div class="dropdown-container" data-dropdown data-astro-cid-qou34bit> <a aria-label="归档" href="/blog/archive/" class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" class="text-[1.1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="material-symbols:archive"> <symbol id="ai:material-symbols:archive" viewBox="0 0 24 24"><path fill="currentColor" d="m12 18l4-4l-1.4-1.4l-1.6 1.6V10h-2v4.2l-1.6-1.6L8 14zm-7 3q-.825 0-1.412-.587T3 19V6.525q0-.35.113-.675t.337-.6L4.7 3.725q.275-.35.687-.538T6.25 3h11.5q.45 0 .863.188t.687.537l1.25 1.525q.225.275.338.6t.112.675V19q0 .825-.587 1.413T19 21zm.4-15h13.2l-.85-1H6.25z"/></symbol><use href="#ai:material-symbols:archive"></use> </svg> 归档 </div> </a> </div> <div class="dropdown-container" data-dropdown data-astro-cid-qou34bit> <button class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95 dropdown-trigger" aria-expanded="false" aria-haspopup="true" data-dropdown-trigger data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" class="text-[1.1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="material-symbols:code"> <symbol id="ai:material-symbols:code" viewBox="0 0 24 24"><path fill="currentColor" d="m8 18l-6-6l6-6l1.425 1.425l-4.6 4.6L9.4 16.6zm8 0l-1.425-1.425l4.6-4.6L14.6 7.4L16 6l6 6z"/></symbol><use href="#ai:material-symbols:code"></use> </svg> 仓库 <svg width="1em" height="1em" class="text-[1.25rem] transition-transform duration-200 dropdown-arrow ml-1" data-astro-cid-qou34bit="true" data-icon="material-symbols:keyboard-arrow-down-rounded"> <symbol id="ai:material-symbols:keyboard-arrow-down-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M11.625 14.913q-.175-.063-.325-.213l-4.6-4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.9 3.9l3.9-3.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213t-.375.062t-.375-.062"/></symbol><use href="#ai:material-symbols:keyboard-arrow-down-rounded"></use> </svg> </div> </button>
<div class="dropdown-menu" data-dropdown-menu data-astro-cid-qou34bit> <div class="dropdown-content" data-astro-cid-qou34bit> <a href="https://gitee.com/starlight-apk/feature-oss" target="_blank" class="dropdown-item" aria-label="Gitee" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="mdi:git"> <use href="#ai:mdi:git"></use> </svg> <span data-astro-cid-qou34bit>Gitee</span> </div> <svg width="1em" height="1em" class="text-[0.75rem] text-black/25 dark:text-white/25" data-astro-cid-qou34bit="true" data-icon="fa6-solid:arrow-up-right-from-square"> <symbol id="ai:fa6-solid:arrow-up-right-from-square" viewBox="0 0 512 512"><path fill="currentColor" d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32zM80 32C35.8 32 0 67.8 0 112v320c0 44.2 35.8 80 80 80h320c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32v112c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16h112c17.7 0 32-14.3 32-32s-14.3-32-32-32z"/></symbol><use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a><a href="https://github.com/starlight-apk/feature-oss" target="_blank" class="dropdown-item" aria-label="GitHub" data-astro-cid-qou34bit> <div class="flex items-center" data-astro-cid-qou34bit> <svg width="0.97em" height="1em" viewBox="0 0 496 512" class="text-[1rem] mr-2" data-astro-cid-qou34bit="true" data-icon="fa6-brands:github"> <use href="#ai:fa6-brands:github"></use> </svg> <span data-astro-cid-qou34bit>GitHub</span> </div> <svg width="1em" height="1em" viewBox="0 0 512 512" class="text-[0.75rem] text-black/25 dark:text-white/25" data-astro-cid-qou34bit="true" data-icon="fa6-solid:arrow-up-right-from-square"> <use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a> </div> </div> </div> </div> <div class="flex"> <!--<SearchPanel client:load>--> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event("astro:only"));})();</script><script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script><astro-island uid="8ERU5" component-url="/blog/_astro/Search.C4siwRRu.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;Search&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <astro-island uid="ZgLYMD" component-url="/blog/_astro/MobileTOC.DcxBJmw1.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;MobileTOC&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <astro-island uid="pXLEo" component-url="/blog/_astro/TranslateButton.gpbH5i1G.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;TranslateButton&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <button aria-label="Display Settings" class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="display-settings-switch"> <svg width="1em" height="1em" class="text-[1.25rem]" data-icon="material-symbols:palette-outline"> <symbol id="ai:material-symbols:palette-outline" viewBox="0 0 24 24"><path fill="currentColor" d="M12 22q-2.05 0-3.875-.788t-3.187-2.15t-2.15-3.187T2 12q0-2.075.813-3.9t2.2-3.175T8.25 2.788T12.2 2q2 0 3.775.688t3.113 1.9t2.125 2.875T22 11.05q0 2.875-1.75 4.413T16 17h-1.85q-.225 0-.312.125t-.088.275q0 .3.375.863t.375 1.287q0 1.25-.687 1.85T12 22m-4.425-9.425Q8 12.15 8 11.5t-.425-1.075T6.5 10t-1.075.425T5 11.5t.425 1.075T6.5 13t1.075-.425m3-4Q11 8.15 11 7.5t-.425-1.075T9.5 6t-1.075.425T8 7.5t.425 1.075T9.5 9t1.075-.425m5 0Q16 8.15 16 7.5t-.425-1.075T14.5 6t-1.075.425T13 7.5t.425 1.075T14.5 9t1.075-.425m3 4Q19 12.15 19 11.5t-.425-1.075T17.5 10t-1.075.425T16 11.5t.425 1.075T17.5 13t1.075-.425M12 20q.225 0 .363-.125t.137-.325q0-.35-.375-.825T11.75 17.3q0-1.05.725-1.675T14.25 15H16q1.65 0 2.825-.962T20 11.05q0-3.025-2.312-5.038T12.2 4Q8.8 4 6.4 6.325T4 12q0 3.325 2.338 5.663T12 20"/></symbol><use href="#ai:material-symbols:palette-outline"></use> </svg> </button> <astro-island uid="Z1LIWpd" component-url="/blog/_astro/LightDarkSwitch.DQTkCfco.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;LightDarkSwitch&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> <button aria-label="Menu" name="Nav Menu" class="btn-plain scale-animation rounded-lg w-11 h-11 active:scale-90 md:!hidden" id="nav-menu-switch"> <svg width="1em" height="1em" class="text-[1.25rem]" data-icon="material-symbols:menu-rounded"> <symbol id="ai:material-symbols:menu-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M4 18q-.425 0-.712-.288T3 17t.288-.712T4 16h16q.425 0 .713.288T21 17t-.288.713T20 18zm0-5q-.425 0-.712-.288T3 12t.288-.712T4 11h16q.425 0 .713.288T21 12t-.288.713T20 13zm0-5q-.425 0-.712-.288T3 7t.288-.712T4 6h16q.425 0 .713.288T21 7t-.288.713T20 8z"/></symbol><use href="#ai:material-symbols:menu-rounded"></use> </svg> </button> </div> <div id="nav-menu-panel" class="float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2 max-h-[80vh] overflow-y-auto" data-astro-cid-h5lvqo6o> <div class="mobile-menu-item" data-astro-cid-h5lvqo6o> <!-- 普通链接项目 -->
<a href="/blog/" class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:home"> <use href="#ai:material-symbols:home"></use> </svg> 主页 </div> <svg width="1em" height="1em" class="transition text-[1.25rem] text-[var(--primary)]" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:chevron-right-rounded"> <symbol id="ai:material-symbols:chevron-right-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="M12.6 12L8.7 8.1q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7z"/></symbol><use href="#ai:material-symbols:chevron-right-rounded"></use> </svg> </a> </div><div class="mobile-menu-item" data-astro-cid-h5lvqo6o> <!-- 普通链接项目 -->
<a href="/blog/archive/" class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:archive"> <use href="#ai:material-symbols:archive"></use> </svg> 归档 </div> <svg width="1em" height="1em" viewBox="0 0 24 24" class="transition text-[1.25rem] text-[var(--primary)]" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:chevron-right-rounded"> <use href="#ai:material-symbols:chevron-right-rounded"></use> </svg> </a> </div><div class="mobile-menu-item" data-astro-cid-h5lvqo6o> <!-- 有子菜单的项目 -->
<div class="mobile-dropdown" data-mobile-dropdown data-astro-cid-h5lvqo6o> <button class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8 w-full text-left
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition" data-mobile-dropdown-trigger aria-expanded="false" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:code"> <use href="#ai:material-symbols:code"></use> </svg> 仓库 </div> <svg width="1em" height="1em" viewBox="0 0 24 24" class="transition text-[1.25rem] text-[var(--primary)] mobile-dropdown-arrow duration-200" data-astro-cid-h5lvqo6o="true" data-icon="material-symbols:keyboard-arrow-down-rounded"> <use href="#ai:material-symbols:keyboard-arrow-down-rounded"></use> </svg> </button> <div class="mobile-submenu" data-mobile-submenu data-astro-cid-h5lvqo6o> <a href="https://gitee.com/starlight-apk/feature-oss" class="group flex justify-between items-center py-2 pl-6 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition" target="_blank" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/60 dark:text-white/60 font-medium group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="1em" height="1em" viewBox="0 0 24 24" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="mdi:git"> <use href="#ai:mdi:git"></use> </svg> Gitee </div> <svg width="1em" height="1em" viewBox="0 0 512 512" class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1" data-astro-cid-h5lvqo6o="true" data-icon="fa6-solid:arrow-up-right-from-square"> <use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a><a href="https://github.com/starlight-apk/feature-oss" class="group flex justify-between items-center py-2 pl-6 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition" target="_blank" data-astro-cid-h5lvqo6o> <div class="flex items-center transition text-black/60 dark:text-white/60 font-medium group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" data-astro-cid-h5lvqo6o> <svg width="0.97em" height="1em" viewBox="0 0 496 512" class="text-[1.1rem] mr-2" data-astro-cid-h5lvqo6o="true" data-icon="fa6-brands:github"> <use href="#ai:fa6-brands:github"></use> </svg> GitHub </div> <svg width="1em" height="1em" viewBox="0 0 512 512" class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1" data-astro-cid-h5lvqo6o="true" data-icon="fa6-solid:arrow-up-right-from-square"> <use href="#ai:fa6-solid:arrow-up-right-from-square"></use> </svg> </a> </div> </div> </div> </div> <script type="module">document.addEventListener("DOMContentLoaded",function(){const r=document.querySelectorAll("[data-mobile-dropdown]");r.forEach(e=>{const t=e.querySelector("[data-mobile-dropdown-trigger]"),d=e.querySelector("[data-mobile-submenu]");!t||!d||t.addEventListener("click",function(o){o.preventDefault();const u=e.getAttribute("data-expanded")==="true";r.forEach(a=>{if(a!==e){a.setAttribute("data-expanded","false");const i=a.querySelector("[data-mobile-dropdown-trigger]");i&&i.setAttribute("aria-expanded","false")}});const n=!u;e.setAttribute("data-expanded",n.toString()),t.setAttribute("aria-expanded",n.toString())})})});</script> <astro-island uid="i6lC1" component-url="/blog/_astro/DisplaySettings.BkjQOgCB.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;DisplaySettings&quot;,&quot;value&quot;:&quot;svelte&quot;}"></astro-island> </div> </div> <script type="module">function c(){localStorage.theme==="dark"?(document.documentElement.classList.remove("dark"),localStorage.theme="light"):(document.documentElement.classList.add("dark"),localStorage.theme="dark")}function d(){let e=document.getElementById("scheme-switch");e&&(e.onclick=function(){c()});let l=document.getElementById("display-settings-switch");l&&(l.onclick=function(){let t=document.getElementById("display-setting");t&&t.classList.toggle("float-panel-closed")});let n=document.getElementById("nav-menu-switch");n&&(n.onclick=function(){let t=document.getElementById("nav-menu-panel");t&&t.classList.toggle("float-panel-closed")})}d();function o(){const e=document.getElementById("navbar");if(!e||e.getAttribute("data-transparent-mode")!=="semifull")return;if(!(e.getAttribute("data-is-home")==="true")){window.semifullScrollHandler&&(window.removeEventListener("scroll",window.semifullScrollHandler),window.semifullScrollHandler=null),e.classList.add("scrolled");return}e.classList.remove("scrolled");let t=!1;function i(){(window.pageYOffset||document.documentElement.scrollTop)>50?e.classList.add("scrolled"):e.classList.remove("scrolled"),t=!1}function s(){t||(requestAnimationFrame(i),t=!0)}window.semifullScrollHandler&&window.removeEventListener("scroll",window.semifullScrollHandler),window.semifullScrollHandler=s,window.addEventListener("scroll",s,{passive:!0}),i()}window.initSemifullScrollDetection=o;document.readyState==="loading"?document.addEventListener("DOMContentLoaded",o):o();</script> <script>(function(){const scriptUrl = "/blog/pagefind/pagefind.js";
async function loadPagefind() {
try {
const response = await fetch(scriptUrl, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`Pagefind script not found: ${response.status}`);
}
const pagefind = await import(scriptUrl);
await pagefind.options({
excerptLength: 20
});
window.pagefind = pagefind;
document.dispatchEvent(new CustomEvent('pagefindready'));
console.log('Pagefind loaded and initialized successfully, event dispatched.');
} catch (error) {
console.error('Failed to load Pagefind:', error);
window.pagefind = {
search: () => Promise.resolve({ results: [] }),
options: () => Promise.resolve(),
};
document.dispatchEvent(new CustomEvent('pagefindloaderror'));
console.log('Pagefind load error, event dispatched.');
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadPagefind);
} else {
loadPagefind();
}
})();</script> </div> </div> <div id="banner-wrapper" class="absolute z-10 w-full transition duration-700 overflow-hidden mobile-hide-banner" style="top: -30vh" data-astro-cid-haiuh7kc> <!-- Single image mode -->
<div class="relative h-full w-full" data-astro-cid-haiuh7kc> <!-- Mobile: use mobile-specific image with same logic as desktop --> <div class="block lg:hidden object-cover h-full w-full transition duration-700 opacity-100 overflow-hidden relative"> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <img src="/blog/assets/placeholder.jpg" alt="Mobile banner image of the blog" class="w-full h-full object-cover" style="object-position: center"> </div> <!-- Desktop: use desktop-specific image --> <div id="banner" class="hidden lg:block object-cover h-full transition duration-700 opacity-100 overflow-hidden relative"> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <img src="/blog/assets/placeholder.jpg" alt="Desktop banner image of the blog" class="w-full h-full object-cover" style="object-position: center"> </div> </div> <!-- Home page text overlay --> <div class="banner-text-overlay absolute inset-0 z-20 flex items-center justify-center hidden" data-astro-cid-haiuh7kc> <div class="w-4/5 lg:w-3/4 text-center mb-0" data-astro-cid-haiuh7kc> <div class="flex flex-col" data-astro-cid-haiuh7kc> <h1 class="banner-title text-6xl lg:text-8xl font-bold text-white drop-shadow-lg mb-2 lg:mb-4" data-astro-cid-haiuh7kc> FutureOSS </h1> <h2 class="banner-subtitle text-xl lg:text-3xl text-white/90 drop-shadow-md" data-astro-cid-haiuh7kc> <span class="typewriter " data-text="[&#34;一切皆为插件的开发者工具运行时框架&#34;,&#34;插件热插拔 · 依赖自动解析 · 熔断降级 · 事件驱动&#34;]" data-speed="100" data-delete-speed="50" data-pause-time="2000" data-astro-cid-4iv2rnoh></span> <script type="module">class n{element;texts;currentTextIndex=0;speed;deleteSpeed;pauseTime;currentIndex=0;isDeleting=!1;timeoutId=null;constructor(t){this.element=t;const e=t.dataset.text||"";try{const i=JSON.parse(e);this.texts=Array.isArray(i)?i:[e]}catch{this.texts=[e]}this.speed=parseInt(t.dataset.speed||"100"),this.deleteSpeed=parseInt(t.dataset.deleteSpeed||"50"),this.pauseTime=parseInt(t.dataset.pauseTime||"2000"),this.texts.length>1&&!this.isTypewriterEnabled()?this.showRandomText():this.start()}isTypewriterEnabled(){return this.element.dataset.speed!==void 0||this.element.dataset.deleteSpeed!==void 0||this.element.dataset.pauseTime!==void 0}showRandomText(){const t=Math.floor(Math.random()*this.texts.length);this.element.textContent=this.texts[t]}start(){this.texts.length!==0&&this.type()}getCurrentText(){return this.texts[this.currentTextIndex]||""}type(){const t=this.getCurrentText();this.isDeleting?this.currentIndex>0?(this.currentIndex--,this.element.textContent=t.substring(0,this.currentIndex),this.timeoutId=window.setTimeout(()=>this.type(),this.deleteSpeed)):(this.isDeleting=!1,this.currentTextIndex=(this.currentTextIndex+1)%this.texts.length,this.timeoutId=window.setTimeout(()=>this.type(),this.speed)):this.currentIndex<t.length?(this.currentIndex++,this.element.textContent=t.substring(0,this.currentIndex),this.timeoutId=window.setTimeout(()=>this.type(),this.speed)):this.texts.length>1&&(this.isDeleting=!0,this.timeoutId=window.setTimeout(()=>this.type(),this.pauseTime))}destroy(){this.timeoutId&&clearTimeout(this.timeoutId)}}document.addEventListener("DOMContentLoaded",()=>{document.querySelectorAll(".typewriter").forEach(t=>{new n(t)})});document.addEventListener("swup:contentReplaced",()=>{document.querySelectorAll(".typewriter").forEach(t=>{new n(t)})});</script> </h2> </div> </div> </div> <!-- Water waves effect --> <div class="waves absolute -bottom-[1px] h-[10vh] max-h-[9.375rem] min-h-[3.125rem] w-full md:h-[15vh]" id="header-waves" style="transform: translateZ(0); will-change: fill;" data-astro-cid-haiuh7kc> <svg class="waves" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 20 150 32" preserveAspectRatio="none" shape-rendering="auto" style="transform: translateZ(0); backface-visibility: hidden;" data-astro-cid-haiuh7kc> <defs data-astro-cid-haiuh7kc> <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v48h-352z" data-astro-cid-haiuh7kc></path> </defs> <g class="parallax" style="transform: translateZ(0);" data-astro-cid-haiuh7kc> <use xlink:href="#gentle-wave" x="48" y="0" class="opacity-25 fill-[var(--page-bg)]" style="animation-delay: -2s; animation-duration: 7s; will-change: transform;" data-astro-cid-haiuh7kc></use> <use xlink:href="#gentle-wave" x="48" y="3" class="opacity-50 fill-[var(--page-bg)]" style="animation-delay: -3s; animation-duration: 10s; will-change: transform;" data-astro-cid-haiuh7kc></use> <use xlink:href="#gentle-wave" x="48" y="5" class="opacity-75 fill-[var(--page-bg)]" style="animation-delay: -4s; animation-duration: 13s; will-change: transform;" data-astro-cid-haiuh7kc></use> <use xlink:href="#gentle-wave" x="48" y="7" class="fill-[var(--page-bg)]" style="animation-delay: -5s; animation-duration: 20s; will-change: transform;" data-astro-cid-haiuh7kc></use> </g> </svg> </div> </div> <div class="absolute w-full z-30 pointer-events-none mobile-main-no-banner " style="top: calc(35vh - 3.5rem)" data-astro-cid-haiuh7kc> <!-- The pointer-events-none here prevent blocking the click event of the TOC --> <div class="relative max-w-[var(--page-width)] mx-auto pointer-events-auto" data-astro-cid-haiuh7kc> <div id="main-grid" class="transition duration-700 w-full left-0 right-0 grid grid-cols-1 md:grid-cols-[17.5rem_1fr] lg:grid-cols-[17.5rem_1fr] grid-rows-[auto_1fr_auto] lg:grid-rows-[auto] mx-auto gap-4 px-0 md:px-4 " data-astro-cid-haiuh7kc> <!-- Banner image credit --> <div id="sidebar" class="mb-4 row-start-2 row-end-3 col-span-2 onload-animation block md:block md:row-start-1 md:row-end-2 md:max-w-[17.5rem] md:col-start-1 md:col-end-2 lg:block lg:row-start-1 lg:row-end-2 lg:max-w-[17.5rem] lg:col-start-1 lg:col-end-2 w-full" data-astro-cid-gfmxq3pg> <!-- 顶部固定组件区域 --> <div class="flex flex-col w-full gap-4 mb-4" data-astro-cid-gfmxq3pg> <div class="card-base p-3"> <a aria-label="Go to About Page" href="/blog/about/" class="group block relative mx-auto mt-1 lg:mx-0 lg:mt-0 mb-3
max-w-[12rem] lg:max-w-none overflow-hidden rounded-xl active:scale-95"> <div class="absolute transition pointer-events-none group-hover:bg-black/30 group-active:bg-black/50
w-full h-full z-50 flex items-center justify-center"> <svg width="1.13em" height="1em" class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl" data-icon="fa6-regular:address-card"> <symbol id="ai:fa6-regular:address-card" viewBox="0 0 576 512"><path fill="currentColor" d="M512 80c8.8 0 16 7.2 16 16v320c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V96c0-8.8 7.2-16 16-16zM64 32C28.7 32 0 60.7 0 96v320c0 35.3 28.7 64 64 64h448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64zm144 224a64 64 0 1 0 0-128a64 64 0 1 0 0 128m-32 32c-44.2 0-80 35.8-80 80c0 8.8 7.2 16 16 16h192c8.8 0 16-7.2 16-16c0-44.2-35.8-80-80-80zm200-144c-13.3 0-24 10.7-24 24s10.7 24 24 24h80c13.3 0 24-10.7 24-24s-10.7-24-24-24zm0 96c-13.3 0-24 10.7-24 24s10.7 24 24 24h80c13.3 0 24-10.7 24-24s-10.7-24-24-24z"/></symbol><use href="#ai:fa6-regular:address-card"></use> </svg> </div> <div class="mx-auto lg:w-full h-full lg:mt-0 overflow-hidden relative"> <div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div> <img src="/blog/_astro/avatar.DslDo0tY_Z2tanD.webp" alt="Profile Image of the Author" style="object-position: center" loading="lazy" decoding="async" fetchpriority="auto" width="640" height="640" class="w-full h-full object-cover"> </div> </a> <div class="px-2"> <div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">FutureOSS</div> <div class="h-1 w-5 bg-[var(--primary)] mx-auto rounded-full mb-2 transition"></div> <div class="text-center text-neutral-400 mb-2.5 transition">一切皆为插件的开发者工具运行时框架</div> <div class="flex gap-2 justify-center mb-1"> <a rel="me" aria-label="Gitee" href="https://gitee.com/starlight-apk/feature-oss" target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90"> <svg width="1em" height="1em" class="text-[1.5rem]" data-icon="mdi:git"> <symbol id="ai:mdi:git" viewBox="0 0 24 24"><path fill="currentColor" d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2a2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82"/></symbol><use href="#ai:mdi:git"></use> </svg> </a><a rel="me" aria-label="GitHub" href="https://github.com/starlight-apk/feature-oss" target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90"> <svg width="0.97em" height="1em" class="text-[1.5rem]" data-icon="fa6-brands:github"> <symbol id="ai:fa6-brands:github" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6c-3.3.3-5.6-1.3-5.6-3.6c0-2 2.3-3.6 5.2-3.6c3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9c2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9c.3 2 2.9 3.3 5.9 2.6c2.9-.7 4.9-2.6 4.6-4.6c-.3-1.9-3-3.2-5.9-2.9M244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2c12.8 2.3 17.3-5.6 17.3-12.1c0-6.2-.3-40.4-.3-61.4c0 0-70 15-84.7-29.8c0 0-11.4-29.1-27.8-36.6c0 0-22.9-15.7 1.6-15.4c0 0 24.9 2 38.6 25.8c21.9 38.6 58.6 27.5 72.9 20.9c2.3-16 8.8-27.1 16-33.7c-55.9-6.2-112.3-14.3-112.3-110.5c0-27.5 7.6-41.3 23.6-58.9c-2.6-6.5-11.1-33.3 2.6-67.9c20.9-6.5 69 27 69 27c20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27c13.7 34.7 5.2 61.4 2.6 67.9c16 17.7 25.8 31.5 25.8 58.9c0 96.5-58.9 104.2-114.8 110.5c9.2 7.9 17 22.9 17 46.4c0 33.7-.3 75.4-.3 83.6c0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252C496 113.3 383.5 8 244.8 8M97.2 352.9c-1.3 1-1 3.3.7 5.2c1.6 1.6 3.9 2.3 5.2 1c1.3-1 1-3.3-.7-5.2c-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9c1.6 1 3.6.7 4.3-.7c.7-1.3-.3-2.9-2.3-3.9c-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2c2.3 2.3 5.2 2.6 6.5 1c1.3-1.3.7-4.3-1.3-6.2c-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2c-1.4-2.3-4-3.3-5.6-2"/></symbol><use href="#ai:fa6-brands:github"></use> </svg> </a> </div> </div> </div><!-- 组件显示现在由sidebarLayoutConfig统一控制无需检查config.enable --><widget-layout data-id="announcement" data-is-collapsed="undefined" class="pb-4 card-base onload-animation" style="animation-delay: 50ms; " data-astro-cid-ucso7hve="true"> <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]" data-astro-cid-ucso7hve style="">欢迎来到 FutureOSS</div> <div id="announcement" class="collapse-wrapper px-4 overflow-hidden" data-astro-cid-ucso7hve style=""> <div> <!-- 公告栏内容 --> <div class="text-neutral-600 dark:text-neutral-300 leading-relaxed mb-3"> 一切皆为插件的开发者工具运行时框架,支持插件热插拔、依赖自动解析、熔断降级、事件驱动等企业级稳定性机制。 </div> <!-- 可选链接和关闭按钮 --> <div class="flex items-center justify-between gap-3"> <div> <a href="https://gitee.com/starlight-apk/feature-oss/wikis/Home" target="_blank" rel="noopener noreferrer" class="btn-regular rounded-lg px-3 py-1.5 text-sm font-medium active:scale-95 transition-transform"> 快速开始 </a> </div> <button class="btn-regular rounded-lg h-8 w-8 text-sm hover:bg-red-100 dark:hover:bg-red-900/30 transition-colors" onclick="closeAnnouncement()" aria-label="关闭"> <svg width="0.75em" height="1em" class="text-sm" data-icon="fa6-solid:xmark"> <symbol id="ai:fa6-solid:xmark" viewBox="0 0 384 512"><path fill="currentColor" d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7L86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256L41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3l105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256z"/></symbol><use href="#ai:fa6-solid:xmark"></use> </svg> </button> </div> </div> </div> </widget-layout> <script type="module">class d extends HTMLElement{constructor(){if(super(),this.dataset.isCollapsed!=="true")return;const e=this.dataset.id,t=this.querySelector(".expand-btn"),s=this.querySelector(`#${e}`);t.addEventListener("click",()=>{s.classList.remove("collapsed"),t.classList.add("hidden")})}}customElements.get("widget-layout")||customElements.define("widget-layout",d);</script> <script type="module">function n(){const e=document.querySelector('widget-layout[data-id="announcement"]');e&&(e.style.display="none",localStorage.setItem("announcementClosed","true"))}document.addEventListener("DOMContentLoaded",function(){const e=document.querySelector('widget-layout[data-id="announcement"]');e&&localStorage.getItem("announcementClosed")==="true"&&(e.style.display="none")});window.closeAnnouncement=n;</script> </div> <!-- 粘性组件区域 --> <div id="sidebar-sticky" class="transition-all duration-700 flex flex-col w-full gap-4 top-4 sticky top-4" data-astro-cid-gfmxq3pg> <widget-layout data-id="categories" data-is-collapsed="false" class="pb-4 card-base onload-animation" style="animation-delay: 150ms; --collapsedHeight: 7.5rem;" data-astro-cid-ucso7hve="true"> <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;">分类</div> <div id="categories" class="collapse-wrapper px-4 overflow-hidden" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;"> <a href="/blog/archive/?category=Examples" aria-label="View all posts in the Examples category"> <button class="
w-full
h-10
rounded-lg
bg-none
hover:bg-[var(--btn-plain-bg-hover)]
active:bg-[var(--btn-plain-bg-active)]
transition-all
pl-2
hover:pl-3
text-neutral-700
hover:text-[var(--primary)]
dark:text-neutral-300
dark:hover:text-[var(--primary)]
"> <div class="flex items-center justify-between relative mr-2"> <div class="overflow-hidden text-left whitespace-nowrap overflow-ellipsis "> Examples </div> <div class="transition px-2 h-7 ml-4 min-w-[2rem] rounded-lg text-sm font-bold
text-[var(--btn-content)] dark:text-[var(--deep-text)]
bg-[oklch(0.95_0.025_var(--hue))] dark:bg-[var(--primary)]
flex items-center justify-center"> 4 </div> </div> </button> </a> </div> </widget-layout> <widget-layout data-id="tags" data-is-collapsed="false" class="pb-4 card-base onload-animation" style="animation-delay: 200ms; --collapsedHeight: 7.5rem;" data-astro-cid-ucso7hve="true"> <div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;">标签</div> <div id="tags" class="collapse-wrapper px-4 overflow-hidden" data-astro-cid-ucso7hve style="--collapsedHeight: 7.5rem;"> <div class="flex gap-2 flex-wrap"> <a href="/blog/archive/?tag=Blogging" aria-label="View all posts with the Blogging tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Blogging </a><a href="/blog/archive/?tag=Example" aria-label="View all posts with the Example tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Example </a><a href="/blog/archive/?tag=Markdown" aria-label="View all posts with the Markdown tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Markdown </a><a href="/blog/archive/?tag=Mermaid" aria-label="View all posts with the Mermaid tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Mermaid </a><a href="/blog/archive/?tag=Video" aria-label="View all posts with the Video tag" class="btn-regular h-8 text-sm px-3 rounded-lg"> Video </a> </div> </div> </widget-layout> </div> </div> <!-- 响应式样式和JavaScript --> <script type="module" src="/blog/_astro/SideBar.astro_astro_type_script_index_0_lang.Fy0FqEdY.js"></script> <main id="swup-container" class="transition-swup-fade overflow-hidden w-full col-span-2 md:col-start-2 md:col-end-3 lg:col-start-2 lg:col-end-3" data-astro-cid-haiuh7kc> <div id="content-wrapper" class="onload-animation" data-astro-cid-haiuh7kc> <!-- the overflow-hidden here prevent long text break the layout--> <!-- make id different from windows.swup global property --> <!-- Iconify图标库加载器 --><script>(function(){const preloadIcons = ["logos:javascript","logos:typescript-icon","logos:react","logos:vue","logos:astro-icon","logos:tailwindcss-icon","logos:nodejs-icon","logos:python","simple-icons:express","logos:postgresql","logos:mongodb-icon","simple-icons:firebase","logos:git-icon","logos:visual-studio-code","logos:docker-icon","logos:figma"];
const timeout = 10000;
const retryCount = 3;
// 全局图标加载逻辑
(function() {
'use strict';
// 避免重复加载
if (window.__iconifyLoaderInitialized) {
return;
}
window.__iconifyLoaderInitialized = true;
// 图标加载器类
class IconifyLoader {
constructor() {
this.isLoaded = false;
this.isLoading = false;
this.loadPromise = null;
this.observers = new Set();
this.preloadQueue = new Set();
}
async load(options = {}) {
const { timeout: loadTimeout = timeout, retryCount: maxRetries = retryCount } = options;
if (this.isLoaded) {
return Promise.resolve();
}
if (this.isLoading && this.loadPromise) {
return this.loadPromise;
}
this.isLoading = true;
this.loadPromise = this.loadWithRetry(loadTimeout, maxRetries);
try {
await this.loadPromise;
this.isLoaded = true;
this.notifyObservers();
await this.processPreloadQueue();
} catch (error) {
console.error('Failed to load Iconify:', error);
throw error;
} finally {
this.isLoading = false;
}
}
async loadWithRetry(timeout, retryCount) {
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
await this.loadScript(timeout);
return;
} catch (error) {
console.warn(`Iconify load attempt ${attempt} failed:`, error);
if (attempt === retryCount) {
throw new Error(`Failed to load Iconify after ${retryCount} attempts`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
loadScript(timeout) {
return new Promise((resolve, reject) => {
// 检查是否已经存在
if (this.isIconifyReady()) {
resolve();
return;
}
const existingScript = document.querySelector('script[src*="iconify-icon"]');
if (existingScript) {
this.waitForIconifyReady().then(resolve).catch(reject);
return;
}
const script = document.createElement('script');
script.src = 'https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js';
script.async = true;
script.crossOrigin = 'anonymous';
const timeoutId = setTimeout(() => {
script.remove();
reject(new Error('Script load timeout'));
}, timeout);
script.onload = () => {
clearTimeout(timeoutId);
this.waitForIconifyReady().then(resolve).catch(reject);
};
script.onerror = () => {
clearTimeout(timeoutId);
script.remove();
reject(new Error('Script load error'));
};
document.head.appendChild(script);
});
}
waitForIconifyReady(maxWait = 5000) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const checkReady = () => {
if (this.isIconifyReady()) {
resolve();
return;
}
if (Date.now() - startTime > maxWait) {
reject(new Error('Iconify initialization timeout'));
return;
}
setTimeout(checkReady, 50);
};
checkReady();
});
}
isIconifyReady() {
return typeof window !== 'undefined' &&
'customElements' in window &&
customElements.get('iconify-icon') !== undefined;
}
onLoad(callback) {
if (this.isLoaded) {
callback();
} else {
this.observers.add(callback);
}
}
notifyObservers() {
this.observers.forEach(callback => {
try {
callback();
} catch (error) {
console.error('Error in icon load observer:', error);
}
});
this.observers.clear();
}
addToPreloadQueue(icons) {
if (Array.isArray(icons)) {
icons.forEach(icon => this.preloadQueue.add(icon));
} else {
this.preloadQueue.add(icons);
}
if (this.isLoaded) {
this.processPreloadQueue();
}
}
async processPreloadQueue() {
if (this.preloadQueue.size === 0) return;
const iconsToLoad = Array.from(this.preloadQueue);
this.preloadQueue.clear();
await this.preloadIcons(iconsToLoad);
}
async preloadIcons(icons) {
if (!this.isLoaded || icons.length === 0) return;
return new Promise((resolve) => {
let loadedCount = 0;
const totalIcons = icons.length;
const tempElements = [];
const cleanup = () => {
tempElements.forEach(el => {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
});
};
const checkComplete = () => {
loadedCount++;
if (loadedCount >= totalIcons) {
cleanup();
resolve();
}
};
icons.forEach(icon => {
const tempIcon = document.createElement('iconify-icon');
tempIcon.setAttribute('icon', icon);
tempIcon.style.cssText = 'position:absolute;top:-9999px;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;';
tempIcon.addEventListener('load', checkComplete);
tempIcon.addEventListener('error', checkComplete);
tempElements.push(tempIcon);
document.body.appendChild(tempIcon);
});
// 设置超时清理
setTimeout(() => {
cleanup();
resolve();
}, 3000);
});
}
}
// 创建全局实例
window.__iconifyLoader = new IconifyLoader();
// 立即开始加载
window.__iconifyLoader.load().catch(error => {
console.error('Failed to initialize Iconify:', error);
});
// 如果有预加载图标,添加到队列
if (preloadIcons && preloadIcons.length > 0) {
window.__iconifyLoader.addToPreloadQueue(preloadIcons);
}
// 导出便捷函数到全局
window.loadIconify = () => window.__iconifyLoader.load();
window.preloadIcons = (icons) => window.__iconifyLoader.addToPreloadQueue(icons);
window.onIconifyReady = (callback) => window.__iconifyLoader.onLoad(callback);
// 页面可见性变化时重新检查
document.addEventListener('visibilitychange', () => {
if (!document.hidden && !window.__iconifyLoader.isLoaded) {
window.__iconifyLoader.load().catch(console.error);
}
});
})();
})();</script> <!-- 为不支持JavaScript的情况提供备用方案 --><noscript><style>
iconify-icon {
display: none;
}
.icon-fallback {
display: inline-block;
}
</style></noscript> <div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32" data-astro-cid-xahix5fp> <div class="card-base z-10 px-9 py-6 relative w-full" data-astro-cid-xahix5fp> <!-- 页面标题 --> <div class="flex flex-col items-start justify-center mb-8" data-astro-cid-xahix5fp> <h1 class="text-4xl font-bold text-black/90 dark:text-white/90 mb-2" data-astro-cid-xahix5fp> 技能展示 </h1> <p class="text-lg text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 我的技术技能和专业知识 </p> </div> <!-- 统计信息 --> <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8" data-astro-cid-xahix5fp> <div class="bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/20 dark:to-blue-800/20 rounded-lg p-4" data-astro-cid-xahix5fp> <div class="text-2xl font-bold text-blue-600 dark:text-blue-400" data-astro-cid-xahix5fp>16</div> <div class="text-sm text-blue-600/70 dark:text-blue-400/70" data-astro-cid-xahix5fp>总技能数</div> </div> <div class="bg-gradient-to-br from-red-50 to-red-100 dark:from-red-900/20 dark:to-red-800/20 rounded-lg p-4" data-astro-cid-xahix5fp> <div class="text-2xl font-bold text-red-600 dark:text-red-400" data-astro-cid-xahix5fp>1</div> <div class="text-sm text-red-600/70 dark:text-red-400/70" data-astro-cid-xahix5fp>专家级</div> </div> <div class="bg-gradient-to-br from-orange-50 to-orange-100 dark:from-orange-900/20 dark:to-orange-800/20 rounded-lg p-4" data-astro-cid-xahix5fp> <div class="text-2xl font-bold text-orange-600 dark:text-orange-400" data-astro-cid-xahix5fp>6</div> <div class="text-sm text-orange-600/70 dark:text-orange-400/70" data-astro-cid-xahix5fp>高级</div> </div> <div class="bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/20 rounded-lg p-4" data-astro-cid-xahix5fp> <div class="text-2xl font-bold text-purple-600 dark:text-purple-400" data-astro-cid-xahix5fp> 32 </div> <div class="text-sm text-purple-600/70 dark:text-purple-400/70" data-astro-cid-xahix5fp>经验</div> </div> </div> <!-- 专业技能 --> <div class="mb-8" data-astro-cid-xahix5fp> <h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-4" data-astro-cid-xahix5fp> 专业技能 </h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-6" data-astro-cid-xahix5fp> <div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #F7DF1E20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #F7DF1E;" data-icon-container="icon-rp47f6xuo" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> J </span> <!-- 实际图标 --> <iconify-icon icon="logos:javascript" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-rp47f6xuo";
const icon = "logos:javascript";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> JavaScript </h3> <span class="px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 现代JavaScript开发包括ES6+语法、异步编程、模块化开发等。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 3年 6个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 80%; background-color: #F7DF1E" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #3178C620" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #3178C6;" data-icon-container="icon-gl6tpmcyc" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> T </span> <!-- 实际图标 --> <iconify-icon icon="logos:typescript-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-gl6tpmcyc";
const icon = "logos:typescript-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> TypeScript </h3> <span class="px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 类型安全的JavaScript超集提升代码质量和开发效率。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 8个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 80%; background-color: #3178C6" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #61DAFB20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #61DAFB;" data-icon-container="icon-ht207m69q" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> R </span> <!-- 实际图标 --> <iconify-icon icon="logos:react" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-ht207m69q";
const icon = "logos:react";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> React </h3> <span class="px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 构建用户界面的JavaScript库包括Hooks、Context、状态管理等。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 10个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 80%; background-color: #61DAFB" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #FF5D0120" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #FF5D01;" data-icon-container="icon-89s6x02f8" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> A </span> <!-- 实际图标 --> <iconify-icon icon="logos:astro-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-89s6x02f8";
const icon = "logos:astro-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> Astro </h3> <span class="px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 现代静态站点生成器,支持多框架集成和优秀的性能。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 2个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 80%; background-color: #FF5D01" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #06B6D420" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #06B6D4;" data-icon-container="icon-qg474oe3c" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> T </span> <!-- 实际图标 --> <iconify-icon icon="logos:tailwindcss-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-qg474oe3c";
const icon = "logos:tailwindcss-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> Tailwind CSS </h3> <span class="px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 实用优先的CSS框架快速构建现代化用户界面。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 80%; background-color: #06B6D4" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #F0503220" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #F05032;" data-icon-container="icon-7q67jt61m" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> G </span> <!-- 实际图标 --> <iconify-icon icon="logos:git-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-7q67jt61m";
const icon = "logos:git-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> Git </h3> <span class="px-2 py-1 text-xs rounded-full bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 分布式版本控制系统,代码管理和团队协作必备工具。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 3年 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 80%; background-color: #F05032" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-6 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-4" data-astro-cid-xahix5fp> <div class="w-12 h-12 rounded-lg flex items-center justify-center" style="background-color: #007ACC20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-2xl" style="color: #007ACC;" data-icon-container="icon-k8whntapy" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> V </span> <!-- 实际图标 --> <iconify-icon icon="logos:visual-studio-code" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-k8whntapy";
const icon = "logos:visual-studio-code";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-2" data-astro-cid-xahix5fp> <h3 class="text-xl font-semibold text-black/90 dark:text-white/90" data-astro-cid-xahix5fp> VS Code </h3> <span class="px-2 py-1 text-xs rounded-full bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" data-astro-cid-xahix5fp> 专家级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-3 text-sm" data-astro-cid-xahix5fp> 轻量级但功能强大的代码编辑器,丰富的插件生态。 </p> <div class="mb-3" data-astro-cid-xahix5fp> <div class="flex justify-between text-sm mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 3年 6个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-300" style="width: 100%; background-color: #007ACC" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div> </div> </div> <!-- 按分类展示技能 --> <div class="space-y-8" data-astro-cid-xahix5fp> <div data-astro-cid-xahix5fp> <h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-4" data-astro-cid-xahix5fp> 前端开发 <span class="text-lg font-normal text-black/60 dark:text-white/60 ml-2" data-astro-cid-xahix5fp>
(6)
</span> </h2> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" data-astro-cid-xahix5fp> <div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #F7DF1E20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #F7DF1E;" data-icon-container="icon-z5vgm6mvq" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> J </span> <!-- 实际图标 --> <iconify-icon icon="logos:javascript" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-z5vgm6mvq";
const icon = "logos:javascript";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> JavaScript </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 现代JavaScript开发包括ES6+语法、异步编程、模块化开发等。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 3年 6个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 80%; background-color: #F7DF1E" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 3 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #3178C620" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #3178C6;" data-icon-container="icon-9f8ibbe8h" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> T </span> <!-- 实际图标 --> <iconify-icon icon="logos:typescript-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-9f8ibbe8h";
const icon = "logos:typescript-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> TypeScript </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 类型安全的JavaScript超集提升代码质量和开发效率。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 8个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 80%; background-color: #3178C6" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 3 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #61DAFB20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #61DAFB;" data-icon-container="icon-813e9zm7j" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> R </span> <!-- 实际图标 --> <iconify-icon icon="logos:react" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-813e9zm7j";
const icon = "logos:react";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> React </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 构建用户界面的JavaScript库包括Hooks、Context、状态管理等。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 10个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 80%; background-color: #61DAFB" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 2 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #4FC08D20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #4FC08D;" data-icon-container="icon-akhqey6xd" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> V </span> <!-- 实际图标 --> <iconify-icon icon="logos:vue" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-akhqey6xd";
const icon = "logos:vue";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Vue.js </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 渐进式JavaScript框架易学易用适合快速开发。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 8个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #4FC08D" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 1 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #FF5D0120" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #FF5D01;" data-icon-container="icon-oky9j58ib" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> A </span> <!-- 实际图标 --> <iconify-icon icon="logos:astro-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-oky9j58ib";
const icon = "logos:astro-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Astro </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 现代静态站点生成器,支持多框架集成和优秀的性能。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 2个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 80%; background-color: #FF5D01" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 1 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #06B6D420" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #06B6D4;" data-icon-container="icon-lq4q5vit8" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> T </span> <!-- 实际图标 --> <iconify-icon icon="logos:tailwindcss-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-lq4q5vit8";
const icon = "logos:tailwindcss-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Tailwind CSS </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 实用优先的CSS框架快速构建现代化用户界面。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 80%; background-color: #06B6D4" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 2 </div> </div> </div> </div> </div> </div><div data-astro-cid-xahix5fp> <h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-4" data-astro-cid-xahix5fp> 后端开发 <span class="text-lg font-normal text-black/60 dark:text-white/60 ml-2" data-astro-cid-xahix5fp>
(3)
</span> </h2> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" data-astro-cid-xahix5fp> <div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #33993320" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #339933;" data-icon-container="icon-g9ty6kqdb" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> N </span> <!-- 实际图标 --> <iconify-icon icon="logos:nodejs-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-g9ty6kqdb";
const icon = "logos:nodejs-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Node.js </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 基于Chrome V8引擎的JavaScript运行时用于服务端开发。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 2年 3个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #339933" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 2 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #3776AB20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #3776AB;" data-icon-container="icon-y45rnouuh" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> P </span> <!-- 实际图标 --> <iconify-icon icon="logos:python" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-y45rnouuh";
const icon = "logos:python";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Python </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 通用编程语言适用于Web开发、数据分析、机器学习等。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 10个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #3776AB" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #00000020" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #000000;" data-icon-container="icon-ckrke0t9j" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> E </span> <!-- 实际图标 --> <iconify-icon icon="simple-icons:express" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-ckrke0t9j";
const icon = "simple-icons:express";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Express.js </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 快速、极简的Node.js Web应用框架。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 8个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #000000" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 1 </div> </div> </div> </div> </div> </div><div data-astro-cid-xahix5fp> <h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-4" data-astro-cid-xahix5fp> 数据库 <span class="text-lg font-normal text-black/60 dark:text-white/60 ml-2" data-astro-cid-xahix5fp>
(3)
</span> </h2> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" data-astro-cid-xahix5fp> <div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #33679120" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #336791;" data-icon-container="icon-vjrln3gpg" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> P </span> <!-- 实际图标 --> <iconify-icon icon="logos:postgresql" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-vjrln3gpg";
const icon = "logos:postgresql";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> PostgreSQL </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 强大的开源关系型数据库管理系统。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 5个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #336791" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 1 </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #47A24820" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #47A248;" data-icon-container="icon-4v2uftpyj" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> M </span> <!-- 实际图标 --> <iconify-icon icon="logos:mongodb-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-4v2uftpyj";
const icon = "logos:mongodb-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> MongoDB </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 面向文档的NoSQL数据库灵活的数据模型。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 2个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #47A248" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #FFCA2820" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #FFCA28;" data-icon-container="icon-9jofnz15q" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> F </span> <!-- 实际图标 --> <iconify-icon icon="simple-icons:firebase" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-9jofnz15q";
const icon = "simple-icons:firebase";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Firebase </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> Google的移动和Web应用开发平台提供实时数据库和认证服务。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 0年 10个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #FFCA28" data-astro-cid-xahix5fp></div> </div> </div> <div class="text-xs text-black/60 dark:text-white/60" data-astro-cid-xahix5fp> 相关项目: 1 </div> </div> </div> </div> </div> </div><div data-astro-cid-xahix5fp> <h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-4" data-astro-cid-xahix5fp> 开发工具 <span class="text-lg font-normal text-black/60 dark:text-white/60 ml-2" data-astro-cid-xahix5fp>
(4)
</span> </h2> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" data-astro-cid-xahix5fp> <div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #F0503220" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #F05032;" data-icon-container="icon-ljy9mhppv" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> G </span> <!-- 实际图标 --> <iconify-icon icon="logos:git-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-ljy9mhppv";
const icon = "logos:git-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Git </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400" data-astro-cid-xahix5fp> 高级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 分布式版本控制系统,代码管理和团队协作必备工具。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 3年 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 80%; background-color: #F05032" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #007ACC20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #007ACC;" data-icon-container="icon-j9yr1y8ny" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> V </span> <!-- 实际图标 --> <iconify-icon icon="logos:visual-studio-code" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-j9yr1y8ny";
const icon = "logos:visual-studio-code";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> VS Code </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" data-astro-cid-xahix5fp> 专家级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 轻量级但功能强大的代码编辑器,丰富的插件生态。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 3年 6个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 100%; background-color: #007ACC" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #2496ED20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #2496ED;" data-icon-container="icon-z60urkko4" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> D </span> <!-- 实际图标 --> <iconify-icon icon="logos:docker-icon" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-z60urkko4";
const icon = "logos:docker-icon";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Docker </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 容器化平台,简化应用部署和环境管理。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #2496ED" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div><div class="bg-white dark:bg-gray-800 rounded-lg border border-black/10 dark:border-white/10 p-4 hover:shadow-lg transition-shadow duration-300" data-astro-cid-xahix5fp> <div class="flex items-start gap-3" data-astro-cid-xahix5fp> <div class="w-10 h-10 rounded-lg flex items-center justify-center shrink-0" style="background-color: #F24E1E20" data-astro-cid-xahix5fp> <span class="inline-flex items-center justify-center text-base text-lg" style="color: #F24E1E;" data-icon-container="icon-ykp1wuwjq" data-astro-cid-qpfihmil> <!-- 加载状态指示器 --> <span class="icon-loading animate-pulse opacity-50" data-loading-indicator data-astro-cid-qpfihmil> F </span> <!-- 实际图标 --> <iconify-icon icon="logos:figma" class="icon-content opacity-0 transition-opacity duration-200" data-icon-element="true" loading="eager" data-astro-cid-qpfihmil="true"></iconify-icon> </span> <script>(function(){const iconId = "icon-ykp1wuwjq";
const icon = "logos:figma";
// 图标加载和显示逻辑
(function() {
const container = document.querySelector(`[data-icon-container="${iconId}"]`);
if (!container) return;
const loadingIndicator = container.querySelector('[data-loading-indicator]');
const iconElement = container.querySelector('[data-icon-element]');
if (!loadingIndicator || !iconElement) return;
// 检查图标是否已经加载
function checkIconLoaded() {
// 检查iconify-icon元素是否已经渲染
const hasContent = iconElement.shadowRoot &&
iconElement.shadowRoot.children.length > 0;
if (hasContent) {
showIcon();
return true;
}
return false;
}
// 显示图标,隐藏加载指示器
function showIcon() {
loadingIndicator.style.display = 'none';
iconElement.classList.remove('opacity-0');
iconElement.classList.add('opacity-100');
}
// 显示加载指示器,隐藏图标
function showLoading() {
loadingIndicator.style.display = 'inline-flex';
iconElement.classList.remove('opacity-100');
iconElement.classList.add('opacity-0');
}
// 初始状态
showLoading();
// 监听图标加载事件
iconElement.addEventListener('load', () => {
showIcon();
});
// 监听图标加载错误
iconElement.addEventListener('error', () => {
// 保持显示fallback
console.warn(`Failed to load icon: ${icon}`);
});
// 使用MutationObserver监听shadow DOM变化
if (window.MutationObserver) {
const observer = new MutationObserver(() => {
if (checkIconLoaded()) {
observer.disconnect();
}
});
// 监听iconify-icon元素的变化
observer.observe(iconElement, {
childList: true,
subtree: true,
attributes: true
});
// 设置超时,避免无限等待
setTimeout(() => {
observer.disconnect();
if (!checkIconLoaded()) {
console.warn(`Icon load timeout: ${icon}`);
}
}, 5000);
}
// 立即检查一次(可能已经加载完成)
setTimeout(() => {
checkIconLoaded();
}, 100);
})();
})();</script> </div> <div class="flex-1 min-w-0" data-astro-cid-xahix5fp> <div class="flex items-center justify-between mb-1" data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/90 dark:text-white/90 truncate" data-astro-cid-xahix5fp> Figma </h3> <span class="px-2 py-1 text-xs rounded-full shrink-0 ml-2 bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" data-astro-cid-xahix5fp> 中级 </span> </div> <p class="text-black/60 dark:text-white/60 mb-2 text-sm line-clamp-2" data-astro-cid-xahix5fp> 协作式界面设计工具用于UI/UX设计和原型制作。 </p> <div class="mb-2" data-astro-cid-xahix5fp> <div class="flex justify-between text-xs mb-1" data-astro-cid-xahix5fp> <span class="text-black/60 dark:text-white/60" data-astro-cid-xahix5fp>经验</span> <span class="text-black/80 dark:text-white/80" data-astro-cid-xahix5fp> 1年 6个月 </span> </div> <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5" data-astro-cid-xahix5fp> <div class="h-1.5 rounded-full transition-all duration-300" style="width: 60%; background-color: #F24E1E" data-astro-cid-xahix5fp></div> </div> </div> </div> </div> </div> </div> </div> </div> <!-- 技能分布图表 --> <div class="mt-12 pt-8 border-t border-black/10 dark:border-white/10" data-astro-cid-xahix5fp> <h2 class="text-2xl font-bold text-black/90 dark:text-white/90 mb-6" data-astro-cid-xahix5fp> 技能分布 </h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-8" data-astro-cid-xahix5fp> <!-- 按等级分布 --> <div data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/80 dark:text-white/80 mb-4" data-astro-cid-xahix5fp> 按等级分布 </h3> <div class="space-y-3" data-astro-cid-xahix5fp> <div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70" data-astro-cid-xahix5fp> 初级 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-500 bg-green-500" style="width: 0%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 0 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70" data-astro-cid-xahix5fp> 中级 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-500 bg-yellow-500" style="width: 56%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 9 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70" data-astro-cid-xahix5fp> 高级 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-500 bg-orange-500" style="width: 38%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 6 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70" data-astro-cid-xahix5fp> 专家级 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full transition-all duration-500 bg-red-500" style="width: 6%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 1 </div> </div> </div> </div> <!-- 按分类分布 --> <div data-astro-cid-xahix5fp> <h3 class="text-lg font-semibold text-black/80 dark:text-white/80 mb-4" data-astro-cid-xahix5fp> 按分类分布 </h3> <div class="space-y-3" data-astro-cid-xahix5fp> <div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70 truncate" data-astro-cid-xahix5fp> 前端开发 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full bg-blue-500 transition-all duration-500" style="width: 38%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 6 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70 truncate" data-astro-cid-xahix5fp> 后端开发 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full bg-blue-500 transition-all duration-500" style="width: 19%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 3 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70 truncate" data-astro-cid-xahix5fp> 数据库 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full bg-blue-500 transition-all duration-500" style="width: 19%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 3 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70 truncate" data-astro-cid-xahix5fp> 开发工具 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full bg-blue-500 transition-all duration-500" style="width: 25%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 4 </div> </div><div class="flex items-center gap-3" data-astro-cid-xahix5fp> <div class="w-20 text-sm text-black/70 dark:text-white/70 truncate" data-astro-cid-xahix5fp> 其他技能 </div> <div class="flex-1 bg-gray-200 dark:bg-gray-700 rounded-full h-2" data-astro-cid-xahix5fp> <div class="h-2 rounded-full bg-blue-500 transition-all duration-500" style="width: 0%" data-astro-cid-xahix5fp></div> </div> <div class="w-12 text-sm text-black/70 dark:text-white/70 text-right" data-astro-cid-xahix5fp> 0 </div> </div> </div> </div> </div> </div> </div> </div> <div class="footer col-span-2 onload-animation hidden lg:block" data-astro-cid-haiuh7kc> <!--<div class="border-t border-[var(&#45;&#45;primary)] mx-16 border-dashed py-8 max-w-[var(&#45;&#45;page-width)] flex flex-col items-center justify-center px-6">--><div class="transition border-t border-black/10 dark:border-white/15 my-10 border-dashed mx-32"></div> <!--<div class="transition bg-[oklch(92%_0.01_var(&#45;&#45;hue))] dark:bg-black rounded-2xl py-8 mt-4 mb-8 flex flex-col items-center justify-center px-6">--> <div class="transition border-dashed border-[oklch(85%_0.01_var(--hue))] dark:border-white/15 rounded-2xl mb-12 flex flex-col items-center justify-center px-6"> <div class="transition text-50 text-sm text-center">
&copy; <span id="copyright-year">2026</span> FutureOSS. All Rights Reserved. /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/rss.xml">RSS</a> /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/sitemap-index.xml">Sitemap</a><br>
Powered by
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://astro.build">Astro</a> &
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">Mizuki</a>&nbsp; Version <a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">4.0<br> </a></div><a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki"></a></div> </div> </div> </main> <div class="footer col-span-2 onload-animation block lg:hidden" data-astro-cid-haiuh7kc> <!--<div class="border-t border-[var(&#45;&#45;primary)] mx-16 border-dashed py-8 max-w-[var(&#45;&#45;page-width)] flex flex-col items-center justify-center px-6">--><div class="transition border-t border-black/10 dark:border-white/15 my-10 border-dashed mx-32"></div> <!--<div class="transition bg-[oklch(92%_0.01_var(&#45;&#45;hue))] dark:bg-black rounded-2xl py-8 mt-4 mb-8 flex flex-col items-center justify-center px-6">--> <div class="transition border-dashed border-[oklch(85%_0.01_var(--hue))] dark:border-white/15 rounded-2xl mb-12 flex flex-col items-center justify-center px-6"> <div class="transition text-50 text-sm text-center">
&copy; <span id="copyright-year">2026</span> FutureOSS. All Rights Reserved. /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/rss.xml">RSS</a> /
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="/blog/sitemap-index.xml">Sitemap</a><br>
Powered by
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://astro.build">Astro</a> &
<a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">Mizuki</a>&nbsp; Version <a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki">4.0<br> </a></div><a class="transition link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/matsuzaka-yuki/mizuki"></a></div> </div> </div> <!-- There can't be a filter on parent element, or it will break `fixed` --><div class="back-to-top-wrapper hidden lg:block" data-astro-cid-eymb5ayk> <div id="back-to-top-btn" class="back-to-top-btn hide flex items-center rounded-2xl overflow-hidden transition" onclick="backToTop()" data-astro-cid-eymb5ayk> <button aria-label="Back to Top" class="btn-card h-[3.75rem] w-[3.75rem]" data-astro-cid-eymb5ayk> <svg width="1em" height="1em" class="mx-auto" data-astro-cid-eymb5ayk="true" data-icon="material-symbols:keyboard-arrow-up-rounded"> <symbol id="ai:material-symbols:keyboard-arrow-up-rounded" viewBox="0 0 24 24"><path fill="currentColor" d="m12 10.8l-3.9 3.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7l4.6-4.6q.3-.3.7-.3t.7.3l4.6 4.6q.275.275.275.7t-.275.7t-.7.275t-.7-.275z"/></symbol><use href="#ai:material-symbols:keyboard-arrow-up-rounded"></use> </svg> </button> </div> </div> <script>
function backToTop() {
// 直接使用原生滚动避免OverlayScrollbars冲突
window.scroll({ top: 0, behavior: 'smooth' });
}
</script> </div> </div> <div class="absolute w-full z-0 hidden xl:block" data-astro-cid-haiuh7kc> <div class="relative max-w-[var(--page-width)] mx-auto" data-astro-cid-haiuh7kc> <!-- TOC component --> <div id="toc-wrapper" class="hidden lg:block transition absolute top-0 w-[var(--toc-width)] items-center -right-[var(--toc-width)] toc-hide" data-astro-cid-haiuh7kc> <div id="toc-inner-wrapper" class="fixed top-14 w-[var(--toc-width)] h-[calc(100vh_-_20rem)] overflow-y-scroll overflow-x-hidden hide-scrollbar" data-astro-cid-haiuh7kc> <div id="toc" class="w-full h-full transition-swup-fade " data-astro-cid-haiuh7kc> <div class="h-8 w-full" data-astro-cid-haiuh7kc></div> <table-of-contents id="toc" class="group"> <!-- TOC内容将由JavaScript动态生成 --> </table-of-contents> <script type="module" src="/blog/_astro/TOC.astro_astro_type_script_index_0_lang.CGKGUlSz.js"></script> <div class="h-8 w-full" data-astro-cid-haiuh7kc></div> </div> </div> </div> <!-- #toc needs to exist for Swup to work normally --> </div> </div> <!-- Music Player --> <script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).load=e;window.dispatchEvent(new Event("astro:load"));})();</script><astro-island uid="Z1HtLzS" component-url="/blog/_astro/MusicPlayer.Ckr8jCXu.js" component-export="default" renderer-url="/blog/_astro/client.svelte.9vP7DkWT.js" props="{&quot;data-astro-cid-sckkx6r4&quot;:[0,true]}" ssr client="load" opts="{&quot;name&quot;:&quot;MusicPlayer&quot;,&quot;value&quot;:true}" await-children><!--[--><!--[-1--><!--]--><!--]--><!--astro:end--></astro-island> <!-- increase the page height during page transition to prevent the scrolling animation from jumping --> <div id="page-height-extend" class="hidden h-[300vh]" data-astro-cid-sckkx6r4 style="--bannerOffset: 15vh;--banner-height-home: 65vh;--banner-height: 35vh;--configHue: 210;--page-width: 90rem;"></div> <!-- Sakura Effect --> <script>(function(){const sakuraConfig = {"enable":true,"sakuraNum":21,"limitTimes":-1,"size":{"min":0.5,"max":1.1},"speed":{"horizontal":{"min":-1.7,"max":-1.2},"vertical":{"min":1.5,"max":2.2},"rotation":0.03},"zIndex":100};
// 樱花对象类
class Sakura {
constructor(x, y, s, r, fn, idx, img, limitArray, config) {
this.x = x;
this.y = y;
this.s = s;
this.r = r;
this.fn = fn;
this.idx = idx;
this.img = img;
this.limitArray = limitArray;
this.config = config;
}
draw(cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.rotate(this.r);
cxt.drawImage(this.img, 0, 0, 40 * this.s, 40 * this.s);
cxt.restore();
}
update() {
this.x = this.fn.x(this.x, this.y);
this.y = this.fn.y(this.y, this.y);
this.r = this.fn.r(this.r);
// 如果樱花越界,重新调整位置
if (
this.x > window.innerWidth ||
this.x < 0 ||
this.y > window.innerHeight ||
this.y < 0
) {
// 如果樱花不做限制
if (this.limitArray[this.idx] === -1) {
this.resetPosition();
}
// 否则樱花有限制
else {
if (this.limitArray[this.idx] > 0) {
this.resetPosition();
this.limitArray[this.idx]--;
}
}
}
}
resetPosition() {
this.r = getRandom('fnr', this.config);
if (Math.random() > 0.4) {
this.x = getRandom('x', this.config);
this.y = 0;
this.s = getRandom('s', this.config);
this.r = getRandom('r', this.config);
} else {
this.x = window.innerWidth;
this.y = getRandom('y', this.config);
this.s = getRandom('s', this.config);
this.r = getRandom('r', this.config);
}
}
}
// 樱花列表类
class SakuraList {
constructor() {
this.list = [];
}
push(sakura) {
this.list.push(sakura);
}
update() {
for (let i = 0, len = this.list.length; i < len; i++) {
this.list[i].update();
}
}
draw(cxt) {
for (let i = 0, len = this.list.length; i < len; i++) {
this.list[i].draw(cxt);
}
}
get(i) {
return this.list[i];
}
size() {
return this.list.length;
}
}
// 获取随机值的函数
function getRandom(option, config) {
let ret;
let random;
switch (option) {
case 'x':
ret = Math.random() * window.innerWidth;
break;
case 'y':
ret = Math.random() * window.innerHeight;
break;
case 's':
ret = config.size.min + Math.random() * (config.size.max - config.size.min);
break;
case 'r':
ret = Math.random() * 6;
break;
case 'fnx':
random = config.speed.horizontal.min + Math.random() * (config.speed.horizontal.max - config.speed.horizontal.min);
ret = function (x, y) {
return x + random;
};
break;
case 'fny':
random = config.speed.vertical.min + Math.random() * (config.speed.vertical.max - config.speed.vertical.min);
ret = function (x, y) {
return y + random;
};
break;
case 'fnr':
ret = function (r) {
return r + config.speed.rotation;
};
break;
}
return ret;
}
// 樱花管理器类
class SakuraManager {
constructor(config) {
this.config = config;
this.canvas = null;
this.ctx = null;
this.sakuraList = null;
this.animationId = null;
this.img = null;
this.isRunning = false;
}
// 初始化樱花特效
async init() {
if (!this.config.enable || this.isRunning) {
return;
}
// 创建图片对象
this.img = new Image();
this.img.src = '/sakura.png'; // 使用樱花图片
// 等待图片加载完成
await new Promise((resolve, reject) => {
if (this.img) {
this.img.onload = () => resolve();
this.img.onerror = () => reject(new Error('Failed to load sakura image'));
}
});
this.createCanvas();
this.createSakuraList();
this.startAnimation();
this.isRunning = true;
}
// 创建画布
createCanvas() {
this.canvas = document.createElement('canvas');
this.canvas.height = window.innerHeight;
this.canvas.width = window.innerWidth;
this.canvas.setAttribute('style', `position: fixed; left: 0; top: 0; pointer-events: none; z-index: ${this.config.zIndex};`);
this.canvas.setAttribute('id', 'canvas_sakura');
document.body.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
// 监听窗口大小变化
window.addEventListener('resize', this.handleResize.bind(this));
}
// 创建樱花列表
createSakuraList() {
if (!this.img || !this.ctx) return;
this.sakuraList = new SakuraList();
const limitArray = new Array(this.config.sakuraNum).fill(this.config.limitTimes);
for (let i = 0; i < this.config.sakuraNum; i++) {
const randomX = getRandom('x', this.config);
const randomY = getRandom('y', this.config);
const randomR = getRandom('r', this.config);
const randomS = getRandom('s', this.config);
const randomFnx = getRandom('fnx', this.config);
const randomFny = getRandom('fny', this.config);
const randomFnR = getRandom('fnr', this.config);
const sakura = new Sakura(
randomX,
randomY,
randomS,
randomR,
{
x: randomFnx,
y: randomFny,
r: randomFnR,
},
i,
this.img,
limitArray,
this.config
);
sakura.draw(this.ctx);
this.sakuraList.push(sakura);
}
}
// 开始动画
startAnimation() {
if (!this.ctx || !this.canvas || !this.sakuraList) return;
const animate = () => {
if (!this.ctx || !this.canvas || !this.sakuraList) return;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.sakuraList.update();
this.sakuraList.draw(this.ctx);
this.animationId = requestAnimationFrame(animate);
};
this.animationId = requestAnimationFrame(animate);
}
// 处理窗口大小变化
handleResize() {
if (this.canvas) {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
}
}
// 停止樱花特效
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
if (this.canvas) {
document.body.removeChild(this.canvas);
this.canvas = null;
}
window.removeEventListener('resize', this.handleResize.bind(this));
this.isRunning = false;
}
// 切换樱花特效
toggle() {
if (this.isRunning) {
this.stop();
} else {
this.init();
}
}
// 更新配置
updateConfig(newConfig) {
const wasRunning = this.isRunning;
if (wasRunning) {
this.stop();
}
this.config = newConfig;
if (wasRunning && newConfig.enable) {
this.init();
}
}
// 获取运行状态
getIsRunning() {
return this.isRunning;
}
}
// 创建全局樱花管理器实例
let globalSakuraManager = null;
// 初始化樱花特效
function initSakura(config) {
if (globalSakuraManager) {
globalSakuraManager.updateConfig(config);
} else {
globalSakuraManager = new SakuraManager(config);
if (config.enable) {
globalSakuraManager.init();
}
}
}
// 樱花特效初始化
(function() {
// 全局标记,确保樱花特效只初始化一次
if (window.sakuraInitialized) {
return;
}
// 初始化樱花特效的函数
const setupSakura = () => {
if (sakuraConfig.enable && !window.sakuraInitialized) {
initSakura(sakuraConfig);
window.sakuraInitialized = true;
}
};
// 页面加载完成后初始化樱花特效
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupSakura);
} else {
setupSakura();
}
})();
})();</script> <!-- Translate.js integration - lazy loading --> <script type="module">class d{constructor(){this.isInitialized=!1,this.clickHandler=null}init(){console.log("GalleryManager init called");const t=document.querySelectorAll(".gallery-group");if(console.log("Found gallery groups:",t.length),t.length===0){console.log("No gallery groups found, skipping initialization");return}this.cleanup(),console.log("Adding click event listener to document"),this.clickHandler=e=>this.handleClick(e),document.addEventListener("click",this.clickHandler),this.isInitialized=!0}cleanup(){this.clickHandler&&(document.removeEventListener("click",this.clickHandler),this.clickHandler=null),this.isInitialized=!1}handleClick(t){console.log("GalleryManager handleClick triggered",t.target);const e=t.target.closest(".gallery-header");if(e){console.log("Gallery header clicked",e),t.preventDefault(),t.stopPropagation(),this.toggleGallery(e);return}const n=t.target.closest(".gallery-image");if(n){console.log("Gallery image clicked",n),t.preventDefault(),t.stopPropagation();const l=n.getAttribute("data-image");l&&this.showImageModal(l)}}toggleGallery(t){console.log("toggleGallery called",t);const e=t.closest(".gallery-group"),n=e.querySelector(".gallery-content"),l=t.querySelector(".expand-icon");console.log("Found elements:",{group:e,content:n,icon:l}),console.log("Content hidden?",n?.classList.contains("hidden")),n&&n.classList.contains("hidden")?(console.log("Expanding gallery"),n.classList.remove("hidden"),e.classList.add("expanded"),l&&(l.style.transform="rotate(180deg)")):n?(console.log("Collapsing gallery"),n.classList.add("hidden"),e.classList.remove("expanded"),l&&(l.style.transform="rotate(0deg)")):console.error("Gallery content not found!")}showImageModal(t){const e=document.createElement("div");e.className="gallery-modal fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 p-4",e.style.opacity="0",e.style.transition="opacity 0.3s ease";const n=document.createElement("div");n.className="relative max-w-full max-h-full";const l=document.createElement("img");l.src=t,l.alt="查看图片",l.className="max-w-full max-h-full object-contain rounded-lg",l.style.transform="scale(0.9)",l.style.transition="transform 0.3s ease";const o=document.createElement("button");o.className="close-btn absolute top-4 right-4 bg-black bg-opacity-50 text-white p-2 rounded-full hover:bg-opacity-70 transition-colors",o.innerHTML=`
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
`,n.appendChild(l),n.appendChild(o),e.appendChild(n),document.body.appendChild(e),requestAnimationFrame(()=>{e.style.opacity="1",l.style.transform="scale(1)"});const a=()=>{e.style.opacity="0",l.style.transform="scale(0.9)",setTimeout(()=>{document.body.contains(e)&&document.body.removeChild(e),document.removeEventListener("keydown",c)},300)},c=s=>{s.key==="Escape"&&a()};o.addEventListener("click",a),e.addEventListener("click",s=>{s.target===e&&a()}),document.addEventListener("keydown",c)}}window.galleryManager=new d;const r=()=>{window.galleryManager.init()},i=()=>{r(),window.swup&&(window.swup.hooks.on("page:view",()=>{setTimeout(r,50)}),window.swup.hooks.on("content:replace",()=>{window.galleryManager.cleanup()},{before:!0}))};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",i):i();window.swup||document.addEventListener("swup:enable",i);</script> </body> </html> <script type="module" src="/blog/_astro/Layout.astro_astro_type_script_index_1_lang.BilWV2C2.js"></script> <script type="module" src="/blog/_astro/Layout.astro_astro_type_script_index_2_lang.BTdkr2U1.js"></script> <script type="module">window.loadTranslateScript=function(){return window.translate||document.getElementById("translate-script")?Promise.resolve():new Promise((n,t)=>{const e=document.createElement("script");e.src="/translate.js",e.id="translate-script",e.async=!0,e.onload=()=>{typeof window.translate<"u"?n():t(new Error("translate.js loaded but window.translate not available"))},e.onerror=t,document.head.appendChild(e)})};</script> <script type="module">document.addEventListener("DOMContentLoaded",()=>{window.__iconifyLoader&&window.__iconifyLoader.load().then(()=>{document.querySelectorAll(".skill-card").forEach(e=>{e.dispatchEvent(new CustomEvent("iconify-ready"))})}).catch(o=>{console.error("Failed to load icons on skills page:",o)})});typeof window<"u"&&window.addEventListener("pageshow",o=>{o.persisted&&window.__iconifyLoader&&setTimeout(()=>{window.__iconifyLoader.load().catch(console.error)},100)});</script>