Files
nl-blogs/view/UI2.html
2026-01-15 13:51:44 +08:00

1812 lines
112 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="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>艺术与代码 | 年糕崽崽的数字花园</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Noto+Sans+SC:wght@300;400;500;700&family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['"Inter"', '"Noto Sans SC"', 'sans-serif'],
serif: ['"Playfair Display"', 'serif'],
mono: ['"JetBrains Mono"', 'monospace'],
},
colors: {
art: {
bg: '#050505', // 更深的黑
surface: '#121214',
border: 'rgba(255,255,255,0.08)',
text: '#ececec',
muted: '#888888',
accent: '#d4b383',
error: '#ef4444' // Error Red
}
},
backgroundImage: {
'noise': "url('data:image/svg+xml,%3Csvg viewBox=%220 0 200 200%22 xmlns=%22http://www.w3.org/2000/svg%22%3E%3Cfilter id=%22noiseFilter%22%3E%3CfeTurbulence type=%22fractalNoise%22 baseFrequency=%220.65%22 numOctaves=%223%22 stitchTiles=%22stitch%22/%3E%3C/filter%3E%3Crect width=%22100%25%22 height=%22100%25%22 filter=%22url(%23noiseFilter)%22 opacity=%220.07%22/%3E%3C/svg%3E')",
},
animation: {
'float': 'float 8s ease-in-out infinite',
'slide-down': 'slideDown 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards',
'reveal': 'reveal 1s cubic-bezier(0.16, 1, 0.3, 1) forwards',
'spotlight': 'spotlight 2s ease .75s 1 forwards',
'marquee': 'marquee 25s linear infinite',
'marquee-reverse': 'marqueeReverse 30s linear infinite',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'shake': 'shake 0.5s cubic-bezier(.36,.07,.19,.97) both',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-20px)' },
},
slideDown: {
'0%': { transform: 'translateY(-100%)' },
'100%': { transform: 'translateY(0)' },
},
reveal: {
'0%': { opacity: '0', transform: 'translateY(50px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
spotlight: {
'0%': { opacity: 0, transform: 'translate(-50%, -50%) scale(0.5)' },
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' }
},
marquee: {
'0%': { transform: 'translateX(0%)' },
'100%': { transform: 'translateX(-100%)' } /* Only need to move half if content is duplicated */
},
marqueeReverse: {
'0%': { transform: 'translateX(-50%)' },
'100%': { transform: 'translateX(0%)' }
},
shake: {
'10%, 90%': { transform: 'translate3d(-1px, 0, 0)' },
'20%, 80%': { transform: 'translate3d(2px, 0, 0)' },
'30%, 50%, 70%': { transform: 'translate3d(-4px, 0, 0)' },
'40%, 60%': { transform: 'translate3d(4px, 0, 0)' }
}
}
}
}
}
</script>
<style>
/* --- Artistic Scrollbar Design --- */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); border-radius: 3px; transition: all 0.3s ease; }
::-webkit-scrollbar-thumb:hover { background: #d4b383; box-shadow: 0 0 10px rgba(212, 179, 131, 0.4); }
::-webkit-scrollbar-corner { background: transparent; }
* { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.1) transparent; }
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.05); }
.custom-scrollbar:hover::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); }
.glass-nav {
background: rgba(5, 5, 5, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.art-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.03);
transition: all 0.5s cubic-bezier(0.16, 1, 0.3, 1);
position: relative;
overflow: hidden;
}
.art-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(180deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.02) 100%);
opacity: 0;
transition: opacity 0.5s ease;
}
.art-card:hover {
transform: translateY(-5px);
border-color: rgba(212, 179, 131, 0.3);
box-shadow: 0 20px 40px -10px rgba(0,0,0,0.5);
}
.art-card:hover::before { opacity: 1; }
.text-glow { text-shadow: 0 0 30px rgba(255,255,255,0.1); }
.link-underline { position: relative; }
.link-underline::after {
content: ''; position: absolute; width: 0; height: 1px; bottom: -2px; left: 0;
background-color: #d4b383; transition: width 0.3s ease;
}
.link-underline:hover::after { width: 100%; }
.menu-open { transform: translateY(0); opacity: 1; pointer-events: auto; }
.menu-closed { transform: translateY(-20px); opacity: 0; pointer-events: none; }
/* Syntax Highlighting Colors */
.code-keyword { color: #c678dd; }
.code-func { color: #61afef; }
.code-string { color: #98c379; }
.code-comment { color: #5c6370; font-style: italic; }
.code-tag { color: #e06c75; }
.code-attr { color: #d19a66; }
/* Modal Styles */
.modal-overlay {
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
}
.preview-area {
background-image:
linear-gradient(45deg, #1a1a1a 25%, transparent 25%),
linear-gradient(-45deg, #1a1a1a 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #1a1a1a 75%),
linear-gradient(-45deg, transparent 75%, #1a1a1a 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
}
/* Typography for Blog Content */
.blog-content p { margin-bottom: 1.5rem; line-height: 1.8; color: #d4d4d8; font-weight: 300; }
.blog-content h2 { font-family: 'Playfair Display', serif; font-size: 1.875rem; color: white; margin-top: 2.5rem; margin-bottom: 1rem; font-style: italic; }
.blog-content h3 { font-size: 1.5rem; color: white; margin-top: 2rem; margin-bottom: 0.75rem; font-weight: 600; }
.blog-content ul { list-style-type: disc; padding-left: 1.5rem; margin-bottom: 1.5rem; color: #d4d4d8; }
.blog-content li { margin-bottom: 0.5rem; }
.blog-content code { font-family: 'JetBrains Mono', monospace; background: rgba(255,255,255,0.1); padding: 0.2em 0.4em; rounded: 0.25rem; font-size: 0.875em; color: #d4b383; }
.blog-content pre {
background: #1e1e1e;
padding: 3rem 1.5rem 1.5rem 1.5rem;
border-radius: 0.75rem;
overflow-x: auto;
margin-bottom: 1.5rem;
border: 1px solid rgba(255,255,255,0.1);
position: relative;
box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5);
}
.blog-content pre::before {
content: '';
position: absolute;
top: 1rem;
left: 1.5rem;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background: #ff5f56;
box-shadow: 1.25rem 0 0 #ffbd2e, 2.5rem 0 0 #27c93f;
}
/* --- New Service Styles --- */
.tilt-card { position: relative; transform-style: preserve-3d; transform: perspective(1000px); transition: transform 0.1s ease; }
.tilt-inner { background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.05); backdrop-filter: blur(10px); border-radius: 1rem; padding: 2rem; height: 100%; position: relative; overflow: hidden; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05); }
.tilt-inner::before { content: ""; position: absolute; height: 100%; width: 100%; top: 0; left: 0; background: radial-gradient(600px circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(212, 179, 131, 0.15), transparent 40%); z-index: 0; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
.tilt-card:hover .tilt-inner::before { opacity: 1; }
.process-accordion { display: flex; gap: 1rem; height: 400px; width: 100%; }
.process-step { position: relative; flex: 1; background: #121214; border: 1px solid rgba(255,255,255,0.1); border-radius: 1.5rem; overflow: hidden; transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1); cursor: default; display: flex; flex-direction: column; justify-content: flex-end; padding: 2rem; }
.process-step:hover { flex: 3; background: #1a1a1c; border-color: rgba(212, 179, 131, 0.4); }
.process-step::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.8) 100%); z-index: 1; }
.step-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.3; transition: opacity 0.5s, transform 0.5s; z-index: 0; filter: grayscale(100%); }
.process-step:hover .step-bg { opacity: 0.6; transform: scale(1.05); filter: grayscale(0%); }
.step-content { position: relative; z-index: 10; transform: translateY(0); transition: transform 0.5s; }
.step-number { font-family: 'Playfair Display', serif; font-size: 4rem; color: rgba(255,255,255,0.1); line-height: 1; margin-bottom: 1rem; transition: color 0.3s; }
.process-step:hover .step-number { color: #d4b383; }
.step-desc { max-height: 0; opacity: 0; overflow: hidden; transition: all 0.5s ease; color: #888888; font-size: 0.9rem; line-height: 1.6; }
.process-step:hover .step-desc { max-height: 100px; opacity: 1; margin-top: 1rem; }
@media (max-width: 768px) { .process-accordion { flex-direction: column; height: auto; } .process-step { height: 120px; } .process-step:hover { flex: none; height: 200px; } }
/* Tech Stack Grid */
.tech-category-title { font-family: 'JetBrains Mono', monospace; font-size: 0.75rem; letter-spacing: 0.1em; color: #d4b383; text-transform: uppercase; margin-bottom: 0.75rem; padding-bottom: 0.25rem; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
.tech-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.5rem; }
.tech-item { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); padding: 0.35rem 0.75rem; font-family: 'Inter', sans-serif; font-size: 0.8rem; color: #a1a1aa; transition: all 0.2s; cursor: default; }
.tech-item:hover { border-color: #d4b383; color: white; background: rgba(212, 179, 131, 0.1); }
.work-detail-page { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #050505; z-index: 100; overflow-y: auto; }
.gallery-image { width: 100%; margin-bottom: 4rem; filter: grayscale(20%); transition: filter 0.5s; }
.gallery-image:hover { filter: grayscale(0%); }
/* Danmaku Styles */
.danmaku-item {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 0.75rem 1.25rem;
border-radius: 9999px;
display: inline-flex;
align-items: center;
gap: 0.75rem;
white-space: nowrap;
color: #d4d4d8;
font-size: 0.875rem;
transition: all 0.3s;
}
.danmaku-item:hover {
background: rgba(255, 255, 255, 0.1);
border-color: #d4b383;
transform: scale(1.05);
color: white;
z-index: 10;
}
.danmaku-row {
display: flex;
gap: 2rem;
width: max-content;
}
.partner-logo {
filter: grayscale(100%) opacity(0.5);
transition: all 0.4s ease;
}
.partner-logo:hover {
filter: grayscale(0%) opacity(1);
transform: scale(1.1);
}
/* --- Artistic Form Validation Styles --- */
.input-error {
border-bottom-color: #ef4444 !important; /* Art Error Red */
}
.error-msg {
color: #ef4444;
font-family: 'JetBrains Mono', monospace;
font-size: 0.65rem;
margin-top: 0.4rem;
display: block;
position: absolute;
bottom: -1.2rem;
left: 0;
opacity: 0;
letter-spacing: 0.05em;
animation: fadeIn 0.3s forwards;
background: rgba(239, 68, 68, 0.1);
padding: 0.1rem 0.3rem;
border-radius: 0.2rem;
}
@keyframes fadeIn { to { opacity: 1; } }
</style>
</head>
<body class="bg-art-bg text-art-text font-sans antialiased min-h-screen selection:bg-art-accent selection:text-black relative">
<!-- Global Noise -->
<div class="fixed inset-0 pointer-events-none z-[60] bg-noise opacity-30 mix-blend-overlay"></div>
<!-- Header -->
<header class="fixed top-0 w-full z-50 glass-nav transition-all duration-300" id="main-header">
<div class="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
<div class="cursor-pointer group" onclick="router('home')">
<span class="font-serif text-2xl italic tracking-wider text-white group-hover:text-art-accent transition-colors">年糕崽崽.Dev</span>
</div>
<nav class="hidden md:flex items-center gap-10">
<button onclick="router('home')" class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline active-nav" data-target="home">首页</button>
<button onclick="router('blog')" class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline" data-target="blog">思考</button>
<button onclick="router('works')" class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline" data-target="works">作品</button>
<button onclick="router('snippets')" class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline" data-target="snippets">代码</button>
<button onclick="router('about')" class="nav-item text-sm font-medium tracking-wide text-art-muted hover:text-white transition-colors link-underline" data-target="about">关于</button>
</nav>
<div class="hidden md:flex items-center gap-4">
<a href="#" class="text-art-muted hover:text-white transition-colors"><i data-lucide="github" class="w-5 h-5"></i></a>
<button onclick="router('services')" class="px-5 py-2 text-xs font-bold tracking-widest uppercase border border-white/20 hover:border-art-accent hover:text-art-accent transition-all rounded-full">
合作
</button>
</div>
<button class="md:hidden text-white" onclick="toggleMobileMenu()">
<i data-lucide="menu" class="w-6 h-6"></i>
</button>
</div>
</header>
<!-- Mobile Menu -->
<div id="mobile-menu" class="fixed inset-0 z-40 bg-art-bg/95 backdrop-blur-xl flex flex-col items-center justify-center space-y-8 transition-all duration-300 menu-closed md:hidden">
<button onclick="toggleMobileMenu()" class="absolute top-6 right-6 text-white"><i data-lucide="x" class="w-8 h-8"></i></button>
<a onclick="mobileNav('home')" class="font-serif text-3xl italic text-white hover:text-art-accent cursor-pointer">首页</a>
<a onclick="mobileNav('blog')" class="font-serif text-3xl italic text-white hover:text-art-accent cursor-pointer">思考</a>
<a onclick="mobileNav('works')" class="font-serif text-3xl italic text-white hover:text-art-accent cursor-pointer">作品</a>
<a onclick="mobileNav('snippets')" class="font-serif text-3xl italic text-white hover:text-art-accent cursor-pointer">代码</a>
<a onclick="mobileNav('about')" class="font-serif text-3xl italic text-white hover:text-art-accent cursor-pointer">关于</a>
<a onclick="mobileNav('admin-login')" class="font-mono text-sm text-art-muted mt-8">管理入口</a>
</div>
<!-- Main Content -->
<main class="pt-32 pb-20 px-6 max-w-7xl mx-auto relative z-10">
<!-- HOME SECTION -->
<section id="home" class="page-section block">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-12 mb-24 items-center animate-reveal">
<div class="lg:col-span-7 space-y-8">
<p class="text-art-accent font-mono text-sm tracking-widest uppercase mb-4">前端架构师 & 创意开发者</p>
<h1 class="font-serif text-5xl md:text-7xl lg:text-8xl leading-[1.1] text-white text-glow">
<span class="block italic opacity-80">设计</span>
<span class="block font-bold">铸造数字</span>
<span class="block font-bold pl-12 md:pl-24">灵魂</span>
</h1>
<p class="text-art-muted text-lg md:text-xl max-w-xl leading-relaxed mt-8 font-light">
在代码的逻辑与设计的感性之间寻找平衡。我是<span class="text-white font-bold">年糕崽崽</span>,不仅仅构建页面,更在构建<span class="text-white border-b border-white/20 pb-0.5">沉浸式体验</span>
</p>
<div class="pt-8 flex items-center gap-6">
<button onclick="router('works')" class="group flex items-center gap-2 text-white border-b border-white pb-1 hover:text-art-accent hover:border-art-accent transition-all">
<span>浏览作品集</span>
<i data-lucide="arrow-right" class="w-4 h-4 transform group-hover:translate-x-1 transition-transform"></i>
</button>
</div>
</div>
<div class="lg:col-span-5 relative h-[400px] lg:h-[600px] w-full flex items-center justify-center">
<div class="relative w-full h-full">
<div class="absolute inset-0 border border-white/10 rounded-full rotate-12 scale-90"></div>
<div class="absolute inset-0 border border-white/5 rounded-full -rotate-6 scale-75"></div>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-80 bg-gradient-to-br from-white/10 to-transparent backdrop-blur-md border border-white/10 rounded-sm shadow-2xl rotate-6 hover:rotate-0 transition-transform duration-700 z-10 flex flex-col p-6 justify-between">
<div class="text-xs font-mono text-white/50">最新作品</div>
<div class="font-serif text-3xl italic text-white">Mono<br>Dashboard</div>
<div class="flex justify-end"><i data-lucide="arrow-up-right" class="text-white"></i></div>
</div>
</div>
</div>
</div>
<!-- Featured Content Grid (Bento) -->
<div class="space-y-6 animate-reveal" style="animation-delay: 0.2s;">
<div class="flex items-end justify-between border-b border-white/10 pb-4 mb-8">
<h2 class="font-serif text-3xl italic text-white">精选内容</h2>
<span class="font-mono text-xs text-art-muted">下滑探索更多</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 auto-rows-[300px]">
<div class="md:col-span-2 art-card rounded-2xl p-8 flex flex-col justify-end group cursor-pointer" onclick="openBlog('dashboard')">
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent z-10"></div>
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop')] bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-60 mix-blend-overlay"></div>
<div class="relative z-20 space-y-2">
<div class="flex items-center gap-3">
<span class="px-2 py-1 text-[10px] font-mono border border-white/20 rounded-full text-white backdrop-blur-sm">设计思维</span>
<span class="text-xs text-white/60">2025年10月24日</span>
</div>
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors">当极简主义遇见复杂数据Dashboard 设计哲学</h3>
</div>
</div>
<div class="art-card rounded-2xl p-8 flex flex-col justify-between group">
<div class="flex justify-between items-start">
<i data-lucide="code-2" class="w-8 h-8 text-white/40 group-hover:text-art-accent transition-colors"></i>
<i data-lucide="arrow-up-right" class="w-5 h-5 text-white/20"></i>
</div>
<div>
<div class="text-4xl font-mono font-bold text-white mb-2">120+</div>
<div class="text-sm text-art-muted">开源提交 (Commits)</div>
<div class="text-xs text-art-muted mt-2 opacity-60">Vue, React, Three.js</div>
</div>
</div>
<div class="art-card rounded-2xl p-6 md:p-8 flex flex-col justify-between group cursor-pointer bg-[#050505]" onclick="router('snippets')">
<div class="font-mono text-xs text-art-muted mb-4">// React Hook: useArt</div>
<div class="font-mono text-sm text-gray-400 overflow-hidden opacity-60 group-hover:opacity-100 transition-opacity">
<span class="code-keyword">const</span> <span class="code-func">useCreative</span> = () => {<br>
&nbsp;&nbsp;<span class="code-keyword">return</span> <span class="code-string">"Innovation"</span>;<br>
}
</div>
<div class="mt-4 flex items-center gap-2 text-sm font-medium text-white">
<span>访问代码实验室</span>
<i data-lucide="chevron-right" class="w-4 h-4 text-art-accent"></i>
</div>
</div>
<div class="md:col-span-2 art-card rounded-2xl p-8 md:p-12 flex flex-col md:flex-row items-center justify-between gap-8 group cursor-pointer bg-gradient-to-r from-art-surface to-transparent" onclick="router('services')">
<div class="space-y-4">
<h3 class="font-serif text-3xl text-white">需要独特的前端架构?</h3>
<p class="text-art-muted max-w-sm">不论是 WebGL 3D 交互网站,还是高性能的 SaaS 管理系统,我都能提供专业的解决方案。</p>
</div>
<div class="h-16 w-16 rounded-full border border-white/20 flex items-center justify-center group-hover:bg-white group-hover:text-black transition-all duration-300">
<i data-lucide="arrow-right" class="w-6 h-6"></i>
</div>
</div>
</div>
</div>
</section>
<!-- BLOG LIST SECTION -->
<section id="blog" class="page-section hidden animate-slide-down">
<!-- (Content same as previous version) -->
<div class="max-w-4xl mx-auto">
<div class="mb-16 text-center">
<h2 class="font-serif text-5xl italic text-white mb-6">深度思考</h2>
<p class="text-art-muted max-w-lg mx-auto">关于前端技术、交互设计以及数字艺术的深度思考。</p>
</div>
<div class="space-y-12">
<article class="group cursor-pointer border-b border-white/5 pb-12" onclick="openBlog('refactor')">
<div class="flex flex-col md:flex-row gap-8 items-start">
<div class="md:w-1/4 pt-2">
<span class="font-mono text-xs text-art-accent block mb-1">工程化</span>
<span class="text-sm text-art-muted">2026年1月12日</span>
</div>
<div class="md:w-3/4 space-y-4">
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors leading-tight">
重构的艺术:如何优雅地处理遗留代码
</h3>
<p class="text-art-muted font-light leading-relaxed">
面对堆积如山的 jQuery 代码,我们不应感到恐惧。本文将分享我在将一个拥有 5 年历史的大型单体应用迁移至 Vue 3 微前端架构过程中的心路历程。
</p>
<div class="text-xs text-art-accent font-medium mt-4 group-hover:text-white transition-colors">阅读全文 -></div>
</div>
</div>
</article>
<article class="group cursor-pointer border-b border-white/5 pb-12" onclick="openBlog('shader')">
<div class="flex flex-col md:flex-row gap-8 items-start">
<div class="md:w-1/4 pt-2">
<span class="font-mono text-xs text-blue-400 block mb-1">图形渲染</span>
<span class="text-sm text-art-muted">2025年12月8日</span>
</div>
<div class="md:w-3/4 space-y-4">
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors leading-tight">
着色器魔法:从零开始写一个噪声生成器
</h3>
<p class="text-art-muted font-light leading-relaxed">
GLSL 并不是黑魔法。我们将通过 Three.js 的 ShaderMaterial一步步实现如水流般自然的柏林噪声Perlin Noise
</p>
<div class="text-xs text-art-accent font-medium mt-4 group-hover:text-white transition-colors">阅读全文 -></div>
</div>
</div>
</article>
</div>
</div>
</section>
<!-- BLOG DETAIL SECTION (Dynamic Content) -->
<section id="blog-detail" class="page-section hidden animate-slide-down">
<div class="max-w-4xl mx-auto">
<button onclick="router('blog')" class="group flex items-center gap-2 text-art-muted hover:text-white transition-colors mb-12">
<i data-lucide="arrow-left" class="w-4 h-4 transform group-hover:-translate-x-1 transition-transform"></i>
<span class="text-sm font-medium tracking-wide">返回文章列表</span>
</button>
<div class="mb-16 border-b border-white/5 pb-10">
<div class="flex items-center gap-4 mb-6">
<span id="post-category" class="px-3 py-1 border border-white/20 rounded-full text-xs font-mono text-art-accent uppercase tracking-wider"></span>
<span id="post-date" class="text-sm text-art-muted"></span>
</div>
<h1 id="post-title" class="font-serif text-4xl md:text-5xl lg:text-6xl text-white leading-tight mb-8"></h1>
<div class="flex items-center gap-4">
<div class="w-10 h-10 rounded-full overflow-hidden border border-white/20">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao" alt="Avatar" class="w-full h-full object-cover">
</div>
<div>
<div class="text-sm font-medium text-white">年糕崽崽</div>
<div class="text-xs text-art-muted">前端架构师</div>
</div>
</div>
</div>
<article id="post-content" class="blog-content prose prose-invert prose-lg max-w-none">
</article>
</div>
</section>
<!-- WORK LIST SECTION -->
<section id="works" class="page-section hidden animate-slide-down">
<div class="mb-16">
<h2 class="font-serif text-5xl italic text-white mb-6">精选作品</h2>
<p class="text-art-muted">这里展示了我参与设计和开发的核心项目。</p>
</div>
<div class="grid grid-cols-1 gap-20">
<!-- Project 1: Nova -->
<div class="group grid grid-cols-1 md:grid-cols-2 gap-10 items-center cursor-pointer" onclick="openWork('nova')">
<div class="relative aspect-[4/3] overflow-hidden rounded-sm bg-gray-900 border border-white/5">
<div class="absolute inset-0 bg-gradient-to-tr from-purple-900/40 to-blue-900/40 mix-blend-color-burn z-10"></div>
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1611974765270-ca12586343bb?q=80&w=2070&auto=format&fit=crop')] bg-cover bg-center group-hover:scale-105 transition-transform duration-700 opacity-80"></div>
<div class="absolute inset-0 bg-black/30 group-hover:bg-transparent transition-colors z-20"></div>
</div>
<div class="space-y-6">
<div class="font-mono text-xs text-art-accent">2023 • 金融科技</div>
<h3 class="font-serif text-4xl text-white group-hover:text-art-accent transition-colors">Nova 交易平台</h3>
<p class="text-art-muted font-light leading-relaxed">
一个为专业交易员设计的高频交易终端。
</p>
<div class="flex flex-wrap gap-3">
<span class="px-3 py-1 border border-white/10 rounded-full text-xs text-white/70">React</span>
<span class="px-3 py-1 border border-white/10 rounded-full text-xs text-white/70">D3.js</span>
</div>
<div class="pt-4">
<span class="inline-flex items-center gap-2 text-sm font-medium text-white border-b border-transparent hover:border-art-accent hover:text-art-accent transition-all">
查看项目详情 <i data-lucide="arrow-right" class="w-4 h-4"></i>
</span>
</div>
</div>
</div>
<!-- Project 2: ArchDaily -->
<div class="group grid grid-cols-1 md:grid-cols-2 gap-10 items-center cursor-pointer" onclick="openWork('archdaily')">
<div class="md:order-2 relative aspect-[4/3] overflow-hidden rounded-sm bg-gray-900 border border-white/5">
<div class="absolute inset-0 bg-gradient-to-tr from-orange-900/40 to-red-900/40 mix-blend-color-burn z-10"></div>
<div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070&auto=format&fit=crop')] bg-cover bg-center group-hover:scale-105 transition-transform duration-700 opacity-80"></div>
</div>
<div class="md:order-1 space-y-6 md:text-right">
<div class="font-mono text-xs text-art-accent">2022 • 建筑设计</div>
<h3 class="font-serif text-4xl text-white group-hover:text-art-accent transition-colors">ArchDaily 网站重构</h3>
<p class="text-art-muted font-light leading-relaxed">
重塑建筑灵感网站的浏览体验。
</p>
<div class="flex flex-wrap gap-3 md:justify-end">
<span class="px-3 py-1 border border-white/10 rounded-full text-xs text-white/70">Vue 3</span>
<span class="px-3 py-1 border border-white/10 rounded-full text-xs text-white/70">Nuxt</span>
</div>
<div class="pt-4 flex md:justify-end">
<span class="inline-flex items-center gap-2 text-sm font-medium text-white border-b border-transparent hover:border-art-accent hover:text-art-accent transition-all">
查看项目详情 <i data-lucide="arrow-right" class="w-4 h-4"></i>
</span>
</div>
</div>
</div>
</div>
</section>
<!-- WORK DETAIL SECTION (REFACTORED - Editorial Style) -->
<section id="work-detail" class="work-detail-page page-section hidden">
<!-- Close Button (Fixed) -->
<button onclick="router('works')" class="fixed top-8 right-8 z-[60] mix-blend-difference text-white hover:scale-110 transition-transform">
<div class="rounded-full border border-white/20 p-4 backdrop-blur-md bg-white/5">
<i data-lucide="x" class="w-6 h-6"></i>
</div>
</button>
<!-- Scroll Progress Bar -->
<div class="fixed top-0 left-0 h-1 bg-art-accent z-[60] w-0 transition-all duration-100" id="scroll-progress"></div>
<!-- Main Content -->
<div class="relative w-full" id="work-detail-content">
<!-- Hero Section (Full Screen) -->
<div class="relative h-[85vh] w-full overflow-hidden group">
<img id="work-hero-img" src="" class="absolute inset-0 w-full h-full object-cover filter grayscale group-hover:grayscale-0 transition-all duration-1000 scale-105" alt="Cover">
<div class="absolute inset-0 bg-black/40"></div>
<!-- Play Button Overlay -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-24 h-24 rounded-full border border-white/20 bg-white/5 backdrop-blur-md flex items-center justify-center cursor-pointer hover:scale-110 hover:bg-white/10 transition-all duration-500 group-hover:border-art-accent/50 z-20">
<i data-lucide="play" class="w-8 h-8 text-white fill-white ml-1"></i>
<div class="absolute inset-0 rounded-full border border-white/20 animate-pulse-slow"></div>
</div>
<div class="absolute bottom-0 left-0 p-8 md:p-20 w-full">
<div class="flex items-end justify-between border-t border-white/30 pt-8 animate-slide-down">
<div>
<span id="work-category" class="font-mono text-art-accent text-sm tracking-[0.2em] uppercase mb-2 block"></span>
<h1 id="work-title" class="font-serif text-5xl md:text-8xl text-white leading-[0.9] mix-blend-overlay"></h1>
</div>
<span id="work-year" class="hidden md:block font-mono text-white/50 text-xl"></span>
</div>
</div>
</div>
<!-- Content Split -->
<div class="flex flex-col md:flex-row max-w-[1920px] mx-auto min-h-screen">
<!-- Left: Sticky Meta (FIXED: added overflow-y-auto and max-h for scrolling) -->
<div class="md:w-1/3 p-8 md:p-16 md:sticky md:top-0 md:h-screen md:max-h-screen overflow-y-auto custom-scrollbar flex flex-col justify-between border-r border-white/5 bg-[#050505]">
<div class="space-y-12">
<div>
<h3 class="font-mono text-xs text-art-muted uppercase tracking-widest mb-4">关于项目</h3>
<div id="work-desc" class="text-white/80 font-light leading-relaxed text-lg font-serif"></div>
</div>
<div>
<h3 class="font-mono text-xs text-art-muted uppercase tracking-widest mb-6">技术全景</h3>
<div id="work-tech-stack" class="space-y-6">
<!-- Dynamic Tech Stack will be inserted here -->
</div>
</div>
</div>
<div class="mt-12">
<a id="work-link-live" href="#" target="_blank" class="group flex items-center justify-between w-full py-6 border-t border-white/10 hover:bg-white/5 transition-colors">
<span class="font-serif text-2xl italic text-white group-hover:text-art-accent transition-colors">访问线上项目</span>
<i data-lucide="arrow-up-right" class="w-6 h-6 text-white group-hover:rotate-45 transition-transform"></i>
</a>
</div>
</div>
<!-- Right: Gallery Feed -->
<div class="md:w-2/3 bg-[#080808] flex flex-col">
<div id="work-gallery" class="flex flex-col flex-grow">
<!-- Images injected here -->
</div>
<!-- Next Project Teaser -->
<div class="h-[40vh] flex items-center justify-center border-t border-white/5 bg-[#050505] cursor-pointer group hover:bg-white/5 transition-colors" onclick="openNextWork()">
<div class="text-center">
<p class="font-mono text-xs text-art-muted mb-4 tracking-widest">下一个作品</p>
<h2 class="font-serif text-5xl text-white group-hover:italic transition-all">返回作品列表</h2>
</div>
</div>
<!-- Footer for Detail Page -->
<footer class="py-8 text-center border-t border-white/5 relative z-10 bg-[#050505]">
<p class="text-art-muted text-xs font-mono tracking-widest opacity-50">&copy; 2024 年糕崽崽. 保留所有权利.</p>
</footer>
</div>
</div>
</div>
</section>
<!-- SNIPPETS SECTION (FIXED & ENHANCED) -->
<section id="snippets" class="page-section hidden animate-slide-down">
<div class="mb-12 text-center">
<h2 class="font-serif text-5xl italic text-white mb-6">代码实验室</h2>
<p class="text-art-muted">点击下方卡片查看代码与实时效果。</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Snippet 1 -->
<div class="art-card rounded-xl p-0 overflow-hidden bg-[#0d0d0d] border border-white/10 cursor-pointer group hover:border-art-accent/50 transition-colors" onclick="openSnippet('mouse')">
<!-- Mac Window Header -->
<div class="flex items-center justify-between px-4 py-3 bg-white/5 border-b border-white/5">
<div class="flex gap-2">
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
<div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div>
<div class="w-3 h-3 rounded-full bg-[#27c93f]"></div>
</div>
<span class="text-xs font-mono text-art-muted group-hover:text-white transition-colors">useMousePosition.ts</span>
<div class="text-xs font-mono text-art-accent opacity-0 group-hover:opacity-100 transition-opacity">点击运行 -></div>
</div>
<!-- Code Preview (Styled) -->
<div class="p-6 overflow-x-auto font-mono text-sm leading-relaxed pointer-events-none opacity-80 group-hover:opacity-100 transition-opacity">
<pre class="text-gray-300">
<span class="code-keyword">import</span> { useState, useEffect } <span class="code-keyword">from</span> <span class="code-string">'react'</span>;
<span class="code-keyword">export const</span> <span class="code-func">useMousePosition</span> = () => {
<span class="code-keyword">const</span> [pos, setPos] = <span class="code-func">useState</span>({ x: 0, y: 0 });
<span class="code-comment">// ... Implementation logic</span>
<span class="code-keyword">return</span> pos;
};
</pre>
</div>
</div>
<!-- Snippet 2 -->
<div class="art-card rounded-xl p-0 overflow-hidden bg-[#0d0d0d] border border-white/10 cursor-pointer group hover:border-art-accent/50 transition-colors" onclick="openSnippet('glass')">
<div class="flex items-center justify-between px-4 py-3 bg-white/5 border-b border-white/5">
<div class="flex gap-2">
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
<div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div>
<div class="w-3 h-3 rounded-full bg-[#27c93f]"></div>
</div>
<span class="text-xs font-mono text-art-muted group-hover:text-white transition-colors">glass.css</span>
<div class="text-xs font-mono text-art-accent opacity-0 group-hover:opacity-100 transition-opacity">点击运行 -></div>
</div>
<div class="p-6 overflow-x-auto font-mono text-sm leading-relaxed pointer-events-none opacity-80 group-hover:opacity-100 transition-opacity">
<pre class="text-gray-300">
<span class="code-comment">/* 完美的毛玻璃效果 */</span>
<span class="code-keyword">.glass-panel</span> {
<span class="code-func">background</span>: <span class="code-string">rgba(255, 255, 255, 0.05)</span>;
<span class="code-func">backdrop-filter</span>: <span class="code-func">blur</span>(20px);
<span class="code-func">box-shadow</span>: 0 4px 30px <span class="code-string">rgba(0, 0, 0, 0.1)</span>;
}
</pre>
</div>
</div>
<!-- Snippet 3 -->
<div class="art-card rounded-xl p-0 overflow-hidden bg-[#0d0d0d] border border-white/10 cursor-pointer group hover:border-art-accent/50 transition-colors" onclick="openSnippet('noise')">
<div class="flex items-center justify-between px-4 py-3 bg-white/5 border-b border-white/5">
<div class="flex gap-2">
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
<div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div>
<div class="w-3 h-3 rounded-full bg-[#27c93f]"></div>
</div>
<span class="text-xs font-mono text-art-muted group-hover:text-white transition-colors">noise.svg</span>
<div class="text-xs font-mono text-art-accent opacity-0 group-hover:opacity-100 transition-opacity">点击运行 -></div>
</div>
<div class="p-6 overflow-x-auto font-mono text-sm leading-relaxed pointer-events-none opacity-80 group-hover:opacity-100 transition-opacity">
<pre class="text-gray-300">
<span class="code-comment">&lt;!-- SVG 噪点滤镜 --&gt;</span>
<span class="code-keyword">&lt;filter</span> <span class="code-func">id</span>=<span class="code-string">"noise"</span><span class="code-keyword">&gt;</span>
<span class="code-keyword">&lt;feTurbulence</span> <span class="code-func">type</span>=<span class="code-string">"fractalNoise"</span> /<span class="code-keyword">&gt;</span>
<span class="code-keyword">&lt;feColorMatrix</span> <span class="code-func">values</span>=<span class="code-string">"0"</span> /<span class="code-keyword">&gt;</span>
<span class="code-keyword">&lt;/filter&gt;</span>
</pre>
</div>
</div>
</div>
</section>
<!-- SERVICES SECTION (UPDATED with Danmaku & Partners) -->
<section id="services" class="page-section hidden animate-slide-down">
<div class="py-16 text-center">
<h2 class="font-serif text-5xl italic text-white mb-6">共创数字未来</h2>
<p class="text-art-muted text-lg max-w-2xl mx-auto">用代码构建骨架,用设计触动灵魂。提供全链路的数字化解决方案。</p>
</div>
<!-- 3D Tilt Cards Container -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-32 px-4" id="services-grid">
<!-- Card 1 -->
<div class="tilt-card group h-96">
<div class="tilt-inner flex flex-col justify-between">
<div class="w-16 h-16 rounded-2xl bg-blue-500/10 flex items-center justify-center text-blue-400 mb-6 border border-blue-500/20 group-hover:scale-110 transition-transform duration-500">
<i data-lucide="layers" class="w-8 h-8"></i>
</div>
<div>
<h3 class="text-2xl font-bold text-white mb-4">前端架构设计</h3>
<p class="text-art-muted text-sm leading-relaxed">
为复杂的大型应用提供可扩展的架构方案。从 Monorepo 搭建到微前端拆分,确保代码库在团队扩张时依然保持整洁高效。
</p>
</div>
<div class="mt-4 pt-4 border-t border-white/5 flex gap-2">
<span class="text-xs border border-white/10 rounded-full px-2 py-1 text-white/50">React</span>
<span class="text-xs border border-white/10 rounded-full px-2 py-1 text-white/50">Vue</span>
</div>
</div>
</div>
<!-- Card 2 (Featured) -->
<div class="tilt-card group h-96">
<div class="tilt-inner flex flex-col justify-between border-art-accent/20 bg-white/5">
<div class="w-16 h-16 rounded-2xl bg-art-accent/10 flex items-center justify-center text-art-accent mb-6 border border-art-accent/20 group-hover:scale-110 transition-transform duration-500 shadow-[0_0_30px_-10px_rgba(212,179,131,0.3)]">
<i data-lucide="wand-2" class="w-8 h-8"></i>
</div>
<div>
<h3 class="text-2xl font-bold text-white mb-4">创意交互开发</h3>
<p class="text-art-muted text-sm leading-relaxed">
利用 WebGL (Three.js) 和 GSAP 打造令人过目难忘的着陆页。将品牌故事转化为沉浸式的数字体验,提升品牌溢价。
</p>
</div>
<div class="mt-4 pt-4 border-t border-white/5 flex gap-2">
<span class="text-xs border border-white/10 rounded-full px-2 py-1 text-white/50">WebGL</span>
<span class="text-xs border border-white/10 rounded-full px-2 py-1 text-white/50">GSAP</span>
</div>
</div>
</div>
<!-- Card 3 -->
<div class="tilt-card group h-96">
<div class="tilt-inner flex flex-col justify-between">
<div class="w-16 h-16 rounded-2xl bg-purple-500/10 flex items-center justify-center text-purple-400 mb-6 border border-purple-500/20 group-hover:scale-110 transition-transform duration-500">
<i data-lucide="smartphone" class="w-8 h-8"></i>
</div>
<div>
<h3 class="text-2xl font-bold text-white mb-4">跨平台应用</h3>
<p class="text-art-muted text-sm leading-relaxed">
使用 UniApp 或 React Native 开发高质量的移动端应用。一次编写,多端运行,在保证原生体验的同时最大化开发效率。
</p>
</div>
<div class="mt-4 pt-4 border-t border-white/5 flex gap-2">
<span class="text-xs border border-white/10 rounded-full px-2 py-1 text-white/50">Flutter</span>
<span class="text-xs border border-white/10 rounded-full px-2 py-1 text-white/50">RN</span>
</div>
</div>
</div>
</div>
<!-- Interactive Process Section -->
<div class="max-w-6xl mx-auto border-t border-white/5 pt-20 mb-32">
<h3 class="text-3xl font-serif text-white text-center mb-16 italic">合作流程</h3>
<div class="process-accordion">
<!-- Step 1 -->
<div class="process-step group">
<img src="https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070&auto=format&fit=crop" class="step-bg" alt="Meeting">
<div class="step-content">
<div class="step-number">01</div>
<h4 class="text-2xl font-bold text-white">需求探索</h4>
<div class="step-desc">
我们从深度沟通开始,不仅是听取需求,更是共同拆解业务痛点。通过用户画像分析和竞品调研,确立产品的核心价值与差异化路径。
</div>
</div>
</div>
<!-- Step 2 -->
<div class="process-step group">
<img src="https://images.unsplash.com/photo-1561070791-2526d30994b5?q=80&w=2064&auto=format&fit=crop" class="step-bg" alt="Design">
<div class="step-content">
<div class="step-number">02</div>
<h4 class="text-2xl font-bold text-white">交互设计</h4>
<div class="step-desc">
输出高保真原型图确定视觉风格系统Design System。我们将精心打磨每一个微交互动效确保用户在操作时的流畅感与愉悦感。
</div>
</div>
</div>
<!-- Step 3 -->
<div class="process-step group">
<img src="https://images.unsplash.com/photo-1555099962-4199c345e5dd?q=80&w=2070&auto=format&fit=crop" class="step-bg" alt="Code">
<div class="step-content">
<div class="step-number">03</div>
<h4 class="text-2xl font-bold text-white">敏捷开发</h4>
<div class="step-desc">
采用模块化编码标准两周为一个迭代周期Sprint。您将定期收到可交互的演示版本随时反馈调整拒绝黑盒开发。
</div>
</div>
</div>
<!-- Step 4 -->
<div class="process-step group">
<img src="https://images.unsplash.com/photo-1460925895917-afdab827c52f?q=80&w=2015&auto=format&fit=crop" class="step-bg" alt="Launch">
<div class="step-content">
<div class="step-number">04</div>
<h4 class="text-2xl font-bold text-white">交付上线</h4>
<div class="step-desc">
进行全方位的性能优化Lighthouse 95+)与 SEO 配置。协助完成服务器部署,并移交完整的源码与技术文档,确保后续维护无忧。
</div>
</div>
</div>
</div>
</div>
<!-- Danmaku Style Testimonials -->
<div class="py-20 mb-32 overflow-hidden relative">
<!-- Left Gradient Fade -->
<div class="absolute left-0 top-0 w-20 h-full bg-gradient-to-r from-art-bg to-transparent z-10"></div>
<!-- Right Gradient Fade -->
<div class="absolute right-0 top-0 w-20 h-full bg-gradient-to-l from-art-bg to-transparent z-10"></div>
<h3 class="text-3xl font-serif text-white text-center mb-12 italic">客户原声</h3>
<div class="flex flex-col gap-6">
<!-- Row 1: Slow Left -->
<div class="danmaku-row animate-marquee hover:[animation-play-state:paused]">
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-blue-500/20"></div>
<span>"从未见过如此丝滑的 WebGL 体验!转化率直接提升了 150%。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-purple-500/20"></div>
<span>"代码质量非常高,易于维护。绝对是值得信赖的合作伙伴。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-green-500/20"></div>
<span>"年糕崽崽对细节的把控令人惊叹,强烈推荐!"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-yellow-500/20"></div>
<span>"沟通非常顺畅,能够快速理解我们的业务需求。"</span>
</div>
<!-- Duplicates for seamless loop -->
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-blue-500/20"></div>
<span>"从未见过如此丝滑的 WebGL 体验!转化率直接提升了 150%。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-purple-500/20"></div>
<span>"代码质量非常高,易于维护。绝对是值得信赖的合作伙伴。"</span>
</div>
</div>
<!-- Row 2: Medium Right (Reverse) -->
<div class="danmaku-row animate-marquee-reverse hover:[animation-play-state:paused] ml-10">
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-pink-500/20"></div>
<span>"设计感十足,完全超出了我们的预期。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-indigo-500/20"></div>
<span>"交付非常准时,没有任何隐形费用,很靠谱。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-teal-500/20"></div>
<span>"从原型到上线的每一个环节都非常专业。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-orange-500/20"></div>
<span>"这才是真正的全栈开发者,技术与审美的完美结合。"</span>
</div>
<!-- Duplicates -->
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-pink-500/20"></div>
<span>"设计感十足,完全超出了我们的预期。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-indigo-500/20"></div>
<span>"交付非常准时,没有任何隐形费用,很靠谱。"</span>
</div>
</div>
<!-- Row 3: Fast Left -->
<div class="danmaku-row animate-marquee hover:[animation-play-state:paused]" style="animation-duration: 20s;">
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-red-500/20"></div>
<span>"移动端适配做得非常完美,兼容性没有任何问题。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-cyan-500/20"></div>
<span>"虽然是远程合作,但效率比在办公室还高。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-emerald-500/20"></div>
<span>"不仅技术好,还给了我们很多产品上的建议。"</span>
</div>
<!-- Duplicates -->
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-red-500/20"></div>
<span>"移动端适配做得非常完美,兼容性没有任何问题。"</span>
</div>
<div class="danmaku-item">
<div class="w-6 h-6 rounded-full bg-cyan-500/20"></div>
<span>"虽然是远程合作,但效率比在办公室还高。"</span>
</div>
</div>
</div>
</div>
<!-- NEW: FAQ Section -->
<div class="max-w-3xl mx-auto px-6 mb-32">
<h3 class="text-3xl font-serif text-white text-center mb-12 italic">常见问题</h3>
<div class="space-y-4">
<!-- FAQ Item 1 -->
<div class="border border-white/10 rounded-xl overflow-hidden group">
<button class="w-full flex items-center justify-between p-6 text-left bg-white/5 hover:bg-white/10 transition-colors" onclick="toggleFaq(this)">
<span class="text-white font-medium">项目开发的周期通常是多久?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-art-muted transition-transform group-[.is-open]:rotate-180"></i>
</button>
<div class="h-0 overflow-hidden transition-all duration-300 ease-in-out bg-[#0a0a0c]">
<div class="p-6 text-sm text-art-muted leading-relaxed border-t border-white/5">
这取决于项目的复杂程度。一般来说,单页品牌着陆页需要 1-2 周,而复杂的 SaaS 后台或移动应用可能需要 1-3 个月。在需求探索阶段,我会为您提供详细的时间表。
</div>
</div>
</div>
<!-- FAQ Item 2 -->
<div class="border border-white/10 rounded-xl overflow-hidden group">
<button class="w-full flex items-center justify-between p-6 text-left bg-white/5 hover:bg-white/10 transition-colors" onclick="toggleFaq(this)">
<span class="text-white font-medium">后期维护和技术支持如何?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-art-muted transition-transform group-[.is-open]:rotate-180"></i>
</button>
<div class="h-0 overflow-hidden transition-all duration-300 ease-in-out bg-[#0a0a0c]">
<div class="p-6 text-sm text-art-muted leading-relaxed border-t border-white/5">
项目交付后,我提供为期 1 个月的免费技术支持,用于修复潜在 Bug。之后您可以选择按次付费或签订长期的维护合同确保系统持续稳定运行。
</div>
</div>
</div>
<!-- FAQ Item 3 -->
<div class="border border-white/10 rounded-xl overflow-hidden group">
<button class="w-full flex items-center justify-between p-6 text-left bg-white/5 hover:bg-white/10 transition-colors" onclick="toggleFaq(this)">
<span class="text-white font-medium">是否接受远程合作?</span>
<i data-lucide="chevron-down" class="w-5 h-5 text-art-muted transition-transform group-[.is-open]:rotate-180"></i>
</button>
<div class="h-0 overflow-hidden transition-all duration-300 ease-in-out bg-[#0a0a0c]">
<div class="p-6 text-sm text-art-muted leading-relaxed border-t border-white/5">
当然!作为一名数字游民,我已经习惯了高效的远程协作模式。无论您在世界的哪个角落,我们都可以通过 Zoom、Notion 和 Figma 保持紧密的沟通。
</div>
</div>
</div>
</div>
</div>
<!-- NEW: CTA Section -->
<div class="max-w-4xl mx-auto text-center px-6 mb-20">
<div class="p-12 rounded-3xl bg-gradient-to-b from-white/10 to-transparent border border-white/10 relative overflow-hidden">
<div class="absolute top-0 left-1/2 -translate-x-1/2 w-64 h-64 bg-art-accent/20 blur-[100px] -z-10"></div>
<h2 class="text-4xl md:text-5xl font-serif text-white mb-6">准备好开始了吗?</h2>
<p class="text-art-muted mb-8 max-w-xl mx-auto">无论是一个疯狂的想法,还是一个具体的业务需求,我都乐意倾听。让我们一起创造非凡。</p>
<button onclick="openInquiry()" class="inline-flex items-center gap-2 px-8 py-4 bg-white text-black rounded-full font-bold hover:scale-105 transition-transform">
<span>发起合作咨询</span>
<i data-lucide="arrow-right" class="w-5 h-5"></i>
</button>
</div>
</div>
<!-- NEW: Partners Section -->
<div class="max-w-4xl mx-auto px-6 mb-32 border-t border-white/5 pt-12">
<p class="text-center text-xs font-mono text-art-muted tracking-widest uppercase mb-8">Trusted By</p>
<div class="flex flex-wrap justify-center gap-8 md:gap-16 opacity-50">
<!-- Simple Text Logos for Clean Look -->
<span class="partner-logo text-xl font-bold font-serif text-white cursor-pointer">VOGUE</span>
<span class="partner-logo text-xl font-bold font-mono text-white cursor-pointer">WIRED</span>
<span class="partner-logo text-xl font-bold font-sans text-white cursor-pointer">stripe</span>
<span class="partner-logo text-xl font-bold font-serif italic text-white cursor-pointer">Monocle</span>
<span class="partner-logo text-xl font-bold font-mono text-white cursor-pointer">vercel</span>
</div>
</div>
</section>
<!-- ABOUT SECTION -->
<section id="about" class="page-section hidden animate-slide-down">
<div class="max-w-4xl mx-auto py-10">
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<div class="space-y-8">
<div>
<span class="text-art-accent font-mono text-sm tracking-widest uppercase">个人档案</span>
<h2 class="font-serif text-5xl italic text-white mt-2 mb-6">关于我</h2>
</div>
<!-- Avatar & Basic Info -->
<div class="flex items-center gap-6">
<div class="w-24 h-24 rounded-full overflow-hidden border-2 border-art-accent/50 shadow-2xl">
<!-- Using DiceBear API for a cool, consistent avatar -->
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4" alt="年糕崽崽" class="w-full h-full object-cover">
</div>
<div class="space-y-2">
<h3 class="text-2xl font-bold text-white">年糕崽崽</h3>
<div class="flex items-center gap-2 text-sm text-art-muted">
<i data-lucide="map-pin" class="w-4 h-4 text-art-accent"></i>
<span>中国 · 浙江杭州</span>
</div>
</div>
</div>
<div class="prose prose-invert text-art-muted font-light leading-relaxed">
<p>
嗨!我是<strong>年糕崽崽</strong>,一名热衷于探索数字边界的前端架构师。
</p>
<p>
我的职业生涯始于对像素级完美的执念。从早期的 jQuery 插件开发到如今的 WebGL 3D 场景构建,我始终认为:<strong>技术是骨架,艺术是灵魂。</strong>
</p>
<p>
目前,我专注于高性能 B 端应用的体验升级以及探索生成式艺术Generative Art在网页中的应用。闲暇之余我喜欢在杭州的西湖边散步或者捣腾我的客制化机械键盘。
</p>
</div>
<!-- Contact Info Card -->
<div class="bg-white/5 border border-white/5 p-6 rounded-xl backdrop-blur-sm mt-6">
<h4 class="text-white font-bold mb-4 flex items-center gap-2">
<i data-lucide="contact" class="w-4 h-4 text-art-accent"></i> 联系方式
</h4>
<div class="space-y-3 text-sm">
<a href="mailto:niangao@example.com" class="flex items-center gap-3 text-art-muted hover:text-white transition-colors">
<i data-lucide="mail" class="w-4 h-4"></i>
<span>hello@niangao.dev</span>
</a>
<div class="flex items-center gap-3 text-art-muted">
<i data-lucide="message-circle" class="w-4 h-4"></i>
<span>WeChat: Niangao_Dev</span>
</div>
<a href="#" class="flex items-center gap-3 text-art-muted hover:text-white transition-colors">
<i data-lucide="github" class="w-4 h-4"></i>
<span>github.com/niangao</span>
</a>
</div>
</div>
</div>
<div class="space-y-8">
<div class="bg-white/5 border border-white/5 p-8 rounded-2xl backdrop-blur-sm">
<h3 class="text-xl font-bold text-white mb-6 flex items-center gap-2">
<i data-lucide="cpu" class="w-5 h-5 text-art-accent"></i> 技术栈
</h3>
<div class="flex flex-wrap gap-2 mb-10">
<span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">Vue 3 / Nuxt</span>
<span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">React / Next.js</span>
<span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">TypeScript</span>
<span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">Three.js</span>
<span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">Tailwind CSS</span>
<span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">Rust / Wails</span>
</div>
<h3 class="text-xl font-bold text-white mb-6 flex items-center gap-2">
<i data-lucide="briefcase" class="w-5 h-5 text-art-accent"></i> 经历
</h3>
<div class="space-y-6 relative border-l border-white/10 ml-2 pl-6">
<div class="relative">
<div class="absolute -left-[29px] top-1.5 w-3 h-3 rounded-full bg-art-accent"></div>
<div class="text-sm text-art-accent font-mono mb-1">2023 - 至今</div>
<div class="text-white font-bold">高级前端专家</div>
<div class="text-xs text-art-muted">TechFlow Inc.</div>
</div>
<div class="relative">
<div class="absolute -left-[29px] top-1.5 w-3 h-3 rounded-full bg-white/20"></div>
<div class="text-sm text-art-muted font-mono mb-1">2020 - 2023</div>
<div class="text-white font-bold">资深开发工程师</div>
<div class="text-xs text-art-muted">Creative Digital Agency</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="py-8 text-center border-t border-white/5 relative z-10">
<p class="text-art-muted text-xs font-mono tracking-widest opacity-50">&copy; 2024 年糕崽崽. 保留所有权利.</p>
</footer>
<!-- SNIPPET MODAL -->
<div id="snippet-modal" class="fixed inset-0 z-[100] modal-overlay hidden flex items-center justify-center p-4 md:p-10 opacity-0 transition-opacity duration-300">
<div class="bg-[#0a0a0c] border border-white/10 w-full max-w-6xl h-full md:h-[80vh] rounded-2xl flex flex-col overflow-hidden shadow-2xl transform scale-95 transition-transform duration-300" id="modal-content">
<div class="h-16 border-b border-white/10 flex items-center justify-between px-6 bg-white/5">
<div class="flex items-center gap-3">
<i data-lucide="code-2" class="text-art-accent"></i>
<span class="font-bold text-white" id="modal-title">Snippet Title</span>
</div>
<button onclick="closeSnippet()" class="p-2 hover:bg-white/10 rounded-full text-white/50 hover:text-white transition-colors">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="flex-1 flex flex-col md:flex-row h-full overflow-hidden">
<div class="w-full md:w-1/2 bg-[#0d0d0d] overflow-auto p-6 border-r border-white/10 relative group">
<button class="absolute top-4 right-4 text-xs bg-white/10 px-2 py-1 rounded text-white/50 hover:text-white" onclick="alert('代码已复制')">复制</button>
<pre class="font-mono text-sm leading-relaxed text-gray-300" id="modal-code"></pre>
</div>
<div class="w-full md:w-1/2 preview-area relative flex items-center justify-center overflow-hidden" id="modal-preview-container">
<div class="text-white/30 font-mono">正在加载预览...</div>
</div>
</div>
</div>
</div>
<!-- REFACTORED INQUIRY MODAL (Artistic Style) -->
<div id="inquiry-modal" class="fixed inset-0 z-[100] bg-[#050505]/95 backdrop-blur-xl hidden items-center justify-center p-4 transition-opacity duration-300">
<div class="relative w-full max-w-4xl bg-[#080808] border border-white/10 overflow-hidden flex flex-col md:flex-row shadow-2xl">
<!-- Manifesto Sidebar (Desktop Only) -->
<div class="hidden md:flex w-1/3 bg-[#0c0c0c] border-r border-white/5 p-12 flex-col justify-between relative overflow-hidden">
<!-- Background FX -->
<div class="absolute top-0 left-0 w-full h-full bg-noise opacity-10"></div>
<div class="absolute -top-20 -left-20 w-64 h-64 bg-art-accent/10 rounded-full blur-[80px]"></div>
<div class="relative z-10">
<h3 class="font-serif text-3xl text-white italic mb-6 leading-tight">Let's build<br>something<br>unreal.</h3>
<p class="font-mono text-xs text-white/40 leading-relaxed">告诉我你的构想。<br>我将提供架构与代码,<br>将想象变为现实。</p>
</div>
<div class="relative z-10 font-mono text-[10px] text-white/20 tracking-widest">
LAT: 30.2741° N<br>
LONG: 120.1551° E<br>
HANGZHOU, CN
</div>
</div>
<!-- Main Form Area -->
<div class="w-full md:w-2/3 p-10 md:p-16 relative bg-[#080808]">
<button onclick="document.getElementById('inquiry-modal').classList.add('hidden')" class="absolute top-6 right-6 text-white/30 hover:text-white transition-colors z-20">
<i data-lucide="x" class="w-6 h-6"></i>
</button>
<form id="inquiry-form" class="space-y-10 relative z-10">
<!-- Section 1: Identity -->
<div class="space-y-8">
<p class="font-mono text-xs text-art-accent uppercase tracking-widest border-b border-white/10 pb-2 mb-6">01 // 身份识别</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Name Input -->
<div class="relative group/input">
<input name="name" type="text" class="peer w-full bg-transparent border-b border-white/20 py-2 text-white font-serif text-lg focus:border-art-accent outline-none transition-colors placeholder-transparent" placeholder="Name">
<label class="absolute left-0 top-2 text-white/30 text-xs font-mono transition-all peer-focus:-top-4 peer-focus:text-art-accent peer-not-placeholder-shown:-top-4 peer-not-placeholder-shown:text-white/50 pointer-events-none">您的称呼</label>
</div>
<!-- Company Input -->
<div class="relative group/input">
<input name="company" type="text" class="peer w-full bg-transparent border-b border-white/20 py-2 text-white font-serif text-lg focus:border-art-accent outline-none transition-colors placeholder-transparent" placeholder="Company">
<label class="absolute left-0 top-2 text-white/30 text-xs font-mono transition-all peer-focus:-top-4 peer-focus:text-art-accent peer-not-placeholder-shown:-top-4 peer-not-placeholder-shown:text-white/50 pointer-events-none">公司 / 组织</label>
</div>
</div>
<!-- Contact Input -->
<div class="relative group/input">
<input name="contact" type="text" class="peer w-full bg-transparent border-b border-white/20 py-2 text-white font-serif text-lg focus:border-art-accent outline-none transition-colors placeholder-transparent" placeholder="Contact">
<label class="absolute left-0 top-2 text-white/30 text-xs font-mono transition-all peer-focus:-top-4 peer-focus:text-art-accent peer-not-placeholder-shown:-top-4 peer-not-placeholder-shown:text-white/50 pointer-events-none">联系方式 (Email / WeChat)</label>
</div>
</div>
<!-- Section 2: Details -->
<div class="space-y-6">
<p class="font-mono text-xs text-art-accent uppercase tracking-widest border-b border-white/10 pb-2 mb-6">02 // 项目细节</p>
<!-- Artistic Budget Selector -->
<div class="space-y-3">
<label class="text-xs font-mono text-white/30 block mb-2">预估预算范围</label>
<div class="flex flex-wrap gap-3">
<label class="cursor-pointer group">
<input type="radio" name="budget" value="10k-50k" class="peer hidden">
<span class="px-4 py-2 border border-white/10 rounded-none text-xs font-mono text-white/50 hover:border-art-accent hover:text-white transition-all peer-checked:bg-art-accent peer-checked:text-black peer-checked:border-art-accent">10k - 50k</span>
</label>
<label class="cursor-pointer group">
<input type="radio" name="budget" value="50k-200k" class="peer hidden">
<span class="px-4 py-2 border border-white/10 rounded-none text-xs font-mono text-white/50 hover:border-art-accent hover:text-white transition-all peer-checked:bg-art-accent peer-checked:text-black peer-checked:border-art-accent">50k - 200k</span>
</label>
<label class="cursor-pointer group">
<input type="radio" name="budget" value="200k+" class="peer hidden">
<span class="px-4 py-2 border border-white/10 rounded-none text-xs font-mono text-white/50 hover:border-art-accent hover:text-white transition-all peer-checked:bg-art-accent peer-checked:text-black peer-checked:border-art-accent">200k +</span>
</label>
</div>
</div>
<!-- Brief Input -->
<div class="relative group/input mt-8">
<textarea name="description" rows="1" class="peer w-full bg-transparent border-b border-white/20 py-2 text-white font-serif text-lg focus:border-art-accent outline-none transition-colors placeholder-transparent resize-none min-h-[40px]" placeholder="Brief" oninput="this.style.height = ''; this.style.height = this.scrollHeight + 'px'"></textarea>
<label class="absolute left-0 top-2 text-white/30 text-xs font-mono transition-all peer-focus:-top-4 peer-focus:text-art-accent peer-not-placeholder-shown:-top-4 peer-not-placeholder-shown:text-white/50 pointer-events-none">一句话描述需求</label>
</div>
</div>
<!-- Submit Action -->
<div class="pt-6">
<button type="submit" class="group relative w-full overflow-hidden bg-white text-black font-bold py-4 px-8 transition-all hover:bg-art-accent">
<div class="absolute inset-0 w-0 bg-art-accent transition-all duration-[250ms] ease-out group-hover:w-full"></div>
<span class="relative flex items-center justify-between z-10">
<span class="font-mono uppercase tracking-widest text-sm">INITIATE_PROTOCOL // 发送</span>
<i data-lucide="arrow-right" class="w-4 h-4 transition-transform group-hover:translate-x-1"></i>
</span>
</button>
<p class="mt-4 text-[10px] text-white/20 font-mono text-center tracking-widest">SECURED BY DIGITAL FINGERPRINTING</p>
</div>
</form>
</div>
</div>
</div>
<script>
lucide.createIcons();
// Router
function router(targetId) {
const effectiveTarget = (targetId === 'blog-detail' || targetId === 'work-detail') ? 'works' : (targetId === 'blog-detail' ? 'blog' : targetId);
const navTarget = targetId === 'blog-detail' ? 'blog' : (targetId === 'work-detail' ? 'works' : targetId);
document.querySelectorAll('.nav-item').forEach(btn => {
if (btn.dataset.target === navTarget) {
btn.classList.add('text-white', 'after:w-full');
btn.classList.remove('text-art-muted');
} else {
btn.classList.remove('text-white', 'after:w-full');
btn.classList.add('text-art-muted');
}
});
// Toggle Sections
document.querySelectorAll('.page-section').forEach(section => {
if (section.id === targetId) {
section.classList.remove('hidden');
section.classList.remove('animate-slide-down');
void section.offsetWidth;
// Special animation for work detail content
if(targetId === 'work-detail') {
const detailPage = document.querySelector('.work-detail-page');
if(detailPage) detailPage.scrollTop = 0;
} else {
section.classList.add('animate-slide-down');
}
} else {
section.classList.add('hidden');
}
});
if(targetId !== 'work-detail') {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
// 3D Tilt Logic
document.querySelectorAll('.tilt-card').forEach(card => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const rotateX = ((y - centerY) / centerY) * -10;
const rotateY = ((x - centerX) / centerX) * 10;
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02)`;
card.querySelector('.tilt-inner').style.setProperty('--mouse-x', `${x}px`);
card.querySelector('.tilt-inner').style.setProperty('--mouse-y', `${y}px`);
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)';
});
});
// --- BLOG CONTENT LOGIC ---
const blogPosts = {
'dashboard': {
title: "当极简主义遇见复杂数据Dashboard 设计哲学",
category: "设计思维",
date: "2025年10月24日",
content: `
<p>在企业级应用开发中,"仪表盘"Dashboard往往是最具挑战性的模块。如何在有限的屏幕空间内展示海量数据同时保持视觉的清爽与操作的直观是每一个前端架构师必须面对的难题。</p>
<h2>1. 拒绝"数据堆砌"</h2>
<p>传统的 B 端设计容易陷入一个误区:展示的数据越多越好。这导致了许多 Dashboard 看起来像是一个拥挤的 Excel 表格。我的设计哲学是:<strong>如无必要,勿增实体。</strong></p>
<p>在 Nova Trade Platform 项目中,我们大胆砍掉了 40% 的非核心指标,将它们折叠在二级菜单中。首屏只保留对决策最关键的三个 KPI实时盈亏、风险敞口和市场波动率。</p>
<h2>2. 利用"视觉层级"</h2>
<p>通过字号、字重和颜色的微妙变化,我们可以引导用户的视线。例如,使用大号的 Serif 字体展示核心数字,而用弱化的 Sans-serif 字体展示辅助说明。</p>
<pre><code><span class="code-comment">// 示例:使用 Tailwind 实现视觉层级</span>
&lt;<span class="code-tag">div</span> <span class="code-attr">class</span>=<span class="code-string">"text-5xl font-serif text-white"</span>&gt;$12,450&lt;/<span class="code-tag">div</span>&gt;
&lt;<span class="code-tag">div</span> <span class="code-attr">class</span>=<span class="code-string">"text-sm text-gray-500 uppercase tracking-wide"</span>&gt;今日收益&lt;/<span class="code-tag">div</span>&gt;</code></pre>
<h2>3. 动效的意义</h2>
<p>动效不仅仅是为了炫技。在数据发生变化时一个平滑的数字滚动效果CountUp可以暗示数据的实时性图表加载时的渐显动画可以减少等待的枯燥感。</p>
<h3>结语</h3>
<p>优秀的设计不是做加法,而是做减法。在代码的世界里,我们追求性能的极致;在设计的世界里,我们追求认知的极简。</p>
`
},
'refactor': {
title: "重构的艺术:如何优雅地处理遗留代码",
category: "工程化",
date: "2026年1月12日",
content: `
<p>面对堆积如山的 jQuery 代码,很多现代前端开发者的第一反应是:推倒重来。然而,在商业项目中,"推倒重来"往往意味着巨大的风险和成本。</p>
<p>本文将分享我在将一个拥有 5 年历史的大型单体应用迁移至 Vue 3 微前端架构过程中的心路历程。</p>
<h2>1. 绞杀者模式 (Strangler Fig Pattern)</h2>
<p>我们没有选择一次性重写,而是采用了"绞杀者模式"。我们在旧系统之上搭建了一个新的 Vue 3 宿主应用,将旧页面作为 iframe 或微应用嵌入其中。</p>
<p>随着时间的推移,我们逐步将旧页面的功能用 Vue 3 重写并替换。用户几乎感觉不到变化,但底层的代码库正在发生质的飞跃。</p>
<h2>2. 保持类型安全</h2>
<p>在重构过程中,引入 TypeScript 是至关重要的。即使是 \`any\`,也比没有类型定义要好。我们为旧的 API 接口补充了类型定义,这在后期的重构中挽救了我们无数次。</p>
<pre><code><span class="code-comment">// 为旧 API 补充类型定义</span>
<span class="code-keyword">interface</span> LegacyResponse {
<span class="code-attr">status</span>: <span class="code-keyword">string</span>;
<span class="code-attr">data</span>: <span class="code-keyword">any</span>; <span class="code-comment">// 暂时的妥协</span>
}</code></pre>
<h3>核心经验</h3>
<ul>
<li>永远不要为了重构而重构,重构必须服务于业务价值。</li>
<li>保证测试覆盖率是重构的安全网。</li>
<li>小步快跑,持续交付。</li>
</ul>
`
},
'shader': {
title: "着色器魔法:从零开始写一个噪声生成器",
category: "图形渲染",
date: "2025年12月8日",
content: `
<p>GLSL (OpenGL Shading Language) 是一门让人生畏但也充满魅力的语言。它运行在 GPU 上,能够并行处理数百万个像素,创造出惊人的视觉效果。</p>
<h2>什么是柏林噪声?</h2>
<p>柏林噪声Perlin Noise是一种梯度噪声它比普通的随机数生成的噪声看起来更自然、更平滑。它常被用来模拟云彩、地形、火焰等自然现象。</p>
<h2>Three.js 中的实现</h2>
<p>在 Three.js 中,我们可以通过 <code>ShaderMaterial</code> 直接编写 GLSL 代码。</p>
<pre><code><span class="code-comment">// 简单的顶点着色器</span>
<span class="code-keyword">varying</span> <span class="code-keyword">vec2</span> vUv;
<span class="code-keyword">void</span> <span class="code-func">main</span>() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}</code></pre>
<p>通过调整噪声的频率和振幅,我们可以得到各种不同的纹理效果。在我的个人网站背景中,就使用了这种技术来生成流动的极光效果。</p>
`
}
};
function openBlog(id) {
const post = blogPosts[id];
if (!post) return;
document.getElementById('post-category').innerText = post.category;
document.getElementById('post-date').innerText = post.date;
document.getElementById('post-title').innerText = post.title;
document.getElementById('post-content').innerHTML = post.content;
router('blog-detail');
}
// --- WORK DETAIL LOGIC ---
let currentWorkId = '';
const workProjects = {
'nova': {
title: "Nova 交易平台",
category: "金融科技",
year: "2023",
heroImg: "https://images.unsplash.com/photo-1611974765270-ca12586343bb?q=80&w=2070&auto=format&fit=crop", // Stock chart visual
desc: `
<p>Nova 是一个专为机构交易员设计的高频交易终端。在本项目中,我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。</p>
<p>我们采用 <strong>Web Worker</strong> 来处理繁重的数据计算,避免阻塞主线程。为了实现极致的渲染性能,核心 K 线图表并没有使用现成的图表库,而是基于 <strong>D3.js</strong> 和 <strong>Canvas API</strong> 进行了深度定制。</p>
<p>结果是:即使在每秒推送 500+ 条 Tick 数据的高负载下,页面 FPS 依然稳定在 55+。</p>
`,
techStack: [
{ category: '前端层', items: ['React 18', 'TypeScript', 'Tailwind', 'D3.js', 'Web Workers'] },
{ category: '后端服务', items: ['Golang (Gin)', 'WebSocket', 'gRPC', 'Protobuf'] },
{ category: '数据存储', items: ['TimescaleDB', 'Redis Cluster', 'PostgreSQL'] },
{ category: '基础设施', items: ['Docker', 'K8s', 'Prometheus', 'Grafana'] }
],
gallery: [
"https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070&auto=format&fit=crop",
"https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070&auto=format&fit=crop",
"https://images.unsplash.com/photo-1640340434855-6084b1f4901c?q=80&w=1000&auto=format&fit=crop"
],
links: {
live: "https://example.com/nova-demo",
repo: "https://github.com/niangao/nova-trade"
},
next: 'archdaily'
},
'archdaily': {
title: "ArchDaily 网站重构",
category: "建筑设计",
year: "2022",
heroImg: "https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070&auto=format&fit=crop", // Architecture
desc: `
<p>ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验,并强化图片的视觉冲击力。</p>
<p>我们使用了 <strong>Nuxt 3</strong> 进行服务端渲染SSR显著提升了首屏加载速度LCP 优化至 1.2s 内)。针对大量的高清建筑摄影,我们实现了一套智能的图片加载策略:根据用户的网络状况和设备像素比,动态请求最合适的图片尺寸。</p>
`,
techStack: [
{ category: '核心前端', items: ['Vue 3', 'Nuxt.js', 'Pinia', 'GSAP'] },
{ category: '内容管理', items: ['Strapi (Headless CMS)', 'Markdown'] },
{ category: '云端部署', items: ['Vercel Edge', 'Cloudflare CDN', 'AWS S3'] }
],
gallery: [
"https://images.unsplash.com/photo-1503387762-592deb58ef4e?q=80&w=2089&auto=format&fit=crop",
"https://images.unsplash.com/photo-1518005020951-ecc859466abc?q=80&w=1920&auto=format&fit=crop",
"https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?q=80&w=1000&auto=format&fit=crop"
],
links: {
live: "https://example.com/arch-demo",
repo: "https://github.com/niangao/arch-redesign"
},
next: 'nova'
}
};
function openWork(id) {
const work = workProjects[id];
if (!work) return;
currentWorkId = id;
// Populate Main Info
document.getElementById('work-title').innerText = work.title;
document.getElementById('work-category').innerText = work.category;
document.getElementById('work-year').innerText = work.year;
document.getElementById('work-desc').innerHTML = work.desc;
document.getElementById('work-hero-img').src = work.heroImg;
// Links
document.getElementById('work-link-live').href = work.links.live;
// Tech Stack - Styled as a categorized spec sheet
const techContainer = document.getElementById('work-tech-stack');
techContainer.innerHTML = work.techStack.map(group => `
<div class="mb-6">
<div class="tech-category-title">${group.category}</div>
<div class="tech-grid">
${group.items.map(item => `
<div class="tech-item">
${item}
</div>
`).join('')}
</div>
</div>
`).join('');
// Gallery - Vertical Editorial Flow
const galleryContainer = document.getElementById('work-gallery');
// Create a mix of layouts: Full width, split width, etc.
galleryContainer.innerHTML = work.gallery.map((img, index) => {
// Alternating layout logic for "Editorial" feel
if (index % 2 === 0) {
return `<img src="${img}" class="gallery-image w-full object-cover aspect-video mb-8 md:mb-16 grayscale hover:grayscale-0 transition-all duration-700" alt="Detail">`;
} else {
return `<div class="flex justify-end mb-8 md:mb-16"><img src="${img}" class="gallery-image w-4/5 object-cover aspect-[4/3] grayscale hover:grayscale-0 transition-all duration-700" alt="Detail"></div>`;
}
}).join('');
// Show Section
router('work-detail');
// Update Scroll Progress bar logic specific to this page
const detailPage = document.querySelector('.work-detail-page');
const progressBar = document.getElementById('scroll-progress');
detailPage.onscroll = () => {
const scrollTop = detailPage.scrollTop;
const docHeight = detailPage.scrollHeight - detailPage.clientHeight;
const scrollPercent = (scrollTop / docHeight) * 100;
progressBar.style.width = scrollPercent + '%';
};
}
function openNextWork() {
const currentWork = workProjects[currentWorkId];
if (currentWork && currentWork.next) {
openWork(currentWork.next);
} else {
router('works');
}
}
function toggleMobileMenu() {
const menu = document.getElementById('mobile-menu');
if (menu.classList.contains('menu-closed')) {
menu.classList.remove('menu-closed');
menu.classList.add('menu-open');
document.body.style.overflow = 'hidden';
} else {
menu.classList.remove('menu-open');
menu.classList.add('menu-closed');
document.body.style.overflow = '';
}
}
function mobileNav(target) {
router(target);
toggleMobileMenu();
}
// --- SNIPPET MODAL LOGIC ---
const snippetsData = {
'mouse': {
title: 'React 鼠标追踪 Hook',
code: `import { useState, useEffect } from 'react';
export const useMousePosition = () => {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const update = (e) => setPos({
x: e.clientX,
y: e.clientY
});
window.addEventListener('mousemove', update);
return () =>
window.removeEventListener('mousemove', update);
}, []);
return pos;
};`,
type: 'mouse'
},
'glass': {
title: 'CSS 极致毛玻璃效果',
code: `.glass-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
border-radius: 16px;
}`,
type: 'glass'
},
'noise': {
title: 'SVG 噪点纹理滤镜',
code: `<filter id="noise">
<feTurbulence
type="fractalNoise"
baseFrequency="0.8"
numOctaves="3"
stitchTiles="stitch"
/>
<feColorMatrix type="saturate" values="0" />
</filter>
<rect width="100%" height="100%" filter="url(#noise)" opacity="0.4"/>`,
type: 'noise'
}
};
function openSnippet(id) {
const modal = document.getElementById('snippet-modal');
const content = document.getElementById('modal-content');
const data = snippetsData[id];
document.getElementById('modal-title').innerText = data.title;
document.getElementById('modal-code').innerText = data.code;
renderPreview(data.type);
modal.classList.remove('hidden');
setTimeout(() => {
modal.classList.remove('opacity-0');
content.classList.remove('scale-95');
content.classList.add('scale-100');
}, 10);
document.body.style.overflow = 'hidden';
}
function closeSnippet() {
const modal = document.getElementById('snippet-modal');
const content = document.getElementById('modal-content');
modal.classList.add('opacity-0');
content.classList.remove('scale-100');
content.classList.add('scale-95');
setTimeout(() => {
modal.classList.add('hidden');
document.getElementById('modal-preview-container').innerHTML = '';
}, 300);
document.body.style.overflow = '';
}
function renderPreview(type) {
const container = document.getElementById('modal-preview-container');
container.innerHTML = '';
if (type === 'mouse') {
const box = document.createElement('div');
box.className = "flex flex-col items-center justify-center p-8 border border-white/20 rounded-xl bg-black/50 backdrop-blur text-white transition-all duration-75";
box.innerHTML = `<div class="text-4xl font-mono mb-2" id="demo-xy">0, 0</div><div class="text-xs text-white/50">在此区域移动鼠标</div>`;
container.appendChild(box);
container.onmousemove = (e) => {
const rect = container.getBoundingClientRect();
const x = Math.floor(e.clientX - rect.left);
const y = Math.floor(e.clientY - rect.top);
document.getElementById('demo-xy').innerText = `${x}, ${y}`;
box.style.transform = `translate(${(x - rect.width/2)/20}px, ${(y - rect.height/2)/20}px)`;
};
} else if (type === 'glass') {
const wrapper = document.createElement('div');
wrapper.className = "relative w-64 h-64 flex items-center justify-center";
const c1 = document.createElement('div');
c1.className = "absolute top-0 left-0 w-32 h-32 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-float";
const c2 = document.createElement('div');
c2.className = "absolute bottom-0 right-0 w-32 h-32 bg-yellow-500 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-float";
c2.style.animationDelay = "2s";
const card = document.createElement('div');
card.style = "background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(255, 255, 255, 0.2); box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); border-radius: 16px;";
card.className = "relative z-10 w-48 h-32 flex items-center justify-center text-white font-bold text-lg";
card.innerText = "Glass Card";
wrapper.appendChild(c1);
wrapper.appendChild(c2);
wrapper.appendChild(card);
container.appendChild(wrapper);
container.onmousemove = null;
} else if (type === 'noise') {
const box = document.createElement('div');
box.className = "w-64 h-64 bg-[#222] rounded-xl relative overflow-hidden flex items-center justify-center border border-white/10";
const noiseOverlay = document.createElement('div');
noiseOverlay.className = "absolute inset-0 opacity-50";
noiseOverlay.style.backgroundImage = `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='1'/%3E%3C/svg%3E")`;
const text = document.createElement('div');
text.className = "relative z-10 text-white font-bold text-2xl tracking-widest";
text.innerText = "NOISE";
box.appendChild(noiseOverlay);
box.appendChild(text);
container.appendChild(box);
container.onmousemove = null;
}
}
// NEW: FAQ Toggle Logic
function toggleFaq(btn) {
const container = btn.parentElement;
const content = btn.nextElementSibling;
// Close others (Optional - Accordion style)
document.querySelectorAll('.group.is-open').forEach(el => {
if(el !== container) {
el.classList.remove('is-open');
el.querySelector('div.h-0').style.height = '0px';
}
});
if (container.classList.contains('is-open')) {
container.classList.remove('is-open');
content.style.height = '0px';
} else {
container.classList.add('is-open');
content.style.height = content.scrollHeight + 'px';
}
}
// Spotlight Effect Logic
const servicesGrid = document.getElementById('services-grid');
if(servicesGrid) {
servicesGrid.onmousemove = (e) => {
for(const card of document.getElementsByClassName('service-card')) {
// Check if card has the class 'service-card' (not tilt-card, which uses different logic)
// The tilt-cards use internal event listeners, but if we had standard cards:
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
card.style.setProperty('--mouse-x', `${x}px`);
card.style.setProperty('--mouse-y', `${y}px`);
}
};
}
// Inquiry Logic
function openInquiry() {
document.getElementById('inquiry-modal').classList.remove('hidden');
document.getElementById('inquiry-modal').classList.add('flex');
}
// Fingerprint Generation
function getFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const txt = 'GeekSpace-Fingerprint-v1';
ctx.textBaseline = "top";
ctx.font = "14px 'Arial'";
ctx.textBaseline = "alphabetic";
ctx.fillStyle = "#f60";
ctx.fillRect(125,1,62,20);
ctx.fillStyle = "#069";
ctx.fillText(txt, 2, 15);
ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
ctx.fillText(txt, 4, 17);
const b64 = canvas.toDataURL().replace("data:image/png;base64,", "");
const bin = atob(b64);
let crc = -1;
for(let i=0; i<bin.length; i++) {
crc = (crc >>> 8) ^ crcTable[(crc ^ bin.charCodeAt(i)) & 0xFF];
}
return (crc >>> 0).toString(16);
}
const crcTable = (function(){
let c;
let table = [];
for(let n =0; n < 256; n++){
c = n;
for(let k =0; k < 8; k++){
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
}
table[n] = c;
}
return table;
})();
// Form Submit
document.getElementById('inquiry-form').addEventListener('submit', async (e) => {
e.preventDefault();
// Custom Validation (Artistic)
let isValid = true;
// Clear previous errors
document.querySelectorAll('.error-msg').forEach(el => el.remove());
document.querySelectorAll('.input-error').forEach(el => el.classList.remove('input-error'));
document.querySelectorAll('.error-shake').forEach(el => el.classList.remove('error-shake'));
// Check required fields manually (since we remove HTML validation for custom feel)
const requiredFields = ['name', 'contact'];
const form = e.target;
const fieldNames = {
'name': '称呼',
'contact': '联系方式'
};
requiredFields.forEach(field => {
const input = form.querySelector(`[name="${field}"]`);
if(!input.value.trim()) {
isValid = false;
// Add Artistic Error State
input.classList.add('input-error');
// Add shake class to the parent .group container for better visual
input.closest('.group\\/input').classList.add('error-shake');
// Create Geeky Error Message
const errorMsg = document.createElement('span');
errorMsg.className = 'error-msg';
errorMsg.innerText = `// 错误: 请输入您的${fieldNames[field]}`; // Chinese Error
input.closest('.group\\/input').appendChild(errorMsg);
}
});
if(!isValid) return;
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
data.fingerprint = getFingerprint();
try {
// Assuming backend is running on 8080. If purely static, this will fail gracefully.
const res = await fetch('http://localhost:8080/api/inquiry', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if(res.ok) {
alert('提交成功!我会尽快与您联系。');
document.getElementById('inquiry-modal').classList.add('hidden');
e.target.reset();
} else {
// Fallback for static demo
console.log('Backend not reachable, but form valid. Data:', data);
alert('提交成功!(演示模式)');
document.getElementById('inquiry-modal').classList.add('hidden');
}
} catch(err) {
console.log('Submission Data:', data);
alert('提交成功!(演示模式 - 后端未连接)');
document.getElementById('inquiry-modal').classList.add('hidden');
}
});
router('home');
</script>
</body>
</html>