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

875 lines
63 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'
}
},
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%)' }
},
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>
/* Custom Scrollbar */
::-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; }
.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 & Utils */
.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%; }
/* Modal & Toast */
.modal-overlay { background: rgba(0, 0, 0, 0.9); backdrop-filter: blur(10px); }
.toast-container { position: fixed; bottom: 2rem; right: 2rem; z-index: 100; display: flex; flex-direction: column; gap: 0.5rem; pointer-events: none; }
.toast { background: rgba(20, 20, 23, 0.9); border: 1px solid rgba(255,255,255,0.1); backdrop-filter: blur(12px); padding: 1rem 1.5rem; border-radius: 0.5rem; color: white; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5); display: flex; align-items: center; gap: 0.75rem; transform: translateX(100%); transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); pointer-events: auto; min-width: 300px; }
.toast.show { transform: translateX(0); }
.toast-success { border-left: 3px solid #d4b383; }
.toast-error { border-left: 3px solid #ef4444; }
/* Form Error */
.input-error { border-bottom-color: #ef4444 !important; }
.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; } }
/* Code Block */
.mac-code-block { background: #1e1e1e; padding: 3rem 1.5rem 1.5rem 1.5rem; border-radius: 0.75rem; overflow-x: auto; margin-bottom: 0; border: 1px solid rgba(255,255,255,0.1); position: relative; box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5); }
.mac-code-block::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; }
.code-keyword { color: #c678dd; } .code-func { color: #61afef; } .code-string { color: #98c379; } .code-comment { color: #5c6370; font-style: italic; }
/* Layout Specifics */
.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%); }
.menu-open { transform: translateY(0); opacity: 1; pointer-events: auto; }
.menu-closed { transform: translateY(-20px); opacity: 0; pointer-events: none; }
/* Tilt & 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); }
/* 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); }
</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>
<!-- Toast Container -->
<div id="toast-container" class="toast-container"></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="window.open('admin.html', '_blank')" 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">
<!-- Hero -->
<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 id="home-hero-work" 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 cursor-pointer">
<div class="text-white/50 text-xs">加载中...</div>
</div>
</div>
</div>
</div>
<!-- Bento Grid -->
<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]" id="bento-grid">
<div class="md:col-span-3 h-[300px] border border-white/5 rounded-xl bg-white/5 animate-pulse"></div>
</div>
</div>
</section>
<!-- Dynamic Content Sections -->
<section id="blog" class="page-section hidden animate-slide-down">
<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" id="blog-list-container"></div>
</div>
</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" id="works-list-container"></div>
</section>
<!-- Work Detail -->
<section id="work-detail" class="work-detail-page page-section hidden">
<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>
<div class="fixed top-0 left-0 h-1 bg-art-accent z-[60] w-0 transition-all duration-100" id="scroll-progress"></div>
<div class="relative w-full" id="work-detail-content">
<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="">
<div class="absolute inset-0 bg-black/40"></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>
<div class="flex flex-col md:flex-row max-w-[1920px] mx-auto min-h-screen">
<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"></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>
<div class="md:w-2/3 bg-[#080808] flex flex-col">
<div id="work-gallery" class="flex flex-col flex-grow"></div>
<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 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 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" id="snippets-grid">
<!-- JS Injected -->
</div>
</section>
<!-- Services (About/Contact) -->
<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>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-32 px-4" id="services-grid">
<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">为复杂的大型应用提供可扩展的架构方案。</p></div></div></div>
<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></div>
<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></div>
</div>
<!-- Danmaku & Partners -->
<div class="py-20 mb-32 overflow-hidden relative">
<div class="absolute left-0 top-0 w-20 h-full bg-gradient-to-r from-art-bg to-transparent z-10"></div>
<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" id="danmaku-container">
<!-- JS Injected -->
<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 体验!"</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-blue-500/20"></div><span>"从未见过如此丝滑的 WebGL 体验!"</span></div>
</div>
</div>
</div>
<!-- CTA -->
<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>
<!-- Partners -->
<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">
<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>
</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>
<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"><img src="https://api.dicebear.com/7.x/avataaars/svg?seed=NianGao&backgroundColor=b6e3f4" alt="Avatar" 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>,一名热衷于探索数字边界的前端架构师。我的职业生涯始于对像素级完美的执念。目前,我专注于高性能 B 端应用的体验升级。</p>
</div>
<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>
</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</span><span class="px-3 py-1 bg-white/5 rounded-full text-xs text-white/80 border border-white/10">React</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">Golang</span></div>
</div>
</div>
</div>
</div>
</section>
</main>
<footer class="py-8 text-center border-t border-white/5 relative z-10">
<div class="flex justify-center items-center gap-4 mb-4"><a onclick="window.open('admin.html', '_blank')" class="text-xs text-art-muted/30 hover:text-art-accent cursor-pointer">管理入口</a></div>
<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">Code</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="showToast('代码已复制')">复制</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">Loading Preview...</div></div>
</div>
</div>
</div>
<!-- Inquiry Modal -->
<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">
<div class="hidden md:flex w-1/3 bg-[#0c0c0c] border-r border-white/5 p-12 flex-col justify-between relative overflow-hidden">
<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>
<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">
<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">
<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>
<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>
<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>
<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>
<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>
<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>
<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();
const API_BASE = "http://localhost:8080/api";
// Global State for Data
const store = {
works: [],
posts: [],
snippets: [],
testimonials: []
};
let currentWorkId = null;
// Static fallback data for reliability
const workProjects = {
'nova': {
id: 'nova',
title: "Nova 交易平台",
category: "金融科技",
year: "2023",
heroImg: "https://images.unsplash.com/photo-1611974765270-ca12586343bb?q=80&w=2070",
desc: `<p>Nova 是一个专为机构交易员设计的高频交易终端。我们面临的最大挑战是如何在处理毫秒级市场数据的同时,保持界面的流畅响应。我们采用 <strong>Web Worker</strong> 来处理繁重的数据计算,避免阻塞主线程。</p>`,
techStack: [{ category: '前端层', items: ['React 18', 'TypeScript', 'D3.js'] }, { category: '后端服务', items: ['Golang', 'gRPC'] }],
gallery: ["https://images.unsplash.com/photo-1642543492481-44e81e3914a7?q=80&w=2070", "https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070"],
links: {live: "#"}, next: 'archdaily'
},
'archdaily': {
id: 'archdaily',
title: "ArchDaily 网站重构",
category: "建筑设计",
year: "2022",
heroImg: "https://images.unsplash.com/photo-1487958449943-2429e8be8625?q=80&w=2070",
desc: `<p>ArchDaily 是全球最受欢迎的建筑网站之一。这次重构的目标是提升移动端体验。我们使用了 <strong>Nuxt 3</strong> 进行服务端渲染SSR。</p>`,
techStack: [{ category: '核心前端', items: ['Vue 3', 'Nuxt.js', 'GSAP'] }, { category: 'CMS', items: ['Strapi'] }],
gallery: ["https://images.unsplash.com/photo-1503387762-592deb58ef4e?q=80&w=2089", "https://images.unsplash.com/photo-1518005020951-ecc859466abc?q=80&w=1920"],
links: {live: "#"}, next: 'nova'
}
};
const snippetsData = {
'mouse': { title: 'React 鼠标追踪 Hook', code: `import { useState, useEffect } from 'react';\n\nexport const useMousePosition = () => {\n const [pos, setPos] = useState({ x: 0, y: 0 });\n useEffect(() => {\n const update = (e) => setPos({ x: e.clientX, y: e.clientY });\n window.addEventListener('mousemove', update);\n return () => window.removeEventListener('mousemove', update);\n }, []);\n return pos;\n};`, type: 'mouse' },
'glass': { title: 'CSS 极致毛玻璃效果', code: `.glass-panel {\n background: rgba(255, 255, 255, 0.1);\n backdrop-filter: blur(16px);\n box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);\n}`, type: 'glass' },
'noise': { title: 'SVG 噪点纹理滤镜', code: `<filter id="noise">\n <feTurbulence type="fractalNoise" baseFrequency="0.8" />\n</filter>`, type: 'noise' }
};
// --- Core UI Functions ---
function showToast(message, type = 'success') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type === 'success' ? 'toast-success' : 'toast-error'}`;
toast.innerHTML = `<i data-lucide="${type === 'success' ? 'check-circle' : 'alert-circle'}" class="w-5 h-5 ${type === 'success' ? 'text-[#d4b383]' : 'text-red-500'}"></i><span class="text-sm font-medium">${message}</span>`;
container.appendChild(toast);
lucide.createIcons();
requestAnimationFrame(() => toast.classList.add('show'));
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 400);
}, 3000);
}
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');
}
});
document.querySelectorAll('.page-section').forEach(section => {
if (section.id === targetId) {
section.classList.remove('hidden');
section.classList.remove('animate-slide-down');
void section.offsetWidth;
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' });
}
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(); }
// --- Data & Rendering ---
async function init() {
try {
const [worksRes, postsRes, snippetsRes] = await Promise.all([
fetch(`${API_BASE}/works`).catch(()=>({ok:false})),
fetch(`${API_BASE}/posts`).catch(()=>({ok:false})),
fetch(`${API_BASE}/snippets`).catch(()=>({ok:false}))
]);
// Use static fallback if API fails or returns empty
store.works = (worksRes.ok && (await worksRes.json()).length > 0) ? await worksRes.json() : Object.values(workProjects);
store.posts = (postsRes.ok && (await postsRes.json()).length > 0) ? await postsRes.json() : [
{id:'refactor', title:"重构的艺术:如何优雅地处理遗留代码", category:"工程化", date:"2026-01-12"},
{id:'shader', title:"着色器魔法:从零开始写一个噪声生成器", category:"图形渲染", date:"2025-12-08"}
];
store.snippets = snippetsRes.ok ? await snippetsRes.json() : [];
renderBento();
renderWorksList();
renderBlogList();
} catch (e) {
console.warn("Init fallback");
store.works = Object.values(workProjects);
renderBento();
renderWorksList();
}
}
function renderBento() {
const heroWork = store.works[0];
if(heroWork) {
const heroEl = document.getElementById('home-hero-work');
heroEl.onclick = () => openWork(heroWork.id);
heroEl.innerHTML = `
<div class="text-xs font-mono text-white/50">最新作品</div>
<div class="font-serif text-3xl italic text-white">${heroWork.title.replace(' ', '<br>')}</div>
<div class="flex justify-end"><i data-lucide="arrow-up-right" class="text-white"></i></div>
`;
}
const post = store.posts[0] || {title: "当极简主义遇见复杂数据", category: "设计思维", date: "2025-10-24"};
const gridHTML = `
<div class="md:col-span-2 art-card rounded-2xl p-8 flex flex-col justify-end group cursor-pointer" onclick="openBlog('${post.id || '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')] 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">${post.category}</span></div>
<h3 class="font-serif text-3xl text-white group-hover:text-art-accent transition-colors">${post.title}</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">开源提交</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</div>
<div class="font-mono text-sm text-gray-400 overflow-hidden opacity-60 group-hover:opacity-100 transition-opacity">useMousePosition...</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>
`;
document.getElementById('bento-grid').innerHTML = gridHTML;
lucide.createIcons();
}
function renderWorksList() {
const container = document.getElementById('works-list-container');
container.innerHTML = store.works.map((work, index) => {
const isEven = index % 2 === 0;
const imgOrder = isEven ? '' : 'md:order-2';
const textOrder = isEven ? '' : 'md:order-1 md:text-right';
const btnAlign = isEven ? '' : 'md:justify-end';
return `
<div class="group grid grid-cols-1 md:grid-cols-2 gap-10 items-center cursor-pointer" onclick="openWork('${work.id}')">
<div class="${imgOrder} 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('${work.heroImg}')] bg-cover bg-center group-hover:scale-105 transition-transform duration-700 opacity-80"></div>
</div>
<div class="space-y-6 ${textOrder}">
<div class="font-mono text-xs text-art-accent">${work.year}${work.category}</div>
<h3 class="font-serif text-4xl text-white group-hover:text-art-accent transition-colors">${work.title}</h3>
<p class="text-art-muted font-light leading-relaxed line-clamp-3">${work.desc ? work.desc.replace(/<[^>]*>?/gm, '') : ''}</p>
<div class="pt-4 flex ${btnAlign}"><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>
`}).join('');
lucide.createIcons();
}
function renderBlogList() {
const container = document.getElementById('blog-list-container');
// Using store.posts which is populated in init
container.innerHTML = store.posts.map(post => `
<article class="group cursor-pointer border-b border-white/5 pb-12" onclick="openBlog('${post.id}')">
<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">${post.category}</span>
<span class="text-sm text-art-muted">${post.date || '2025-01-01'}</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">${post.title}</h3>
<div class="text-xs text-art-accent font-medium mt-4 group-hover:text-white transition-colors">阅读全文 -></div>
</div>
</div>
</article>
`).join('');
lucide.createIcons();
}
function renderSnippetsList() {
const container = document.getElementById('snippets-grid');
const data = Object.keys(snippetsData).map(k => snippetsData[k]);
container.innerHTML = data.map(s => `
<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('${s.type}')">
<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-2.5 h-2.5 rounded-full bg-red-500/50"></div><div class="w-2.5 h-2.5 rounded-full bg-yellow-500/50"></div><div class="w-2.5 h-2.5 rounded-full bg-green-500/50"></div></div>
<span class="text-xs font-mono text-art-muted">${s.title}</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 text-gray-500 line-clamp-3">${s.code}</div>
</div>
`).join('');
lucide.createIcons();
}
// --- Detail Actions ---
function openWork(id) {
let work = store.works.find(w => w.id == id);
if(!work) return; // Should not happen with valid ID
currentWorkId = id;
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;
document.getElementById('work-link-live').href = work.links.live;
// Tech Stack
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
const galleryContainer = document.getElementById('work-gallery');
galleryContainer.innerHTML = work.gallery.map((img, index) => {
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">`;
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"></div>`;
}).join('');
router('work-detail');
}
function openBlog(id) {
router('blog-detail');
document.getElementById('post-content').innerHTML = `<h2>文章内容加载中...</h2><p class="text-art-muted">(这是一个演示版面,真实内容需连接数据库)</p>`;
}
function openNextWork() {
// Simple next logic based on array index
const idx = store.works.findIndex(w => w.id === currentWorkId);
const nextIdx = (idx + 1) % store.works.length;
openWork(store.works[nextIdx].id);
}
function openSnippet(id) {
const modal = document.getElementById('snippet-modal');
const data = snippetsData[id];
if(!data) return;
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'); document.getElementById('modal-content').classList.remove('scale-95'); document.getElementById('modal-content').classList.add('scale-100'); }, 10);
document.body.style.overflow = 'hidden';
}
function closeSnippet() { const modal = document.getElementById('snippet-modal'); modal.classList.add('opacity-0'); setTimeout(() => { modal.classList.add('hidden'); }, 300); document.body.style.overflow = ''; }
function renderPreview(type) {
const container = document.getElementById('modal-preview-container');
container.innerHTML = '';
// (Reusing visual logic from prev step)
const box = document.createElement('div');
if(type === 'mouse') {
box.className = "flex flex-col items-center justify-center p-8 border border-white/20 rounded-xl bg-black/50 backdrop-blur text-white";
box.innerHTML = `<div class="text-4xl font-mono mb-2" id="demo-xy">0, 0</div>`;
container.onmousemove = (e) => {
const rect = container.getBoundingClientRect();
document.getElementById('demo-xy').innerText = `${Math.floor(e.clientX - rect.left)}, ${Math.floor(e.clientY - rect.top)}`;
};
} else {
box.className = "w-40 h-40 bg-white/10 rounded-full flex items-center justify-center border border-white/20 backdrop-blur-md";
box.innerHTML = "Preview";
container.onmousemove = null;
}
container.appendChild(box);
}
function openInquiry() { document.getElementById('inquiry-modal').classList.remove('hidden'); document.getElementById('inquiry-modal').classList.add('flex'); }
function getFingerprint() { return "FP-" + Math.random().toString(36).substr(2, 9).toUpperCase(); }
document.getElementById('inquiry-form').addEventListener('submit', async (e) => {
e.preventDefault();
let isValid = true;
document.querySelectorAll('.error-msg').forEach(el => el.remove());
document.querySelectorAll('.input-error').forEach(el => el.classList.remove('input-error'));
const fieldNames = { 'name': '称呼', 'contact': '联系方式' };
['name', 'contact'].forEach(field => {
const input = e.target.querySelector(`[name="${field}"]`);
if(!input.value.trim()) {
isValid = false;
input.classList.add('input-error');
const errorMsg = document.createElement('span');
errorMsg.className = 'error-msg';
errorMsg.innerText = `// 错误: 请输入您的${fieldNames[field]}`;
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 {
const res = await fetch(`${API_BASE}/inquiry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if(res.ok) {
showToast('咨询已提交,我会尽快联系您');
document.getElementById('inquiry-modal').classList.add('hidden');
e.target.reset();
} else {
showToast('提交失败 (后端未连接)', 'error');
}
} catch(err) {
showToast('网络错误 (演示模式)', 'error');
}
});
// Spotlight
const servicesGrid = document.getElementById('services-grid');
if(servicesGrid) {
servicesGrid.onmousemove = (e) => {
for(const card of document.getElementsByClassName('service-card')) {
// Logic from CSS variable binding if needed, but tilt-card handles itself
}
};
}
// New FAQ Toggle Logic
function toggleFaq(btn) {
const container = btn.parentElement;
const content = btn.nextElementSibling;
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';
}
}
init();
</script>
</body>
</html>