东燊科技(Donsen)宣传网页:中英双语单页站 + 未来科技感视觉系统

- 单页双语站:data-lang 切换中/英,全站内容双语文案
- Hero 不对称双栏:左文案列(eyebrow/标题/渐变 slogan/CTA/数据条)+ 右凤凰视觉舱(深色玻璃 HUD + 能量轨道环 + 均衡器脉冲)
- 视觉系统 fx.css/fx.js(与 main.js 解耦):深空星野/鼠标聚光/自定义光标/点击爆炸/卡片 3D 倾斜/数字滚动;?static=1、prefers-reduced-motion、触屏自动降级
- 产品矩阵四段式结构(痛点→方案→场景→效益),四条产品线全部真实截图(WebP + PNG 兜底)
- 顶栏/页脚透明凤凰标识(phoenix.webp/png),去白底
- 部署配套:404.html、_headers、nginx.conf、robots.txt、favicon/apple-touch-icon/og-cover
- 性能基线:总传输约 153KB、CLS 0、CDP 两套取证套件全绿
This commit is contained in:
2026-10-19 23:13:30 +08:00
commit 59607097c7
24 changed files with 1681 additions and 0 deletions
+288
View File
@@ -0,0 +1,288 @@
/* 东燊科技 Donsen — 未来科技感动效层(fx.js)
依赖:fx.css。与 main.js 完全解耦,任一方失效不影响基础功能。
模块:① 深空星野 ② 鼠标聚光 ③ 自定义光标 ④ 点击爆炸 ⑤ 卡片 3D 倾斜 ⑥ 数字滚动 */
(function () {
'use strict';
var html = document.documentElement;
var params = new URLSearchParams(location.search);
var staticMode = params.get('static') === '1';
var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 触屏设备(含无头移动端模拟)不启用自定义光标/聚光/倾斜;爆炸效果保留(pointerdown 兼容触摸)
var isTouch = (navigator.maxTouchPoints || 0) > 0 || 'ontouchstart' in window;
var finePointer = window.matchMedia && window.matchMedia('(pointer:fine)').matches && !isTouch;
var dpr = Math.min(window.devicePixelRatio || 1, 2);
/* ============ ① 深空星野 ============ */
function starfield() {
var cv = document.getElementById('starfield');
if (!cv) return null;
var ctx = cv.getContext('2d');
var W = 0, H = 0, stars = [], shooters = [], raf = 0, mx = 0, my = 0, tmx = 0, tmy = 0;
function resize() {
W = window.innerWidth; H = window.innerHeight;
cv.width = W * dpr; cv.height = H * dpr;
cv.style.width = W + 'px'; cv.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function seed(done) {
var n = Math.round((W * H) / 14000);
stars = [];
var i = 0;
// 分片生成(每帧 40 颗):避免首屏启动时一次循环占满主线程构成长任务
function chunk() {
var end = Math.min(i + 40, n);
for (; i < end; i++) {
var depth = 0.25 + Math.random() * 0.75;
stars.push({
x: Math.random() * W, y: Math.random() * H,
r: depth * 1.5 + 0.3,
a: 0.25 + Math.random() * 0.65,
tw: 0.6 + Math.random() * 1.8, ph: Math.random() * Math.PI * 2,
vy: depth * 0.05, hue: Math.random() < 0.12 ? 190 : (Math.random() < 0.1 ? 32 : 215),
depth: depth
});
}
if (i < n) { requestAnimationFrame(chunk); return; }
if (done) done();
}
chunk();
}
function seedAndStart() {
stop();
seed(function () {
if (staticMode || reduceMotion) { frame(0); stop(); } else start();
});
}
function spawnShooter() {
if (shooters.length > 1 || Math.random() > 0.35) return;
shooters.push({
x: Math.random() * W * 0.7, y: Math.random() * H * 0.4,
vx: 7 + Math.random() * 5, vy: 2.4 + Math.random() * 1.6, life: 1
});
}
function frame(ms) {
var t = ms / 1000;
tmx += (mx - tmx) * 0.04; tmy += (my - tmy) * 0.04;
ctx.clearRect(0, 0, W, H);
for (var i = 0; i < stars.length; i++) {
var s = stars[i];
var tw = 0.72 + 0.28 * Math.sin(t * s.tw + s.ph);
var px = s.x + tmx * 26 * s.depth;
var py = s.y + tmy * 18 * s.depth;
if (!reduceMotion) { s.y += s.vy; if (s.y > H + 4) { s.y = -4; s.x = Math.random() * W; } }
ctx.beginPath();
ctx.arc(px, py, s.r * tw, 0, Math.PI * 2);
ctx.fillStyle = 'hsla(' + s.hue + ',90%,' + (72 + s.depth * 18) + '%,' + (s.a * tw).toFixed(3) + ')';
ctx.fill();
if (s.r > 1.35) { // 大星加光晕
ctx.beginPath();
ctx.arc(px, py, s.r * 3.2, 0, Math.PI * 2);
ctx.fillStyle = 'hsla(' + s.hue + ',95%,75%,' + (s.a * 0.10).toFixed(3) + ')';
ctx.fill();
}
}
// 流星
if (!reduceMotion && Math.random() < 0.0022) spawnShooter();
for (var j = shooters.length - 1; j >= 0; j--) {
var sh = shooters[j];
sh.x += sh.vx; sh.y += sh.vy; sh.life -= 0.012;
var g = ctx.createLinearGradient(sh.x, sh.y, sh.x - sh.vx * 9, sh.y - sh.vy * 9);
g.addColorStop(0, 'rgba(255,255,255,' + (0.85 * sh.life).toFixed(3) + ')');
g.addColorStop(1, 'rgba(255,255,255,0)');
ctx.strokeStyle = g; ctx.lineWidth = 1.6;
ctx.beginPath(); ctx.moveTo(sh.x, sh.y); ctx.lineTo(sh.x - sh.vx * 9, sh.y - sh.vy * 9); ctx.stroke();
if (sh.life <= 0 || sh.x > W + 60) shooters.splice(j, 1);
}
if (!document.hidden) raf = requestAnimationFrame(frame);
}
function start() { if (!raf && !document.hidden) raf = requestAnimationFrame(frame); }
function stop() { if (raf) { cancelAnimationFrame(raf); raf = 0; } }
document.addEventListener('visibilitychange', function () { document.hidden ? stop() : start(); });
window.addEventListener('resize', function () { resize(); seedAndStart(); });
window.addEventListener('mousemove', function (e) {
mx = (e.clientX / W - 0.5) * 2; my = (e.clientY / H - 0.5) * 2;
});
resize(); seedAndStart();
return { stop: stop, start: start };
}
/* ============ ② 鼠标聚光 + ③ 自定义光标 ============ */
function pointerFx() {
if (staticMode || reduceMotion || !finePointer) return null;
html.classList.add('fx-on');
var spot = document.getElementById('spotlight');
var dot = document.querySelector('.cursor-dot');
var ring = document.querySelector('.cursor-ring');
var tx = innerWidth / 2, ty = innerHeight / 2, sx = tx, sy = ty, rx = tx, ry = ty, raf = 0;
function loop() {
sx += (tx - sx) * 0.12; sy += (ty - sy) * 0.12;
rx += (tx - rx) * 0.24; ry += (ty - ry) * 0.24;
if (spot) spot.style.transform = 'translate3d(' + sx + 'px,' + sy + 'px,0)';
if (dot) dot.style.transform = 'translate3d(' + tx + 'px,' + ty + 'px,0)';
if (ring) ring.style.transform = 'translate3d(' + rx + 'px,' + ry + 'px,0)';
// 三轴全部收敛到指针(<0.5px)即休眠,下次 mousemove 重启——避免指针静止后 rAF 永不停转
if (Math.abs(tx - sx) < 0.5 && Math.abs(ty - sy) < 0.5 &&
Math.abs(tx - rx) < 0.5 && Math.abs(ty - ry) < 0.5) {
raf = 0;
return;
}
raf = requestAnimationFrame(loop);
}
window.addEventListener('mousemove', function (e) {
tx = e.clientX; ty = e.clientY;
if (!raf) raf = requestAnimationFrame(loop);
}, { passive: true });
if (!raf) raf = requestAnimationFrame(loop);
// 悬停可交互元素:光环放大
var hot = 'a,button,[data-tilt],.scene-chips span,.benefit-item';
document.addEventListener('mouseover', function (e) {
if (e.target.closest && e.target.closest(hot) && ring) ring.classList.add('is-hot');
});
document.addEventListener('mouseout', function (e) {
if (e.target.closest && e.target.closest(hot) && ring) ring.classList.remove('is-hot');
});
return true;
}
/* ============ ④ 点击爆炸 ============ */
function explosion() {
var layer = document.getElementById('fx-layer');
if (!layer) return null;
var ctx = layer.getContext('2d');
var parts = [], rings = [], raf = 0;
var COLORS = ['#5B9BFF', '#2E7CF6', '#5BE9FF', '#FFFFFF', '#9D8CFF'];
function resize() {
layer.width = innerWidth * dpr; layer.height = innerHeight * dpr;
layer.style.width = innerWidth + 'px'; layer.style.height = innerHeight + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
window.addEventListener('resize', resize); resize();
function burst(x, y, warm) {
if (staticMode || reduceMotion) return;
var n = 34 + Math.round(Math.random() * 10);
for (var i = 0; i < n; i++) {
var ang = Math.random() * Math.PI * 2;
var sp = 1.6 + Math.random() * 7.2;
parts.push({
x: x, y: y,
vx: Math.cos(ang) * sp, vy: Math.sin(ang) * sp - 1.4,
r: 1.1 + Math.random() * 2.6,
life: 1, decay: 0.016 + Math.random() * 0.02,
col: warm && Math.random() < 0.4 ? '#FFB37A' : COLORS[(Math.random() * COLORS.length) | 0]
});
}
rings.push({ x: x, y: y, r: 6, life: 1, warm: !!warm });
var el = document.createElement('div');
el.className = 'shockwave' + (warm ? ' warm' : '');
el.style.left = x + 'px'; el.style.top = y + 'px';
document.body.appendChild(el);
setTimeout(function () { el.remove(); }, 700);
if (!raf) raf = requestAnimationFrame(frame);
}
function frame() {
ctx.clearRect(0, 0, innerWidth, innerHeight);
var i, p;
for (i = parts.length - 1; i >= 0; i--) {
p = parts[i];
p.x += p.vx; p.y += p.vy; p.vy += 0.14; p.vx *= 0.985; p.vy *= 0.985;
p.life -= p.decay;
if (p.life <= 0) { parts.splice(i, 1); continue; }
ctx.globalAlpha = Math.max(0, p.life);
ctx.fillStyle = p.col;
ctx.beginPath(); ctx.arc(p.x, p.y, p.r * p.life + 0.4, 0, Math.PI * 2); ctx.fill();
}
for (i = rings.length - 1; i >= 0; i--) {
var r = rings[i];
r.r += 7.5; r.life -= 0.045;
if (r.life <= 0) { rings.splice(i, 1); continue; }
ctx.globalAlpha = Math.max(0, r.life * 0.8);
ctx.strokeStyle = r.warm ? '#FFB37A' : '#5BE9FF';
ctx.lineWidth = 2 * r.life;
ctx.beginPath(); ctx.arc(r.x, r.y, r.r, 0, Math.PI * 2); ctx.stroke();
}
ctx.globalAlpha = 1;
raf = (parts.length || rings.length) ? requestAnimationFrame(frame) : 0;
}
window.addEventListener('pointerdown', function (e) {
burst(e.clientX, e.clientY, Math.random() < 0.16);
});
return { burst: burst };
}
/* ============ ⑤ 卡片 3D 倾斜 + 指针辉光 ============ */
function tilt() {
if (staticMode || reduceMotion || !finePointer) return null;
var els = document.querySelectorAll('[data-tilt]');
// 矩形缓存:mouseenter 时读一次,mousemove 直接用;滚动/缩放/离开时失效
// (15 张卡 × 每秒数十次 mousemove,每次都 getBoundingClientRect 会强制布局)
function invalidate() {
for (var i = 0; i < els.length; i++) els[i]._rect = null;
}
els.forEach(function (el) {
el.addEventListener('mouseenter', function () {
el._rect = el.getBoundingClientRect();
});
el.addEventListener('mousemove', function (e) {
var r = el._rect;
if (!r) r = el._rect = el.getBoundingClientRect();
var px = (e.clientX - r.left) / r.width, py = (e.clientY - r.top) / r.height;
el.style.setProperty('--mx', (px * 100).toFixed(1) + '%');
el.style.setProperty('--my', (py * 100).toFixed(1) + '%');
var rx = (0.5 - py) * 5, ry = (px - 0.5) * 5;
el.style.transform = 'perspective(900px) rotateX(' + rx.toFixed(2) + 'deg) rotateY(' + ry.toFixed(2) + 'deg) translateY(-4px)';
});
el.addEventListener('mouseleave', function () {
el._rect = null;
el.style.transform = 'perspective(900px) rotateX(0deg) rotateY(0deg)';
});
});
window.addEventListener('scroll', invalidate, { passive: true });
window.addEventListener('resize', invalidate);
return els.length;
}
/* ============ ⑥ 数字滚动 ============ */
function countUp() {
var els = Array.prototype.slice.call(document.querySelectorAll('[data-count]'));
if (!els.length) return 0;
function setFinal(el) { el.textContent = el.getAttribute('data-count') + (el.getAttribute('data-suffix') || ''); }
if (staticMode || reduceMotion || !('IntersectionObserver' in window)) {
els.forEach(setFinal); return els.length;
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (!en.isIntersecting) return;
var el = en.target, end = parseFloat(el.getAttribute('data-count')) || 0;
var suffix = el.getAttribute('data-suffix') || '';
var t0 = performance.now(), dur = 1100;
(function step(now) {
var k = Math.min(1, (now - t0) / dur);
var eased = 1 - Math.pow(1 - k, 3);
el.textContent = Math.round(end * eased) + suffix;
if (k < 1) requestAnimationFrame(step); else el.textContent = end + suffix;
})(t0);
io.unobserve(el);
});
}, { threshold: 0.4 });
els.forEach(function (el) { io.observe(el); });
return els.length;
}
/* ============ 启动 ============ */
function boot() {
starfield();
pointerFx();
explosion();
tilt();
countUp();
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
else boot();
})();
+102
View File
@@ -0,0 +1,102 @@
/* 东燊科技 Donsen — 交互脚本 */
(function () {
'use strict';
/* ---------- 语言切换(默认中文,记忆选择) ---------- */
var body = document.body;
var toggle = document.getElementById('langToggle');
var optZh = document.querySelector('.lang-zh');
var optEn = document.querySelector('.lang-en');
function applyLang(lang) {
body.setAttribute('data-lang', lang);
document.documentElement.setAttribute('lang', lang === 'zh' ? 'zh-CN' : 'en');
if (optZh) optZh.classList.toggle('is-active', lang === 'zh');
if (optEn) optEn.classList.toggle('is-active', lang === 'en');
try { localStorage.setItem('donsen-lang', lang); } catch (e) {}
}
var params = new URLSearchParams(location.search);
var saved = null;
try { saved = params.get('lang') || localStorage.getItem('donsen-lang'); } catch (e) {}
applyLang(saved === 'en' ? 'en' : 'zh');
if (toggle) {
toggle.addEventListener('click', function () {
applyLang(body.getAttribute('data-lang') === 'zh' ? 'en' : 'zh');
});
}
/* ---------- 移动端菜单 ---------- */
var burger = document.getElementById('navBurger');
var links = document.getElementById('navLinks');
if (burger && links) {
burger.addEventListener('click', function () {
links.classList.toggle('open');
});
links.querySelectorAll('a').forEach(function (a) {
a.addEventListener('click', function () { links.classList.remove('open'); });
});
}
/* ---------- 滚动高亮导航(rAF 节流 + offsetTop 缓存,滚动帧内零强制布局) ---------- */
var navEl = document.getElementById('nav');
var sections = Array.prototype.slice.call(document.querySelectorAll('section[id]'));
var navLinks = Array.prototype.slice.call(document.querySelectorAll('.nav-link'));
var sectionTops = [];
function measureTops() {
sectionTops = sections.map(function (s) { return s.getBoundingClientRect().top + window.scrollY; });
}
var scrollTick = false;
function onScrollFrame() {
scrollTick = false;
var y = window.scrollY;
var scrolled = y > 40;
if (navEl) {
navEl.style.background = scrolled ? 'rgba(6,15,34,.96)' : 'rgba(6,15,34,.82)';
// 滚动后切纯色背景并去掉毛玻璃(backdrop-filter 持续混合开销大)
navEl.classList.toggle('scrolled', scrolled);
}
var current = sections[0] ? sections[0].id : '';
for (var i = 0; i < sectionTops.length; i++) {
if (sectionTops[i] <= y + 120) current = sections[i].id;
}
navLinks.forEach(function (l) {
l.classList.toggle('active', l.getAttribute('href') === '#' + current);
});
}
function onScroll() {
if (scrollTick) return;
scrollTick = true;
requestAnimationFrame(onScrollFrame);
}
measureTops();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', function () { measureTops(); onScrollFrame(); });
onScrollFrame();
/* ---------- 滚动显现动画(?static=1 或减弱动效时直接全展) ---------- */
var revealEls = document.querySelectorAll('.reveal');
var staticMode = new URLSearchParams(location.search).get('static') === '1';
var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (staticMode || reduceMotion || !('IntersectionObserver' in window)) {
revealEls.forEach(function (el) { el.classList.add('in'); });
} else {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) {
en.target.classList.add('in');
io.unobserve(en.target);
}
});
}, { threshold: 0.12 });
revealEls.forEach(function (el) { io.observe(el); });
}
/* ---------- 页脚年份 ---------- */
var y = new Date().getFullYear();
['year', 'year-en'].forEach(function (id) {
var el = document.getElementById(id);
if (el) el.textContent = y;
});
})();