Files
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
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>
2026-11-01 20:05:04 +08:00

273 lines
8.8 KiB
JavaScript

/**
* Jio Enhancements Part 2 — 高级交互组件
*
* 包含:3D 悬停倾斜、滚动触发动画、封面轮播、打赏按钮
*
* @package Jio
*/
(function () {
'use strict';
/* ============================================================
* 1. 3D 悬停倾斜效果 (vanilla-tilt 轻量版)
* ============================================================ */
function initTilt() {
document.querySelectorAll('[data-jio-tilt]').forEach(function (el) {
if (el.dataset.tiltInit) return;
el.dataset.tiltInit = '1';
var maxTilt = parseInt(el.dataset.jioTiltMax || '15', 10);
var perspective = parseInt(el.dataset.jioTiltPerspective || '1000', 10);
var scale = parseFloat(el.dataset.jioTiltScale || '1.03');
var speed = parseInt(el.dataset.jioTiltSpeed || '400', 10);
var glare = el.dataset.jioTiltGlare === '1';
el.style.transformStyle = 'preserve-3d';
el.style.transition = 'transform ' + speed + 'ms ease';
var glareEl = null;
if (glare) {
glareEl = document.createElement('div');
glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity ' + speed + 'ms;';
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 tiltX = (0.5 - y) * maxTilt;
var tiltY = (x - 0.5) * maxTilt;
el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + tiltX + 'deg) rotateY(' + tiltY + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')';
if (glareEl) {
glareEl.style.opacity = '1';
glareEl.style.background = 'linear-gradient(135deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%)';
glareEl.style.transform = 'translate(' + ((x - 0.5) * 100) + '%, ' + ((y - 0.5) * 100) + '%)';
}
});
el.addEventListener('mouseleave', function () {
el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)';
if (glareEl) glareEl.style.opacity = '0';
});
});
}
/* ============================================================
* 2. 滚动触发动画 (IntersectionObserver)
* ============================================================ */
function initScrollAnimations() {
if (!('IntersectionObserver' in window)) {
// 不支持时直接显示所有元素
document.querySelectorAll('[data-jio-animate]').forEach(function (el) {
el.style.opacity = '1';
el.style.transform = 'none';
});
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
var animation = el.dataset.jioAnimate || 'fadeInUp';
var delay = parseInt(el.dataset.jioAnimateDelay || '0', 10);
var duration = parseInt(el.dataset.jioAnimateDuration || '600', 10);
setTimeout(function () {
el.style.transition = 'opacity ' + duration + 'ms ease, transform ' + duration + 'ms ease';
el.style.opacity = '1';
el.style.transform = 'translateY(0) translateX(0) scale(1)';
el.classList.add('jio-animated');
}, delay);
observer.unobserve(el);
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px',
});
document.querySelectorAll('[data-jio-animate]').forEach(function (el) {
if (el.dataset.jioAnimateInit) return;
el.dataset.jioAnimateInit = '1';
var animation = el.dataset.jioAnimate || 'fadeInUp';
// 设置初始状态
el.style.opacity = '0';
switch (animation) {
case 'fadeIn':
el.style.transform = 'none';
break;
case 'fadeInUp':
el.style.transform = 'translateY(30px)';
break;
case 'fadeInDown':
el.style.transform = 'translateY(-30px)';
break;
case 'fadeInLeft':
el.style.transform = 'translateX(-30px)';
break;
case 'fadeInRight':
el.style.transform = 'translateX(30px)';
break;
case 'zoomIn':
el.style.transform = 'scale(0.9)';
break;
default:
el.style.transform = 'translateY(30px)';
}
observer.observe(el);
});
}
/* ============================================================
* 5. 粘性元素 (Sticky)
* ============================================================ */
function initSticky() {
document.querySelectorAll('[data-jio-sticky]').forEach(function (el) {
if (el.dataset.jioStickyInit) return;
el.dataset.jioStickyInit = '1';
var top = parseInt(el.dataset.jioStickyTop || '80', 10);
var offset = parseInt(el.dataset.jioStickyOffset || '0', 10);
function update() {
var rect = el.getBoundingClientRect();
var parentRect = el.parentElement.getBoundingClientRect();
if (parentRect.top < top && parentRect.bottom > el.offsetHeight + top) {
el.style.position = 'sticky';
el.style.top = top + 'px';
}
}
window.addEventListener('scroll', update, { passive: true });
});
}
/* ============================================================
* 6. 视差滚动
* ============================================================ */
var parallaxScrollBound = false;
function initParallax() {
if (!document.querySelector('[data-jio-parallax]')) return;
if (parallaxScrollBound) return;
parallaxScrollBound = true;
var ticking = false;
function update() {
document.querySelectorAll('[data-jio-parallax]').forEach(function (el) {
var speed = parseFloat(el.dataset.jioParallax || '0.3');
var rect = el.getBoundingClientRect();
var center = rect.top + rect.height / 2;
var windowCenter = window.innerHeight / 2;
var offset = (center - windowCenter) * speed;
el.style.transform = 'translateY(' + offset + 'px)';
});
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) {
requestAnimationFrame(update);
ticking = true;
}
}, { passive: true });
}
/* ============================================================
* 7. 涟漪点击效果 — document 委托只绑定一次
* ============================================================ */
var rippleBound = false;
function initRipple() {
if (rippleBound) return;
rippleBound = true;
document.addEventListener('click', function (e) {
var el = e.target.closest('.jio-ripple');
if (!el) return;
var rect = el.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
var size = Math.max(rect.width, rect.height) * 2;
var wave = document.createElement('span');
wave.className = 'jio-ripple__wave';
wave.style.width = wave.style.height = size + 'px';
wave.style.left = (x - size / 2) + 'px';
wave.style.top = (y - size / 2) + 'px';
el.appendChild(wave);
wave.addEventListener('animationend', function () {
wave.remove();
});
});
}
/* ============================================================
* 8. 表单浮动标签自动初始化
* ============================================================ */
function initFloatLabels() {
document.querySelectorAll('.jio-float-label__input').forEach(function (input) {
if (input.dataset.jioFloatInit) return;
input.dataset.jioFloatInit = '1';
// 检查初始值
if (input.value) {
input.setAttribute('data-has-value', '1');
}
input.addEventListener('input', function () {
if (this.value) {
this.setAttribute('data-has-value', '1');
} else {
this.removeAttribute('data-has-value');
}
});
});
}
/* ============================================================
* 初始化入口 — 先执行一次,随后监听 auto_fun 重扫(各 init 幂等)
* ============================================================ */
function init() {
initTilt();
initScrollAnimations();
initRipple();
initSticky();
initParallax();
initFloatLabels();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
if (window.jQuery) {
window.jQuery(document).on('auto_fun', init);
}
// 暴露到全局供动态内容使用
window.JioEnhancements = {
initTilt: initTilt,
initScrollAnimations: initScrollAnimations,
initRipple: initRipple,
};
})();