Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.
Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)
Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
(dropped 3 stale entries, added the 17 missing ones)
Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
when it is empty
Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.
Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
195 lines
6.8 KiB
JavaScript
195 lines
6.8 KiB
JavaScript
/**
|
|
* Jio Effects — count-up / countdown / marquee / 3D tilt / FPS detection
|
|
*
|
|
* 全部渐进增强,尊重 prefers-reduced-motion。
|
|
*
|
|
* @package Jio
|
|
*/
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
|
/* ============================================================
|
|
* 1. 数字递增 (count-up) — IntersectionObserver 触发
|
|
* 用法:<span data-jio-countup data-target="1234" data-duration="2000">0</span>
|
|
* ============================================================ */
|
|
function initCountUp() {
|
|
if (reducedMotion) return;
|
|
if (!('IntersectionObserver' in window)) return;
|
|
|
|
var els = document.querySelectorAll('[data-jio-countup]:not([data-jio-countup-init])');
|
|
if (!els.length) return;
|
|
|
|
var observer = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) return;
|
|
var el = entry.target;
|
|
observer.unobserve(el);
|
|
|
|
var target = parseFloat(el.dataset.target || '0');
|
|
var duration = parseInt(el.dataset.duration || '2000', 10);
|
|
var decimals = (el.dataset.decimals || '0|').split('|');
|
|
var prefix = decimals[1] || '';
|
|
var dec = parseInt(decimals[0], 10) || 0;
|
|
var start = 0;
|
|
var startTime = null;
|
|
|
|
function step(timestamp) {
|
|
if (!startTime) startTime = timestamp;
|
|
var progress = Math.min((timestamp - startTime) / duration, 1);
|
|
// ease-out cubic
|
|
var eased = 1 - Math.pow(1 - progress, 3);
|
|
var current = start + (target - start) * eased;
|
|
el.textContent = prefix + current.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
if (progress < 1) {
|
|
requestAnimationFrame(step);
|
|
} else {
|
|
el.textContent = prefix + target.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(step);
|
|
});
|
|
}, { threshold: 0.3 });
|
|
|
|
els.forEach(function (el) {
|
|
el.dataset.jioCountupInit = '1';
|
|
observer.observe(el);
|
|
});
|
|
}
|
|
|
|
/* ============================================================
|
|
* 2. 倒计时组件
|
|
* 用法:<span data-jio-countdown data-end="2026-12-31T23:59:59">--:--:--</span>
|
|
* ============================================================ */
|
|
function initCountdown() {
|
|
var els = document.querySelectorAll('[data-jio-countdown]:not([data-jio-countdown-init])');
|
|
if (!els.length) return;
|
|
|
|
function pad(n) { return n < 10 ? '0' + n : '' + n; }
|
|
|
|
function update(el) {
|
|
var now = Date.now();
|
|
var end = new Date(el.dataset.end).getTime();
|
|
var diff = end - now;
|
|
if (diff <= 0) {
|
|
el.textContent = el.dataset.expired || '已结束';
|
|
return;
|
|
}
|
|
var d = Math.floor(diff / 86400000);
|
|
var h = Math.floor((diff % 86400000) / 3600000);
|
|
var m = Math.floor((diff % 86400000) / 60000);
|
|
var s = Math.floor((diff % 60000) / 1000);
|
|
var format = el.dataset.format || 'dhms';
|
|
if (format === 'dhms') {
|
|
el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + ':' + pad(m) + ':' + pad(s);
|
|
} else if (format === 'hms') {
|
|
el.textContent = pad(h + d * 24) + ':' + pad(m) + ':' + pad(s);
|
|
} else {
|
|
el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + '时' + pad(m) + '分' + pad(s) + '秒';
|
|
}
|
|
}
|
|
|
|
els.forEach(function (el) {
|
|
el.dataset.jioCountdownInit = '1';
|
|
update(el);
|
|
setInterval(function () { update(el); }, 1000);
|
|
});
|
|
}
|
|
|
|
/* ============================================================
|
|
* 4. 3D 悬停倾斜 (vanilla tilt)
|
|
* 用法:<div data-jio-tilt data-tilt-max="15">...</div>
|
|
* ============================================================ */
|
|
function initTilt() {
|
|
if (reducedMotion) return;
|
|
|
|
document.querySelectorAll('[data-jio-tilt]').forEach(function (el) {
|
|
if (el.dataset.tiltBound) return;
|
|
el.dataset.tiltBound = '1';
|
|
|
|
var maxDeg = parseFloat(el.dataset.tiltMax || '10');
|
|
var perspective = parseFloat(el.dataset.tiltPerspective || '800');
|
|
var scale = parseFloat(el.dataset.tiltScale || '1.03');
|
|
var glare = el.dataset.tiltGlare === '1';
|
|
var glareEl = null;
|
|
|
|
if (glare) {
|
|
glareEl = document.createElement('div');
|
|
glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(135deg,rgba(255,255,255,0) 0%,rgba(255,255,255,0.25) 100%);opacity:0;transition:opacity 0.2s;';
|
|
el.style.position = el.style.position || 'relative';
|
|
el.style.overflow = 'hidden';
|
|
el.appendChild(glareEl);
|
|
}
|
|
|
|
el.addEventListener('mousemove', function (e) {
|
|
var rect = el.getBoundingClientRect();
|
|
var x = (e.clientX - rect.left) / rect.width;
|
|
var y = (e.clientY - rect.top) / rect.height;
|
|
var rotateX = (0.5 - y) * maxDeg;
|
|
var rotateY = (x - 0.5) * maxDeg;
|
|
el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')';
|
|
if (glareEl) glareEl.style.opacity = '1';
|
|
});
|
|
|
|
el.addEventListener('mouseleave', function () {
|
|
el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)';
|
|
if (glareEl) glareEl.style.opacity = '0';
|
|
});
|
|
});
|
|
}
|
|
|
|
/* ============================================================
|
|
* 5. FPS 检测 + 低端设备降级 — 只检测一次
|
|
* ============================================================ */
|
|
var fpsDetected = false;
|
|
|
|
function initFpsDetection() {
|
|
if (fpsDetected || !('requestAnimationFrame' in window)) return;
|
|
fpsDetected = true;
|
|
|
|
var frames = 0;
|
|
var startTime = performance.now();
|
|
|
|
function countFrames() {
|
|
frames++;
|
|
var elapsed = performance.now() - startTime;
|
|
if (elapsed >= 1000) {
|
|
var fps = Math.round(frames * 1000 / elapsed);
|
|
if (fps < 24) {
|
|
document.body.classList.add('fps-accelerat');
|
|
}
|
|
return; // 只检测一次
|
|
}
|
|
requestAnimationFrame(countFrames);
|
|
}
|
|
|
|
requestAnimationFrame(countFrames);
|
|
}
|
|
|
|
/* ============================================================
|
|
* 初始化入口 — 先执行一次,随后监听 auto_fun 重扫(各 init 幂等)
|
|
* ============================================================ */
|
|
function init() {
|
|
initCountUp();
|
|
initCountdown();
|
|
if (typeof initMarquee === 'function') {
|
|
initMarquee();
|
|
}
|
|
initTilt();
|
|
initFpsDetection();
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
if (window.jQuery) {
|
|
window.jQuery(document).on('auto_fun', init);
|
|
}
|
|
|
|
})();
|