const { useState, useRef, useEffect, useCallback } = React; // ========== 自定义 Hook:拖尾 + 惯性滑动揭示 ========== // 所有层图片尺寸均为 100%,拖尾大小差异仅通过 mask-size 和 opacity 实现 function useTrailReveal(sectionRef, layerRefs, options = {}) { const { baseSize = 136, // 主层 mask 基础直径(px) baseSizeX = null, // 水平方向 mask 尺寸(覆盖 baseSize) baseSizeY = null, // 垂直方向 mask 尺寸(覆盖 baseSize) moveBoost = 28, // 移动时额外放大(px) breathAmount = 4, // 呼吸脉动幅度(px) friction = 0.93, // 惯性摩擦系数 stillThreshold = 80, // 停止移动判定阈值(ms) activeClass = 'is-revealing', // 激活时添加到 section 的类名 trailLayers = [ { lerp: 0.15, opacity: 1.00, maskScale: 1.00 }, { lerp: 0.10, opacity: 0.72, maskScale: 0.92 }, { lerp: 0.07, opacity: 0.50, maskScale: 0.82 }, { lerp: 0.05, opacity: 0.32, maskScale: 0.72 }, { lerp: 0.035, opacity: 0.18, maskScale: 0.62 }, { lerp: 0.02, opacity: 0.08, maskScale: 0.52 }, ], } = options; const targetPosRef = useRef({ x: 50, y: 50 }); const posRef = useRef({ x: 50, y: 50 }); const velRef = useRef({ x: 0, y: 0 }); const lastMovePosRef = useRef({ x: 50, y: 50 }); const lastMoveTimeRef = useRef(0); const isMovingRef = useRef(false); const isHoveringRef = useRef(false); const animFrameRef = useRef(null); const startTimeRef = useRef(performance.now()); const setActive = useCallback((active) => { const section = sectionRef.current; if (!section) return; if (active) { section.classList.add(activeClass); } else { section.classList.remove(activeClass); } }, [sectionRef, activeClass]); const updatePosition = useCallback((clientX, clientY) => { const section = sectionRef.current; if (!section) return; const rect = section.getBoundingClientRect(); const mx = ((clientX - rect.left) / rect.width) * 100; const my = ((clientY - rect.top) / rect.height) * 100; const now = performance.now(); const dt = Math.max(now - lastMoveTimeRef.current, 1); velRef.current.x = ((mx - lastMovePosRef.current.x) / dt) * 16.67; velRef.current.y = ((my - lastMovePosRef.current.y) / dt) * 16.67; targetPosRef.current.x = mx; targetPosRef.current.y = my; lastMovePosRef.current.x = mx; lastMovePosRef.current.y = my; lastMoveTimeRef.current = now; isMovingRef.current = true; }, [sectionRef]); const handleMouseMove = useCallback((e) => { updatePosition(e.clientX, e.clientY); }, [updatePosition]); const handleMouseEnter = useCallback(() => { isHoveringRef.current = true; setActive(true); }, [setActive]); const handleMouseLeave = useCallback(() => { isHoveringRef.current = false; velRef.current.x = 0; velRef.current.y = 0; setActive(false); }, [setActive]); // 触摸事件处理 const handleTouchStart = useCallback((e) => { if (!e.touches || e.touches.length === 0) return; const touch = e.touches[0]; isHoveringRef.current = true; setActive(true); updatePosition(touch.clientX, touch.clientY); }, [setActive, updatePosition]); const handleTouchMove = useCallback((e) => { if (!e.touches || e.touches.length === 0) return; const touch = e.touches[0]; updatePosition(touch.clientX, touch.clientY); }, [updatePosition]); const handleTouchEnd = useCallback(() => { isHoveringRef.current = false; velRef.current.x = 0; velRef.current.y = 0; setActive(false); }, [setActive]); useEffect(() => { const layers = layerRefs.current; if (!layers || layers.length === 0) return; const animate = () => { const now = performance.now(); const elapsed = now - startTimeRef.current; if (now - lastMoveTimeRef.current > stillThreshold) { isMovingRef.current = false; } if (isMovingRef.current) { posRef.current.x += (targetPosRef.current.x - posRef.current.x) * trailLayers[0].lerp; posRef.current.y += (targetPosRef.current.y - posRef.current.y) * trailLayers[0].lerp; } else { posRef.current.x += velRef.current.x; posRef.current.y += velRef.current.y; velRef.current.x *= friction; velRef.current.y *= friction; if (Math.abs(velRef.current.x) < 0.005) velRef.current.x = 0; if (Math.abs(velRef.current.y) < 0.005) velRef.current.y = 0; } posRef.current.x = Math.max(-15, Math.min(115, posRef.current.x)); posRef.current.y = Math.max(-15, Math.min(115, posRef.current.y)); const timeSinceMove = now - lastMoveTimeRef.current; const speedMag = Math.sqrt( velRef.current.x * velRef.current.x + velRef.current.y * velRef.current.y ); const boostAmount = Math.min(speedMag * 2.5, moveBoost); const boost = isHoveringRef.current && timeSinceMove < 250 ? boostAmount + (moveBoost - boostAmount) * (1 - timeSinceMove / 250) : isHoveringRef.current ? boostAmount : 0; const breath = Math.sin(elapsed * 0.0015) * breathAmount; let prevPos = { x: posRef.current.x, y: posRef.current.y }; trailLayers.forEach((layer, i) => { const el = layers[i]; if (!el) return; let cx, cy; if (i === 0) { cx = posRef.current.x; cy = posRef.current.y; } else { let cachedX = parseFloat(el.dataset.trailX || '50'); let cachedY = parseFloat(el.dataset.trailY || '50'); cachedX += (prevPos.x - cachedX) * layer.lerp * 1.6; cachedY += (prevPos.y - cachedY) * layer.lerp * 1.6; el.dataset.trailX = cachedX; el.dataset.trailY = cachedY; cx = cachedX; cy = cachedY; prevPos = { x: cx, y: cy }; } el.style.setProperty('--mouse-x', cx + '%'); el.style.setProperty('--mouse-y', cy + '%'); const baseX = baseSizeX !== null ? baseSizeX : baseSize; const baseY = baseSizeY !== null ? baseSizeY : baseSize; const maskPxX = (baseX + boost + breath) * layer.maskScale; const maskPxY = (baseY + boost + breath) * layer.maskScale; el.style.setProperty('--mask-size-x', maskPxX + 'px'); el.style.setProperty('--mask-size-y', maskPxY + 'px'); // 兼容旧变量 el.style.setProperty('--mask-size', maskPxX + 'px'); el.style.opacity = layer.opacity; }); animFrameRef.current = requestAnimationFrame(animate); }; animFrameRef.current = requestAnimationFrame(animate); return () => { if (animFrameRef.current) { cancelAnimationFrame(animFrameRef.current); } }; }, [sectionRef, layerRefs, baseSize, baseSizeX, baseSizeY, moveBoost, breathAmount, friction, stillThreshold, trailLayers]); return { handleMouseMove, handleMouseEnter, handleMouseLeave, handleTouchStart, handleTouchMove, handleTouchEnd, }; } // ========== 作品数据 ========== const works = [ { id: 1, slug: 'ocean-spirit', title: 'Senlen X 古典香护手霜', category: '诗人生活', categoryEn: 'Branding / Hand Cream', desc: '东方古典香氛植萃护手霜,复古植物徽章视觉与温润古典色调,呈现永恒之香的品牌气质。', img: 'assets/images/byte_3d7f3f0b00c44fec9b40f87092c43b04.png', year: '2024', bpearl: true }, { id: 2, slug: 'guangdong-huamei', title: '三一重工全球礼品 X SENLAN', category: '佳节好礼', categoryEn: 'Packaging / Brand Identity', desc: '三一重工中秋佳节礼品包装设计,烫金深蓝与鲜明色彩交织,承载企业文化与节日温情。', img: 'assets/images/byte_6671e0779a9a4ed0bddc60827fc1d391.png', year: '2024', huamei: true }, { id: 8, slug: 'sany-midautumn', title: '广东华美 X SENLAN', category: '佳节好礼', categoryEn: 'Packaging / huameigroup', desc: '三一重工中秋佳节礼品包装设计,烫金深蓝与鲜明色彩交织,承载企业文化与节日温情。', img: 'assets/images/byte_8512d7e5944a49c7b0184712e19652ff.png', year: '2024', sany: true }, { id: 3, slug: 'b-luxury', title: 'B Luxury · 永恒优雅', category: '佳节好礼', categoryEn: 'FESTIVE GIFT', desc: '高端美妆礼盒 · Timeless Elegance', img: 'assets/images/aka_sNOJKIRmyT.jpg', year: '2026', bLuxury: true }, { id: 4, slug: 'ocean-heart', title: '海洋之心 X fairy', category: '摩登女神', categoryEn: 'Heart of the Ocean × Fairy', desc: '北欧风格家居品牌视觉重塑,温暖克制的几何语言定义产品气质。', img: 'assets/images/byte_26e77ded5d1d457c950d3ed9b1cd9ee0.png', year: '2023', oceanHeart: true }, { id: 5, slug: 'archive-07', title: 'Lonely City X 孤城香氛', category: '诗人生活', categoryEn: 'Oriental Fragrance', desc: '东方意象香水品牌视觉,孤城意象与宋式美学诠释孤独与诗意。', img: 'assets/images/byte_8a3cff9006c64c0ebbfb8712c37a8658.png', year: '2023', archive07: true }, { id: 6, slug: 'b-pearl-pink', title: 'B X Pearl pink 珍脂粉', category: '摩登女神', categoryEn: 'Pearl pink', desc: '东方珍珠美学品牌视觉,胭脂粉主色调与古典植物插画,传递温润优雅的品牌气质。', img: 'assets/images/byte_af56e7e6adc84850a93c44edde5cea86.png', year: '2024', bpearlPink: true }, { id: 7, slug: 'meridian-journal', title: 'EDX - 联名款', category: '素人潮品', categoryEn: '电子手表戒指 - EDX', desc: '独立生活方式杂志的整体视觉体系,大胆排版与克制色彩的平衡。', img: 'assets/images/byte_d6139d9cd5f64f13a3e9354189f9b68b.png', year: '2022', meridianJournal: true }, { id: 9, slug: 'qingzhao-li-xin', title: 'LI HeartQ· 清照 LI 心', category: '诗人生活', categoryEn: 'LI HeartQ', desc: '占位:清照 LI 心品牌设计作品,待补充详细描述。', img: 'assets/images/byte_2280ab1061094945ad469a7bd192657c.jpg', year: '2024', qingzhao: true }, { id: 10, slug: 'senxing-aroma-lamp', title: 'Senxing森星', category: '素人潮品', categoryEn: 'Branding', desc: '占位:Senxing森星香薰电子灯品牌设计作品,待补充详细描述。', img: 'assets/images/byte_c8fd9959882c42658956c8564a0fa23c.png', year: '2024', senxing: true }, { id: 11, slug: 'amber-spot', title: 'AMBER SPOT · 琥珀斑点', category: '摩登女神', categoryEn: 'MODERN GODDESS', desc: 'EAU DE PARFUM · 俏皮的优雅', img: 'assets/images/aka_Udq2MKQ48i.jpg', year: '2026', amberSpot: true }, ]; const categories = ['摩登女神', '佳节好礼', '诗人生活', '素人潮品']; // ========== 服务数据 ========== const services = [ { title: '策略产品', subtitle: 'Branding', desc: '时尚必须紧跟时代,否则就会消逝;就像优雅永不逝去,但她改头换面时,应该认出来,不然就出局了。', }, { title: '品牌升维', subtitle: 'Packaging', desc: '风格是什么?风格始于打破常规,击碎大家默认的现实。', }, { title: '美学消费', subtitle: 'Art Direction', desc: '永远不要使用 "便宜没好货" 这个词,穿着平价衣服也可以很时髦,富人也会买。', }, { title: '视觉价值', subtitle: 'Editorial', desc: '传达表现符合当代文化和当下消费者群体精神画像,表达共鸣的品牌情绪。', }, ]; // ========== 品牌签名 ========== const SIGNATURE_URL = 'assets/senlan-signature-outline.png'; // ========== 导航栏 ========== function Navbar({ onHomeClick }) { const handleLogoClick = (e) => { if (onHomeClick) { e.preventDefault(); onHomeClick(); } }; return ( ); } // ========== Hero 首屏 ========== function Hero() { return (
Independent Brand Designer & Creative Director

极致美&共鸣情 品牌资产最深护城河

/Aesthetics of Restraint

); } // ========== 动态作品展示窗口 ========== // ========== 动态作品展示(Ken Burns 电影感轮播) ========== const showcaseProjects = [ { id: 1, slug: 'user-pick-01', category: 'User Pick', title: '01', subtitle: 'User Selected 01', bg: 'assets/images/aka_t2e6hFBqcP.jpg', cardImg: 'assets/images/aka_t2e6hFBqcP.jpg', titleColor: '#fff' }, { id: 2, slug: 'user-pick-02', category: 'User Pick', title: '02', subtitle: 'User Selected 02', bg: 'assets/images/aka_CSIKMHo8LH.jpg', cardImg: 'assets/images/aka_CSIKMHo8LH.jpg', titleColor: '#fff' }, { id: 3, slug: 'user-pick-03', category: 'User Pick', title: '03', subtitle: 'User Selected 03', bg: 'assets/images/aka_Wtyofv2jJJ.jpg', cardImg: 'assets/images/aka_Wtyofv2jJJ.jpg', titleColor: '#fff' }, { id: 4, slug: 'user-pick-04', category: 'User Pick', title: '04', subtitle: 'User Selected 04', bg: 'assets/images/aka_W5UcYj0MM7.jpg', cardImg: 'assets/images/aka_W5UcYj0MM7.jpg', titleColor: '#fff' }, { id: 5, slug: 'user-pick-05', category: 'User Pick', title: '05', subtitle: 'User Selected 05', bg: 'assets/images/aka_nDApgknsHJ.jpg', cardImg: 'assets/images/aka_nDApgknsHJ.jpg', titleColor: '#fff' }, { id: 6, slug: 'user-pick-06', category: 'User Pick', title: '06', subtitle: 'User Selected 06', bg: 'assets/images/aka_VD7035aJaL.jpg', cardImg: 'assets/images/aka_VD7035aJaL.jpg', titleColor: '#fff' }, { id: 7, slug: 'user-pick-07', category: 'User Pick', title: '07', subtitle: 'User Selected 07', bg: 'assets/images/aka_1p2cynxf41.jpg', cardImg: 'assets/images/aka_1p2cynxf41.jpg', titleColor: '#fff' }, { id: 8, slug: 'user-pick-08', category: 'User Pick', title: '08', subtitle: 'User Selected 08', bg: 'assets/images/aka_9Sh8ZbkGCb.jpg', cardImg: 'assets/images/aka_9Sh8ZbkGCb.jpg', titleColor: '#fff' }, { id: 9, slug: 'user-pick-09', category: 'User Pick', title: '09', subtitle: 'User Selected 09', bg: 'assets/images/aka_FAFqHPkvZL.jpg', cardImg: 'assets/images/aka_FAFqHPkvZL.jpg', titleColor: '#fff' }, { id: 10, slug: 'user-pick-10', category: 'User Pick', title: '10', subtitle: 'User Selected 10', bg: 'assets/images/aka_5VDAoB35j3.jpg', cardImg: 'assets/images/aka_5VDAoB35j3.jpg', titleColor: '#fff' }, { id: 11, slug: 'user-pick-11', category: 'User Pick', title: '11', subtitle: 'User Selected 11', bg: 'assets/images/aka_tn8czKBGVH.jpg', cardImg: 'assets/images/aka_tn8czKBGVH.jpg', titleColor: '#fff' }, { id: 12, slug: 'user-pick-12', category: 'User Pick', title: '12', subtitle: 'User Selected 12', bg: 'assets/images/aka_C3xPJavpQA.jpg', cardImg: 'assets/images/aka_C3xPJavpQA.jpg', titleColor: '#fff' }, { id: 13, slug: 'user-pick-13', category: 'User Pick', title: '13', subtitle: 'User Selected 13', bg: 'assets/images/aka_9mQULVANC2.jpg', cardImg: 'assets/images/aka_9mQULVANC2.jpg', titleColor: '#fff' }, { id: 14, slug: 'user-pick-14', category: 'User Pick', title: '14', subtitle: 'User Selected 14', bg: 'assets/images/aka_K7UH9QxMNT.jpg', cardImg: 'assets/images/aka_K7UH9QxMNT.jpg', titleColor: '#fff' }, { id: 15, slug: 'user-pick-15', category: 'User Pick', title: '15', subtitle: 'User Selected 15', bg: 'assets/images/aka_GaV88kssUy.jpg', cardImg: 'assets/images/aka_GaV88kssUy.jpg', titleColor: '#fff' }, { id: 16, slug: 'user-pick-16', category: 'User Pick', title: '16', subtitle: 'User Selected 16', bg: 'assets/images/aka_ZsoINjYWtH.jpg', cardImg: 'assets/images/aka_ZsoINjYWtH.jpg', titleColor: '#fff' }, { id: 17, slug: 'user-pick-17', category: 'User Pick', title: '17', subtitle: 'User Selected 17', bg: 'assets/images/aka_Bj7woUjsDh.jpg', cardImg: 'assets/images/aka_Bj7woUjsDh.jpg', titleColor: '#fff' }, { id: 18, slug: 'user-pick-18', category: 'User Pick', title: '18', subtitle: 'User Selected 18', bg: 'assets/images/aka_Ii1moRZmRK.jpg', cardImg: 'assets/images/aka_Ii1moRZmRK.jpg', titleColor: '#fff' }, { id: 19, slug: 'user-pick-19', category: 'User Pick', title: '19', subtitle: 'User Selected 19', bg: 'assets/images/aka_0Y9BPPdE66.jpg', cardImg: 'assets/images/aka_0Y9BPPdE66.jpg', titleColor: '#fff' }, { id: 20, slug: 'user-pick-20', category: 'User Pick', title: '20', subtitle: 'User Selected 20', bg: 'assets/images/aka_0Hg220lL0X.jpg', cardImg: 'assets/images/aka_0Hg220lL0X.jpg', titleColor: '#fff' }, { id: 21, slug: 'user-pick-21', category: 'User Pick', title: '21', subtitle: 'User Selected 21', bg: 'assets/images/aka_C8puVV9buL.jpg', cardImg: 'assets/images/aka_C8puVV9buL.jpg', titleColor: '#fff' }, { id: 22, slug: 'user-pick-22', category: 'User Pick', title: '22', subtitle: 'User Selected 22', bg: 'assets/images/aka_FZw2J7oak3.jpg', cardImg: 'assets/images/aka_FZw2J7oak3.jpg', titleColor: '#fff' }, { id: 23, slug: 'user-pick-23', category: 'User Pick', title: '23', subtitle: 'User Selected 23', bg: 'assets/images/aka_UIhApmEKlV.jpg', cardImg: 'assets/images/aka_UIhApmEKlV.jpg', titleColor: '#fff' }, { id: 24, slug: 'user-pick-24', category: 'User Pick', title: '24', subtitle: 'User Selected 24', bg: 'assets/images/aka_zalSWkOpUZ.jpg', cardImg: 'assets/images/aka_zalSWkOpUZ.jpg', titleColor: '#fff' }, { id: 25, slug: 'user-pick-25', category: 'User Pick', title: '25', subtitle: 'User Selected 25', bg: 'assets/images/aka_01lGD33CLf.jpg', cardImg: 'assets/images/aka_01lGD33CLf.jpg', titleColor: '#fff' }, { id: 26, slug: 'user-pick-26', category: 'User Pick', title: '26', subtitle: 'User Selected 26', bg: 'assets/images/aka_WQMo3urDja.jpg', cardImg: 'assets/images/aka_WQMo3urDja.jpg', titleColor: '#fff' }, { id: 27, slug: 'user-pick-27', category: 'User Pick', title: '27', subtitle: 'User Selected 27', bg: 'assets/images/aka_tfLJNwpKt4.jpg', cardImg: 'assets/images/aka_tfLJNwpKt4.jpg', titleColor: '#fff' }, { id: 28, slug: 'user-pick-28', category: 'User Pick', title: '28', subtitle: 'User Selected 28', bg: 'assets/images/aka_V2TsU4BqnH.jpg', cardImg: 'assets/images/aka_V2TsU4BqnH.jpg', titleColor: '#fff' }, { id: 29, slug: 'user-pick-29', category: 'User Pick', title: '29', subtitle: 'User Selected 29', bg: 'assets/images/aka_y7Pwcg0hXM.jpg', cardImg: 'assets/images/aka_y7Pwcg0hXM.jpg', titleColor: '#fff' }, { id: 30, slug: 'user-pick-30', category: 'User Pick', title: '30', subtitle: 'User Selected 30', bg: 'assets/images/aka_6WRzwUVqL7.jpg', cardImg: 'assets/images/aka_6WRzwUVqL7.jpg', titleColor: '#fff' }, { id: 31, slug: 'user-pick-31', category: 'User Pick', title: '31', subtitle: 'User Selected 31', bg: 'assets/images/aka_Lnf0QacYnn.jpg', cardImg: 'assets/images/aka_Lnf0QacYnn.jpg', titleColor: '#fff' }, { id: 32, slug: 'user-pick-32', category: 'User Pick', title: '32', subtitle: 'User Selected 32', bg: 'assets/images/aka_w51ugRuAQX.jpg', cardImg: 'assets/images/aka_w51ugRuAQX.jpg', titleColor: '#fff' }, { id: 33, slug: 'user-pick-33', category: 'User Pick', title: '33', subtitle: 'User Selected 33', bg: 'assets/images/aka_UKgzrTz5Vo.jpg', cardImg: 'assets/images/aka_UKgzrTz5Vo.jpg', titleColor: '#fff' }, { id: 34, slug: 'user-pick-34', category: 'User Pick', title: '34', subtitle: 'User Selected 34', bg: 'assets/images/aka_LVWKc9mTP4.jpg', cardImg: 'assets/images/aka_LVWKc9mTP4.jpg', titleColor: '#fff' }, { id: 35, slug: 'user-pick-35', category: 'User Pick', title: '35', subtitle: 'User Selected 35', bg: 'assets/images/aka_acWQKIMaGV.jpg', cardImg: 'assets/images/aka_acWQKIMaGV.jpg', titleColor: '#fff' }, { id: 36, slug: 'user-pick-36', category: 'User Pick', title: '36', subtitle: 'User Selected 36', bg: 'assets/images/aka_vNsSLURX4h.jpg', cardImg: 'assets/images/aka_vNsSLURX4h.jpg', titleColor: '#fff' }, { id: 37, slug: 'user-pick-37', category: 'User Pick', title: '37', subtitle: 'User Selected 37', bg: 'assets/images/aka_urj99kUfSM.jpg', cardImg: 'assets/images/aka_urj99kUfSM.jpg', titleColor: '#fff' }, { id: 38, slug: 'user-pick-38', category: 'User Pick', title: '38', subtitle: 'User Selected 38', bg: 'assets/images/aka_rRuCFZGUwR.jpg', cardImg: 'assets/images/aka_rRuCFZGUwR.jpg', titleColor: '#fff' }, { id: 39, slug: 'user-pick-39', category: 'User Pick', title: '39', subtitle: 'User Selected 39', bg: 'assets/images/aka_lN3kYLjEwZ.jpg', cardImg: 'assets/images/aka_lN3kYLjEwZ.jpg', titleColor: '#fff' }, { id: 40, slug: 'user-pick-40', category: 'User Pick', title: '40', subtitle: 'User Selected 40', bg: 'assets/images/aka_Wyf8HhORRx.jpg', cardImg: 'assets/images/aka_Wyf8HhORRx.jpg', titleColor: '#fff' }, { id: 41, slug: 'user-pick-41', category: 'User Pick', title: '41', subtitle: 'User Selected 41', bg: 'assets/images/aka_q6rpnlhrPX.jpg', cardImg: 'assets/images/aka_q6rpnlhrPX.jpg', titleColor: '#fff' }, { id: 42, slug: 'user-pick-42', category: 'User Pick', title: '42', subtitle: 'User Selected 42', bg: 'assets/images/aka_woUuh8fK71.jpg', cardImg: 'assets/images/aka_woUuh8fK71.jpg', titleColor: '#fff' }, { id: 43, slug: 'user-pick-43', category: 'User Pick', title: '43', subtitle: 'User Selected 43', bg: 'assets/images/aka_4MYGGMTTOn.jpg', cardImg: 'assets/images/aka_4MYGGMTTOn.jpg', titleColor: '#fff' }, ]; // Fisher-Yates 洗牌 function shuffleArray(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } const shuffledShowcaseProjects = shuffleArray(showcaseProjects); function DynamicShowcase() { const [currentIndex, setCurrentIndex] = React.useState(0); const [loadedIndices, setLoadedIndices] = React.useState(new Set([0])); // 已加载的图片索引 React.useEffect(() => { const timer = setInterval(() => { setCurrentIndex((prev) => { const len = shuffledShowcaseProjects.length; if (len <= 1) return 0; let next = Math.floor(Math.random() * len); // 避免连续同一张 while (next === prev) { next = Math.floor(Math.random() * len); } // 标记下一张为已加载 setLoadedIndices(prevSet => { if (prevSet.has(next)) return prevSet; const newSet = new Set(prevSet); newSet.add(next); return newSet; }); return next; }); }, 900); return () => clearInterval(timer); }, []); // 预加载首屏前5张(加速前几次切换),其余按需加载 React.useEffect(() => { const preloadCount = Math.min(5, shuffledShowcaseProjects.length); setLoadedIndices(prev => { const newSet = new Set(prev); for (let i = 0; i < preloadCount; i++) { newSet.add(i); } return newSet; }); }, []); return (
{/* 背景层:Ken Burns 缓慢缩放(仅渲染已加载的) */}
); } // ========== 设计宣言 ========== function DesignManifesto() { const sectionRef = useRef(null); const layerRefs = useRef([]); const shadowRef = useRef(null); const [isMobile, setIsMobile] = useState( typeof window !== 'undefined' && window.innerWidth <= 640 ); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth <= 640); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const { handleMouseMove, handleMouseEnter, handleMouseLeave, handleTouchStart, handleTouchMove, handleTouchEnd } = useTrailReveal( sectionRef, layerRefs, { // 移动端:正圆形 mask,130px 直径,完整落在图片内 // 桌面端:椭圆形 mask,468×324px(放大1倍),宽高比 1.45:1 baseSizeX: isMobile ? 130 : 468, baseSizeY: isMobile ? 130 : 324, moveBoost: isMobile ? 10 : 20, breathAmount: isMobile ? 2 : 3, activeClass: 'is-revealing', trailLayers: [ { lerp: 0.18, opacity: 1.00, maskScale: 1.00 }, { lerp: 0.14, opacity: 0.82, maskScale: 0.94 }, { lerp: 0.11, opacity: 0.64, maskScale: 0.87 }, { lerp: 0.08, opacity: 0.46, maskScale: 0.78 }, { lerp: 0.055, opacity: 0.28, maskScale: 0.68 }, { lerp: 0.035, opacity: 0.14, maskScale: 0.56 }, ], } ); const setLayerRef = (index) => (el) => { if (el) layerRefs.current[index] = el; }; // 阴影层跟随主层(index 0)位置 useEffect(() => { const shadow = shadowRef.current; if (!shadow) return; const mainLayer = layerRefs.current[0]; if (!mainLayer) return; let rafId; const syncShadow = () => { const mx = mainLayer.style.getPropertyValue('--mouse-x') || '50%'; const my = mainLayer.style.getPropertyValue('--mouse-y') || '50%'; const ms = mainLayer.style.getPropertyValue('--mask-size') || '360px'; shadow.style.setProperty('--shadow-x', mx); shadow.style.setProperty('--shadow-y', my); shadow.style.setProperty('--shadow-size', ms); const opacity = parseFloat(mainLayer.style.opacity || '0'); shadow.style.opacity = opacity * 0.6; rafId = requestAnimationFrame(syncShadow); }; rafId = requestAnimationFrame(syncShadow); return () => cancelAnimationFrame(rafId); }, []); const metalSrc = "assets/images/aka_ZPGmgYLgL2.jpg"; const trailCount = 6; return (
{/* 底层:底图 */} 底层浮雕 {/* 边缘阴影层 */}
); } // ========== 彩色浮雕揭示板块 ========== // 优化版:柔和径向渐变揭示 + 多层拖尾 + 边缘阴影 function ColorReveal() { const sectionRef = useRef(null); const layerRefs = useRef([]); const shadowRef = useRef(null); const [isMobile, setIsMobile] = useState( typeof window !== 'undefined' && window.innerWidth <= 640 ); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth <= 640); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const { handleMouseMove, handleMouseEnter, handleMouseLeave, handleTouchStart, handleTouchMove, handleTouchEnd } = useTrailReveal( sectionRef, layerRefs, { // 移动端:正圆形 mask,130px 直径,完整落在图片内 // 桌面端:椭圆形 mask,468×324px(放大1倍),宽高比 1.45:1 baseSizeX: isMobile ? 130 : 468, baseSizeY: isMobile ? 130 : 324, moveBoost: isMobile ? 10 : 20, breathAmount: isMobile ? 2 : 3, activeClass: 'is-revealing', trailLayers: [ { lerp: 0.18, opacity: 1.00, maskScale: 1.00 }, { lerp: 0.14, opacity: 0.82, maskScale: 0.94 }, { lerp: 0.11, opacity: 0.64, maskScale: 0.87 }, { lerp: 0.08, opacity: 0.46, maskScale: 0.78 }, { lerp: 0.055, opacity: 0.28, maskScale: 0.68 }, { lerp: 0.035, opacity: 0.14, maskScale: 0.56 }, ], } ); const setLayerRef = (index) => (el) => { if (el) layerRefs.current[index] = el; }; // 阴影层跟随主层(index 0)位置 useEffect(() => { const shadow = shadowRef.current; if (!shadow) return; const mainLayer = layerRefs.current[0]; if (!mainLayer) return; let rafId; const syncShadow = () => { const mx = mainLayer.style.getPropertyValue('--mouse-x') || '50%'; const my = mainLayer.style.getPropertyValue('--mouse-y') || '50%'; const ms = mainLayer.style.getPropertyValue('--mask-size') || '360px'; shadow.style.setProperty('--shadow-x', mx); shadow.style.setProperty('--shadow-y', my); shadow.style.setProperty('--shadow-size', ms); // 同步透明度 const opacity = parseFloat(mainLayer.style.opacity || '0'); shadow.style.opacity = opacity * 0.6; rafId = requestAnimationFrame(syncShadow); }; rafId = requestAnimationFrame(syncShadow); return () => cancelAnimationFrame(rafId); }, []); const colorSrc = "assets/images/aka_e7XQyVHRKb.jpg"; const trailCount = 6; return (
{/* 底层:石膏图 */} 石膏浮雕 {/* 边缘阴影层:在揭示区域边缘投射柔和内阴影,增强立体感 */}
); } // ========== 关于我 ========== function About() { return (
SENLAN
Creative Director

Hero 不问出处,live 只看状态。出身不能定义人,头衔无需做背书。Karl Lagerfeld 曾说:人生不是选美比赛,青春美貌终会褪色,唯有智慧长存。对我而言,Career 是持续的 "不满足":不甘现状、拒绝陈旧、不将就 "差不多就行"。比起过往履历,我更看重当下的状态与内核。

理念 极致美 + 共鸣情 = 品牌资产最深护城河
愿景 创业四年验证0-1全链路,寻成熟平台聚焦品牌创意本身。
优势 策略策略 · BOSS思维 · 团队管理
); } // ========== 作品展示 ========== function Works() { const [activeCat, setActiveCat] = useState(categories[0]); const filtered = works.filter(w => w.category === activeCat); const cardHeights = [520, 470, 420, 370]; const cardWidths = [340, 320, 300, 280]; const displayWorks = filtered.slice(0, 4); return (
创意 / Director WORK

不必疆古,新构产品高阶价值。

{categories.map(cat => ( ))}
{displayWorks.map((work, i) => ( {work.title}
{work.categoryEn}

{work.title}

))}
); } // ========== 服务 ========== function Services() { return (
价值 / SERVICES

提供从策略
到落地的全链落地指导

{services.map((s, i) => (
0{i + 1}

{s.title}

{s.subtitle}

{s.desc}

))}
); } // ========== 联系 + 页脚 ========== function Contact() { const dragonRef = useRef(null); const layerRefs = useRef([]); const [isMobile, setIsMobile] = useState( typeof window !== 'undefined' && window.innerWidth <= 640 ); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth <= 640); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const { handleMouseMove, handleMouseEnter, handleMouseLeave, handleTouchStart, handleTouchMove, handleTouchEnd } = useTrailReveal( dragonRef, layerRefs, { // 移动端:正圆 280px // 桌面端:椭圆 560×386px(放大1倍,宽高比 1.45:1) baseSizeX: isMobile ? 280 : 560, baseSizeY: isMobile ? 280 : 386, moveBoost: isMobile ? 30 : 60, breathAmount: isMobile ? 4 : 8, activeClass: 'is-revealing', trailLayers: [ { lerp: 0.18, opacity: 1.00, maskScale: 1.00 }, { lerp: 0.14, opacity: 0.82, maskScale: 0.94 }, { lerp: 0.11, opacity: 0.64, maskScale: 0.87 }, { lerp: 0.08, opacity: 0.46, maskScale: 0.78 }, { lerp: 0.055, opacity: 0.28, maskScale: 0.68 }, { lerp: 0.035, opacity: 0.14, maskScale: 0.56 }, ], } ); const setLayerRef = (index) => (el) => { if (el) layerRefs.current[index] = el; }; const dragonSrc = "assets/contact-dragon-white.png"; const trailCount = 6; return (
{/* 龙图 wrapper:与龙图同尺寸同位置,承接鼠标事件 */}
{/* 底层:淡色龙图(默认显示) */} {/* 6 层高亮揭示层龙图,mask 柔和揭示 */} {Array.from({ length: trailCount }).map((_, i) => ( ))}
联系 / Please contact me

创意虽无价,
执行才值钱。

Senlanyes@163.com
聘请 合作共进 投资创意 顾问
); } // ========== 作品详情页 ========== function WorkDetail({ work, onBack }) { React.useEffect(() => { window.scrollTo(0, 0); }, []); // Scroll reveal React.useEffect(() => { const els = document.querySelectorAll('.work-detail .reveal'); const io = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('is-visible'); io.unobserve(entry.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' } ); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, []); return (
{ e.preventDefault(); onBack(); }}> 返回作品列表
摩登女神 / Design meets

B X Pearl pink 珍脂粉

Pearl pink

海洋系列 X 小精灵

海洋系列 X 小精灵,是一支以海洋自然之美为灵感的香氛品牌。我们以深海精灵——水母为核心视觉符号,将海洋的神秘、空灵与优雅香水的嗅觉体验融为一体,构建出一套从品牌策略、视觉识别到包装落地的完整体系。设计语言克制而富有诗意,让每一次打开都成为一场向深海的漫游。

{/* 板块1:品牌视觉识别 */}
视觉符号 / Visual Identity

灵动线稿,勾勒海洋精灵

以水母(海洋精灵)为核心视觉符号,线稿风格勾勒灵动形态,传递自然野性与优雅的平衡。每一根线条都如水波般自由流动,赋予品牌鲜活而持久的生命力。

海洋系列水母线稿
水母线稿变体
{/* 板块2:Logo与品牌标识 */}
品牌标识 / Brand Mark

深海蓝上的通透质感

Senlan × Fairy 小精灵,水母图形与字体组合,在深海蓝背景上呈现通透质感。真实水母影像与线稿符号的叠加,让品牌标识既有自然的野性,又有设计的精致。

senlan BEAST Logo设计
{/* 板块3:包装设计 */}
包装 / Packaging

把海洋精灵装进瓶中

水母形态香水瓶,紫色透明玻璃质感,将海洋生物的灵动转化为可触摸的产品形态。从瓶身曲线到光影折射,每一处细节都在讲述深海的神秘与优雅。

香水瓶渲染
{/* 板块4:细节与应用 */}
创意呈现 / Details

从线稿到落地的全链路

从线稿到渲染,从概念到落地,全链路设计确保品牌视觉的一致性。每一个触点都经过精细打磨,让品牌精神在每一处细节都被感知。

品牌细节
品牌细节
香水瓶渲染
{/* 设计理念 */}

极致审美 + 精神共鸣 = 品牌资产里最深的护城河

海洋系列不只是一瓶香水,它是一种情绪、一段关于自由与深邃的隐喻。我们相信,真正打动人的品牌,是在审美之上建立精神共鸣——当消费者拿起瓶子的那一刻,她触摸到的是自己心中那片未曾抵达的海。

{/* 收尾大图 */}
水母香水瓶多瓶展示
{ e.preventDefault(); onBack(); }}> ← 返回全部作品
); } // ========== 主组件 ========== function App() { const [currentPage, setCurrentPage] = React.useState('home'); const [selectedWork, setSelectedWork] = React.useState(null); // 监听hash变化实现路由 React.useEffect(() => { const handleHashChange = () => { const hash = window.location.hash; const match = hash.match(/^#\/work\/(.+)$/); if (match) { const slug = match[1]; const w = works.find(item => item.slug === slug); if (w) { if (w.sany) { setCurrentPage('sany'); setSelectedWork(w); return; } if (w.huamei) { setCurrentPage('huamei'); setSelectedWork(w); return; } if (w.bpearl) { setCurrentPage('bpearl'); setSelectedWork(w); return; } if (w.bpearlPink) { setCurrentPage('bpearlPink'); setSelectedWork(w); return; } if (w.oceanHeart) { setCurrentPage('oceanHeart'); setSelectedWork(w); return; } if (w.qingzhao) { setCurrentPage('qingzhao'); setSelectedWork(w); return; } if (w.senxing) { setCurrentPage('senxing'); setSelectedWork(w); return; } if (w.meridianJournal) { setCurrentPage('meridianJournal'); setSelectedWork(w); return; } if (w.ritualHouse) { setCurrentPage('ritualHouse'); setSelectedWork(w); return; } if (w.bLuxury) { setCurrentPage('bLuxury'); setSelectedWork(w); return; } if (w.archive07) { setCurrentPage('archive07'); setSelectedWork(w); return; } if (w.placeholder) { setCurrentPage('placeholder'); setSelectedWork(w); return; } if (w.amberSpot) { setCurrentPage('amberSpot'); setSelectedWork(w); return; } setSelectedWork(w); setCurrentPage('work'); return; } } setCurrentPage('home'); setSelectedWork(null); }; handleHashChange(); window.addEventListener('hashchange', handleHashChange); return () => window.removeEventListener('hashchange', handleHashChange); }, []); const goHome = () => { window.location.hash = ''; setCurrentPage('home'); setSelectedWork(null); }; const openWork = (slug) => { window.location.hash = `#/work/${slug}`; }; if (currentPage === 'sany' && selectedWork && typeof SanyMidautumnDetail !== 'undefined') { return (
); } if (currentPage === 'huamei' && selectedWork && typeof GuangdongHuameiDetail !== 'undefined') { return (
); } if (currentPage === 'oceanHeart' && selectedWork && typeof OceanHeartDetail !== 'undefined') { return (
); } if (currentPage === 'bpearl' && selectedWork && typeof BPealDetail !== 'undefined') { return (
); } if (currentPage === 'bpearlPink' && selectedWork && typeof BPearlPinkDetail !== 'undefined') { return (
); } if (currentPage === 'meridianJournal' && selectedWork && typeof MeridianJournalDetail !== 'undefined') { return (
); } if (currentPage === 'ritualHouse' && selectedWork && typeof RitualHouseDetail !== 'undefined') { return (
); } if (currentPage === 'bLuxury' && selectedWork && typeof BLuxuryDetail !== 'undefined') { return (
); } if (currentPage === 'archive07' && selectedWork && typeof Archive07Detail !== 'undefined') { return (
); } if (currentPage === 'qingzhao' && selectedWork && typeof QingzhaoDetail !== 'undefined') { return (
); } if (currentPage === 'senxing' && selectedWork && typeof SenxingDetail !== 'undefined') { return (
); } if (currentPage === 'placeholder' && selectedWork && typeof PlaceholderDetail !== 'undefined') { return (
); } if (currentPage === 'amberSpot' && selectedWork && typeof AmberSpotDetail !== 'undefined') { return (
); } if (currentPage === 'work' && selectedWork) { return (
); } return (
); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render();