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 缓慢缩放(仅渲染已加载的) */}
{shuffledShowcaseProjects.map((p, i) => (
loadedIndices.has(i) && (
)
))}
{/* 遮罩层 */}
{/* 内容层 */}
{/* 卡片层(仅渲染已加载的,减少 DOM 节点) */}
{shuffledShowcaseProjects.map((p, i) => (
loadedIndices.has(i) && (
)
))}
);
}
// ========== 设计宣言 ==========
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 (
{/* 底层:底图 */}

{/* 边缘阴影层 */}
{/* 6 层金属图,全部 100% 尺寸,仅 mask-size / opacity 不同 */}
{Array.from({ length: trailCount }).map((_, i) => (

))}
);
}
// ========== 彩色浮雕揭示板块 ==========
// 优化版:柔和径向渐变揭示 + 多层拖尾 + 边缘阴影
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 (
{/* 底层:石膏图 */}

{/* 边缘阴影层:在揭示区域边缘投射柔和内阴影,增强立体感 */}
{/* 6 层彩色揭示图,全部 100% 尺寸,仅 mask-size / opacity 不同 */}
{Array.from({ length: trailCount }).map((_, i) => (

))}
);
}
// ========== 关于我 ==========
function About() {
return (
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 => (
))}
);
}
// ========== 服务 ==========
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 (
);
}
// ========== 作品详情页 ==========
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 (
海洋系列 X 小精灵,是一支以海洋自然之美为灵感的香氛品牌。我们以深海精灵——水母为核心视觉符号,将海洋的神秘、空灵与优雅香水的嗅觉体验融为一体,构建出一套从品牌策略、视觉识别到包装落地的完整体系。设计语言克制而富有诗意,让每一次打开都成为一场向深海的漫游。
{/* 板块1:品牌视觉识别 */}
视觉符号 / Visual Identity
灵动线稿,勾勒海洋精灵
以水母(海洋精灵)为核心视觉符号,线稿风格勾勒灵动形态,传递自然野性与优雅的平衡。每一根线条都如水波般自由流动,赋予品牌鲜活而持久的生命力。
{/* 板块2:Logo与品牌标识 */}
品牌标识 / Brand Mark
深海蓝上的通透质感
Senlan × Fairy 小精灵,水母图形与字体组合,在深海蓝背景上呈现通透质感。真实水母影像与线稿符号的叠加,让品牌标识既有自然的野性,又有设计的精致。
{/* 板块3:包装设计 */}
包装 / Packaging
把海洋精灵装进瓶中
水母形态香水瓶,紫色透明玻璃质感,将海洋生物的灵动转化为可触摸的产品形态。从瓶身曲线到光影折射,每一处细节都在讲述深海的神秘与优雅。
{/* 板块4:细节与应用 */}
创意呈现 / Details
从线稿到落地的全链路
从线稿到渲染,从概念到落地,全链路设计确保品牌视觉的一致性。每一个触点都经过精细打磨,让品牌精神在每一处细节都被感知。
{/* 设计理念 */}
极致审美 + 精神共鸣 = 品牌资产里最深的护城河
海洋系列不只是一瓶香水,它是一种情绪、一段关于自由与深邃的隐喻。我们相信,真正打动人的品牌,是在审美之上建立精神共鸣——当消费者拿起瓶子的那一刻,她触摸到的是自己心中那片未曾抵达的海。
{/* 收尾大图 */}
);
}
// ========== 主组件 ==========
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();