/**
* AKSRT Runtime Infrastructure
*
* Unified core primitives for the entire frontend:
* aksrt.tbquire(deps, cb) — lazy script/CSS loader
* aksrt.notyf(msg, opts) — unified toast (same-ID update)
* aksrt.confirm(msg, opts) — i18n confirm dialog
* aksrt.refreshModal(opts) — remote content modal
* aksrt.scrollTopTo(target, off) — smart scroll with header offset
* aksrt.loading(el, show) — element-level loading overlay
* aksrt.Drawer(opts) — mobile fullscreen drawer + route
* aksrt.route(url, title) — push/replace URL + title
*
* Declarative interactions (event delegation on body):
* [data-toggle-class] — toggle class on target
* [data-close] — close target element
* [data-onclick] — trigger click on another element
* [data-confirm] — confirm before action
* [data-action] — universal AJAX action button (like/follow/checkin)
* [ajax-action] — general AJAX button with modal response
*
* Body scroll direction bus:
* body.scroll-down / body.scroll-up (threshold 100px)
*
* @package AKSRT
* @since 5.0.0
*/
(function () {
'use strict';
var aksrt = window.aksrt || {};
window.aksrt = aksrt;
// ══════════════════════════════════════════════════════════════
// §10.11 — tbquire: lazy script/CSS loader
// ══════════════════════════════════════════════════════════════
var _loadedScripts = {};
var _loadingPromises = {};
function loadSingleScript(src) {
if (_loadedScripts[src]) return Promise.resolve();
if (_loadingPromises[src]) return _loadingPromises[src];
_loadingPromises[src] = new Promise(function (resolve, reject) {
var isCSS = /\.css(\?|$)/i.test(src);
var el;
if (isCSS) {
el = document.createElement('link');
el.rel = 'stylesheet';
el.href = src;
} else {
el = document.createElement('script');
el.src = src;
el.async = true;
}
el.onload = function () {
_loadedScripts[src] = true;
delete _loadingPromises[src];
resolve();
};
el.onerror = function () {
delete _loadingPromises[src];
reject(new Error('Failed to load: ' + src));
};
(document.head || document.documentElement).appendChild(el);
});
return _loadingPromises[src];
}
aksrt.tbquire = function (deps, callback) {
if (typeof deps === 'string') deps = [deps];
if (!Array.isArray(deps) || !deps.length) {
if (typeof callback === 'function') callback();
return Promise.resolve();
}
var resolvedDeps = deps.map(function (dep) {
if (/^https?:\/\/|^\//.test(dep)) return dep;
var map = (window.aksrtData && aksrtData.tbquireMap) || {};
return map[dep] || dep;
});
var p = Promise.all(resolvedDeps.map(loadSingleScript));
if (typeof callback === 'function') {
p.then(callback).catch(function (err) {
console.error('[AKSRT tbquire]', err);
});
}
return p;
};
// ══════════════════════════════════════════════════════════════
// §10.10 — notyf: unified toast with same-ID update
// ══════════════════════════════════════════════════════════════
var _notyfContainer = null;
var _notyfMap = {};
var NOTYF_DURATION = 5000;
function getNotyfContainer() {
if (_notyfContainer && _notyfContainer.parentNode) return _notyfContainer;
_notyfContainer = document.createElement('div');
_notyfContainer.className = 'aksrt-notyf';
_notyfContainer.setAttribute('role', 'alert');
_notyfContainer.setAttribute('aria-live', 'polite');
document.body.appendChild(_notyfContainer);
return _notyfContainer;
}
function createNotyfItem(msg, opts) {
var el = document.createElement('div');
el.className = 'aksrt-notyf__item aksrt-notyf--' + (opts.type || 'info');
if (opts.type === 'load') el.classList.add('aksrt-notyf--loading');
var iconMap = {
success: '',
danger: '',
error: '',
warning: '',
load: '',
info: ''
};
el.innerHTML =
'' + (iconMap[opts.type] || iconMap.info) + '' +
'' + escapeHtml(msg) + '' +
'';
el.querySelector('.aksrt-notyf__close').addEventListener('click', function () {
removeNotyf(el, opts.id);
});
return el;
}
function removeNotyf(el, id) {
el.classList.add('aksrt-notyf--out');
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 300);
if (id && _notyfMap[id]) {
clearTimeout(_notyfMap[id].timer);
delete _notyfMap[id];
}
}
aksrt.notyf = function (msg, opts) {
if (typeof opts === 'string') opts = { type: opts };
opts = opts || {};
var type = opts.type || 'info';
var duration = type === 'load' ? 0 : (opts.duration || NOTYF_DURATION);
var id = opts.id || null;
var container = getNotyfContainer();
if (id && _notyfMap[id]) {
var existing = _notyfMap[id];
clearTimeout(existing.timer);
existing.el.className = 'aksrt-notyf__item aksrt-notyf--' + type;
existing.el.querySelector('.aksrt-notyf__text').textContent = msg;
var iconSpan = existing.el.querySelector('.aksrt-notyf__icon');
var iconMap2 = {
success: '',
danger: '',
error: '',
warning: '',
load: '',
info: ''
};
iconSpan.innerHTML = iconMap2[type] || iconMap2.info;
if (duration > 0) {
existing.timer = setTimeout(function () { removeNotyf(existing.el, id); }, duration);
}
return existing.el;
}
var el = createNotyfItem(msg, { type: type, id: id });
container.appendChild(el);
requestAnimationFrame(function () {
el.classList.add('aksrt-notyf--in');
});
var timer = null;
if (duration > 0) {
timer = setTimeout(function () { removeNotyf(el, id); }, duration);
}
if (id) {
_notyfMap[id] = { el: el, timer: timer };
}
return el;
};
aksrt.notyf.close = function (id) {
if (_notyfMap[id]) removeNotyf(_notyfMap[id].el, id);
};
// Backward compat: wire window.aksrtToast to notyf
window.aksrtToast = function (msg, type) {
aksrt.notyf(msg, { type: type || 'info', duration: 2200 });
};
// ══════════════════════════════════════════════════════════════
// §10.10 — confirm: i18n confirm dialog + [data-confirm]
// ══════════════════════════════════════════════════════════════
var _i18n = (window.aksrtData && aksrtData.i18n) || {};
function __(key, fallback) {
return _i18n[key] || fallback || key;
}
aksrt.__ = __;
aksrt.confirm = function (msg, opts) {
opts = opts || {};
return new Promise(function (resolve) {
var overlay = document.createElement('div');
overlay.className = 'aksrt-confirm-overlay';
var dialog = document.createElement('div');
dialog.className = 'aksrt-confirm-dialog';
dialog.setAttribute('role', 'alertdialog');
dialog.setAttribute('aria-modal', 'true');
dialog.innerHTML =
'
' + escapeHtml(msg) + '
' +
'' +
'' +
'' +
'
';
overlay.appendChild(dialog);
document.body.appendChild(overlay);
requestAnimationFrame(function () { overlay.classList.add('is-open'); });
function close(result) {
overlay.classList.remove('is-open');
setTimeout(function () { overlay.remove(); }, 200);
resolve(result);
}
dialog.querySelector('.aksrt-confirm__cancel').addEventListener('click', function () { close(false); });
dialog.querySelector('.aksrt-confirm__ok').addEventListener('click', function () { close(true); });
overlay.addEventListener('click', function (e) { if (e.target === overlay) close(false); });
dialog.querySelector('.aksrt-confirm__ok').focus();
});
};
// ══════════════════════════════════════════════════════════════
// §10.10 — scrollTopTo: smart scroll with header offset
// ══════════════════════════════════════════════════════════════
aksrt.scrollTopTo = function (target, extraOffset) {
var top = 0;
if (typeof target === 'number') {
top = target;
} else if (target && target.getBoundingClientRect) {
top = target.getBoundingClientRect().top + window.pageYOffset;
}
var navFixed = document.querySelector('.site-nav.is-fixed, .site-header.is-fixed');
var headerOffset = navFixed ? (navFixed.offsetHeight + 20) : 0;
top = Math.max(0, top - headerOffset - (extraOffset || 0));
var current = window.pageYOffset;
if (Math.abs(top - current) < 80) return;
window.scrollTo({ top: top, behavior: 'smooth' });
};
// ══════════════════════════════════════════════════════════════
// §10.10 — loading: element-level loading overlay
// ══════════════════════════════════════════════════════════════
aksrt.loading = function (el, show) {
if (!el) return;
if (show === false) {
var mask = el.querySelector('.aksrt-loading-mask');
if (mask) {
mask.classList.add('aksrt-loading-mask--out');
setTimeout(function () { mask.remove(); }, 300);
}
return;
}
if (el.querySelector('.aksrt-loading-mask')) return;
var pos = getComputedStyle(el).position;
if (pos === 'static') el.style.position = 'relative';
var mask = document.createElement('div');
mask.className = 'aksrt-loading-mask';
mask.innerHTML = '';
el.appendChild(mask);
};
// ══════════════════════════════════════════════════════════════
// §10.10 — refreshModal: remote content modal
// ══════════════════════════════════════════════════════════════
var _refreshModalEl = null;
function getRefreshModalEl() {
if (_refreshModalEl && _refreshModalEl.parentNode) return _refreshModalEl;
_refreshModalEl = document.createElement('div');
_refreshModalEl.className = 'aksrt-refresh-modal';
_refreshModalEl.innerHTML =
'' +
'';
document.body.appendChild(_refreshModalEl);
_refreshModalEl.querySelector('.aksrt-refresh-modal__close').addEventListener('click', closeRefreshModal);
_refreshModalEl.querySelector('.aksrt-refresh-modal__overlay').addEventListener('click', function (e) {
if (e.target === e.currentTarget) closeRefreshModal();
});
return _refreshModalEl;
}
function closeRefreshModal() {
if (!_refreshModalEl) return;
_refreshModalEl.classList.remove('is-open', 'from-bottom');
document.body.classList.remove('modal-open');
setTimeout(function () {
var body = _refreshModalEl.querySelector('.aksrt-refresh-modal__body');
if (body) body.innerHTML = '';
}, 300);
}
aksrt.refreshModal = function (opts) {
opts = opts || {};
var modal = getRefreshModalEl();
var body = modal.querySelector('.aksrt-refresh-modal__body');
var title = modal.querySelector('.aksrt-refresh-modal__title');
if (opts.title) title.textContent = opts.title;
else title.textContent = '';
var isMobile = window.innerWidth < 768;
modal.classList.toggle('from-bottom', !!(opts.mobile_from_bottom && isMobile));
if (opts.class) modal.querySelector('.aksrt-refresh-modal__dialog').className = 'aksrt-refresh-modal__dialog ' + opts.class;
body.innerHTML = '';
aksrt.loading(body, true);
modal.classList.add('is-open');
document.body.classList.add('modal-open');
if (opts.content) {
aksrt.loading(body, false);
body.innerHTML = opts.content;
if (window.AksrtFrontend) AksrtFrontend.rehydrate(body, 'modal');
return Promise.resolve(modal);
}
if (opts.remote) {
return fetch(opts.remote, { credentials: 'same-origin' })
.then(function (r) { return r.text(); })
.then(function (html) {
aksrt.loading(body, false);
body.innerHTML = html;
if (window.AksrtFrontend) AksrtFrontend.rehydrate(body, 'modal');
return modal;
})
.catch(function (err) {
aksrt.loading(body, false);
body.innerHTML = '' + escapeHtml(err.message) + '
';
return modal;
});
}
if (opts.action) {
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
var params = new URLSearchParams();
params.set('action', opts.action);
if (window.aksrtData && aksrtData.nonce) params.set('nonce', aksrtData.nonce);
if (opts.data) {
Object.keys(opts.data).forEach(function (k) { params.set(k, opts.data[k]); });
}
return fetch(ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
credentials: 'same-origin',
body: params.toString()
})
.then(function (r) { return r.json(); })
.then(function (json) {
aksrt.loading(body, false);
if (json && json.data && json.data.html) {
body.innerHTML = json.data.html;
} else if (json && json.data && json.data.show_modal) {
body.innerHTML = json.data.show_modal;
} else {
body.innerHTML = '' + escapeHtml(json.data && json.data.message || '') + '
';
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(body, 'modal');
return modal;
})
.catch(function (err) {
aksrt.loading(body, false);
body.innerHTML = '' + escapeHtml(err.message) + '
';
return modal;
});
}
aksrt.loading(body, false);
return Promise.resolve(modal);
};
aksrt.refreshModal.close = closeRefreshModal;
// ══════════════════════════════════════════════════════════════
// §10.11 — Drawer: mobile fullscreen slide-in + route
// ══════════════════════════════════════════════════════════════
aksrt.Drawer = function (opts) {
opts = opts || {};
var drawerEl = document.createElement('div');
drawerEl.className = 'aksrt-drawer' + (opts.class ? ' ' + opts.class : '');
drawerEl.innerHTML =
'' +
'';
var body = drawerEl.querySelector('.aksrt-drawer__body');
if (opts.content) {
body.innerHTML = opts.content;
} else if (opts.remote) {
aksrt.loading(body, true);
fetch(opts.remote, { credentials: 'same-origin' })
.then(function (r) { return r.text(); })
.then(function (html) {
aksrt.loading(body, false);
body.innerHTML = html;
if (window.AksrtFrontend) AksrtFrontend.rehydrate(body, 'drawer');
})
.catch(function () { aksrt.loading(body, false); });
}
document.body.appendChild(drawerEl);
document.body.classList.add('drawer-open');
requestAnimationFrame(function () { drawerEl.classList.add('is-open'); });
if (opts.route) {
aksrt.route(opts.route, opts.title);
}
function close() {
drawerEl.classList.remove('is-open');
document.body.classList.remove('drawer-open');
setTimeout(function () { drawerEl.remove(); }, 300);
if (opts.route) {
window.history.back();
}
if (typeof opts.onClose === 'function') opts.onClose();
}
drawerEl.querySelector('.aksrt-drawer__back').addEventListener('click', close);
// minitouch right-swipe close
if (window.aksrt.minitouch) {
aksrt.minitouch(drawerEl, {
direction: 'right',
onEnd: close
});
}
return { el: drawerEl, body: body, close: close };
};
// ══════════════════════════════════════════════════════════════
// §10.11 — route: tab route + popstate
// ══════════════════════════════════════════════════════════════
aksrt.route = function (url, title) {
if (!window.history || !history.pushState) return;
history.pushState({ aksrt: true }, title || '', url);
if (title) document.title = title;
};
aksrt.routeReplace = function (url, title) {
if (!window.history || !history.replaceState) return;
history.replaceState({ aksrt: true }, title || '', url);
if (title) document.title = title;
};
// Tab route: elements with [data-route] push URL on activation
window.addEventListener('popstate', function (e) {
var routeTabs = document.querySelectorAll('[data-route]');
if (!routeTabs.length) return;
var path = window.location.pathname + window.location.search;
routeTabs.forEach(function (tab) {
var tabRoute = tab.getAttribute('data-route');
var isMatch = path === tabRoute || path.indexOf(tabRoute) === 0;
tab.classList.toggle('is-active', isMatch);
var target = tab.getAttribute('data-route-target');
if (target) {
var panel = document.querySelector(target);
if (panel) panel.classList.toggle('is-active', isMatch);
}
});
});
// ══════════════════════════════════════════════════════════════
// §10.2 — Unified body scroll direction bus
// ══════════════════════════════════════════════════════════════
var _lastScrollTop = 0;
var _scrollTicking = false;
var SCROLL_BUS_THRESHOLD = 100;
function updateScrollBus() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var body = document.body;
if (scrollTop > SCROLL_BUS_THRESHOLD) {
body.classList.add('body-scroll');
} else {
body.classList.remove('body-scroll');
}
if (scrollTop > _lastScrollTop + 10) {
body.classList.add('scroll-down');
body.classList.remove('scroll-up');
} else if (scrollTop < _lastScrollTop - 10) {
body.classList.remove('scroll-down');
body.classList.add('scroll-up');
}
_lastScrollTop = Math.max(0, scrollTop);
_scrollTicking = false;
}
window.addEventListener('scroll', function () {
if (!_scrollTicking) {
_scrollTicking = true;
requestAnimationFrame(updateScrollBus);
}
}, { passive: true });
// ══════════════════════════════════════════════════════════════
// §10.11 — Declarative interactions (event delegation)
// ══════════════════════════════════════════════════════════════
document.addEventListener('click', function (e) {
var target = e.target.closest('[data-toggle-class]');
if (target) {
e.preventDefault();
var cls = target.getAttribute('data-toggle-class');
var sel = target.getAttribute('data-target') || target.getAttribute('data-toggle-target');
var el = sel ? document.querySelector(sel) : target;
if (el && cls) el.classList.toggle(cls);
return;
}
target = e.target.closest('[data-close]');
if (target) {
e.preventDefault();
var sel2 = target.getAttribute('data-close');
var el2 = sel2 ? document.querySelector(sel2) : target.parentElement;
if (el2) {
el2.classList.remove('is-open', 'is-active', 'is-visible');
el2.hidden = true;
}
return;
}
target = e.target.closest('[data-onclick]');
if (target) {
e.preventDefault();
var sel3 = target.getAttribute('data-onclick');
var el3 = document.querySelector(sel3);
if (el3) el3.click();
return;
}
});
// [data-confirm] — intercept submission
document.addEventListener('click', function (e) {
var target = e.target.closest('[data-confirm]');
if (!target) return;
var msg = target.getAttribute('data-confirm');
if (!msg) return;
e.preventDefault();
e.stopPropagation();
aksrt.confirm(msg).then(function (ok) {
if (!ok) return;
target.removeAttribute('data-confirm');
target.click();
target.setAttribute('data-confirm', msg);
});
}, true);
// ══════════════════════════════════════════════════════════════
// §10.11 — [data-action] / [ajax-action] universal buttons
// ══════════════════════════════════════════════════════════════
document.addEventListener('click', function (e) {
// [data-action] — like/follow/checkin with localStorage de-dup
var btn = e.target.closest('[data-action]');
if (btn) {
e.preventDefault();
handleDataAction(btn);
return;
}
// [ajax-action] — general AJAX button
btn = e.target.closest('[ajax-action]');
if (btn) {
e.preventDefault();
handleAjaxAction(btn);
return;
}
});
function handleDataAction(btn) {
if (btn.classList.contains('is-loading')) return;
var action = btn.getAttribute('data-action');
var id = btn.getAttribute('data-id') || '';
var storageKey = 'aksrt_action_' + action + '_' + id;
if (action === 'like' || action === 'favorite') {
try {
if (localStorage.getItem(storageKey) && !btn.classList.contains('is-active')) {
aksrt.notyf(__('already_done', '已操作'), 'warning');
return;
}
} catch (ex) { /* storage unavailable */ }
}
btn.classList.add('is-loading');
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
var params = new URLSearchParams();
params.set('action', 'aksrt_' + action);
if (window.aksrtData && aksrtData.nonce) params.set('nonce', aksrtData.nonce);
params.set('id', id);
var extraData = btn.getAttribute('data-action-data');
if (extraData) {
try {
var ed = JSON.parse(extraData);
Object.keys(ed).forEach(function (k) { params.set(k, ed[k]); });
} catch (ex) { /* ignore */ }
}
fetch(ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
credentials: 'same-origin',
body: params.toString()
})
.then(function (r) { return r.json(); })
.then(function (json) {
btn.classList.remove('is-loading');
if (json && json.success) {
var isActive = !!json.data && !!json.data.active;
btn.classList.toggle('is-active', isActive);
var countEl = btn.querySelector('[data-count]') || btn.querySelector('.action-count');
if (countEl && json.data && json.data.count !== undefined) {
countEl.textContent = String(json.data.count);
}
try {
if (isActive) localStorage.setItem(storageKey, '1');
else localStorage.removeItem(storageKey);
} catch (ex) { /* */ }
if (json.data && json.data.message) {
aksrt.notyf(json.data.message, 'success');
}
if (json.data && json.data.goto) {
window.location.href = json.data.goto;
}
} else {
aksrt.notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
}
})
.catch(function (err) {
btn.classList.remove('is-loading');
aksrt.notyf(err.message || __('network_error', '网络错误'), 'danger');
});
}
function handleAjaxAction(btn) {
if (btn.classList.contains('is-loading')) return;
btn.classList.add('is-loading');
var action = btn.getAttribute('ajax-action');
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
var params = new URLSearchParams();
params.set('action', action);
if (window.aksrtData && aksrtData.nonce) params.set('nonce', aksrtData.nonce);
var form = btn.closest('form');
if (form) {
new FormData(form).forEach(function (v, k) { params.set(k, v); });
}
fetch(ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
credentials: 'same-origin',
body: params.toString()
})
.then(function (r) { return r.json(); })
.then(function (json) {
btn.classList.remove('is-loading');
if (json && json.success) {
if (json.data && json.data.show_modal) {
aksrt.refreshModal({ content: json.data.show_modal, mobile_from_bottom: true });
} else if (json.data && json.data.message) {
aksrt.notyf(json.data.message, 'success');
}
if (json.data && json.data.goto) {
window.location.href = json.data.goto;
}
if (json.data && json.data.reload) {
window.location.reload();
}
} else {
aksrt.notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
}
})
.catch(function (err) {
btn.classList.remove('is-loading');
aksrt.notyf(err.message || __('network_error', '网络错误'), 'danger');
});
}
// ══════════════════════════════════════════════════════════════
// §10.11 — Ripple (broad coverage)
// ══════════════════════════════════════════════════════════════
var RIPPLE_SELECTORS = '.toggle-radius,.but-ripple,.but,.btn,.btn-primary,.btn-ghost,.btn-outline,.item-thumbnail,.menu-item>a,.aksrt-ripple,.yiyan-box,.aksrt-card';
document.addEventListener('click', function (e) {
var target = e.target.closest(RIPPLE_SELECTORS);
if (!target) return;
if (target.querySelector('.aksrt-wave')) return;
var rect = target.getBoundingClientRect();
var size = Math.max(rect.width, rect.height) * 2;
var x = e.clientX - rect.left - size / 2;
var y = e.clientY - rect.top - size / 2;
var wave = document.createElement('span');
wave.className = 'aksrt-wave';
wave.style.cssText = 'width:' + size + 'px;height:' + size + 'px;left:' + x + 'px;top:' + y + 'px;';
var pos = getComputedStyle(target).position;
if (pos === 'static') target.style.position = 'relative';
target.appendChild(wave);
setTimeout(function () { wave.remove(); }, 600);
});
// ══════════════════════════════════════════════════════════════
// §10.11 — auto_fun: extended rehydration coverage
// ══════════════════════════════════════════════════════════════
aksrt.autoFun = function (root) {
root = root || document;
if (window.AksrtFrontend) {
AksrtFrontend.rehydrate(root, 'ajax');
}
// Tooltips
root.querySelectorAll('[data-tooltip]').forEach(function (el) {
if (el.dataset.tooltipBound) return;
el.dataset.tooltipBound = '1';
el.addEventListener('mouseenter', function () {
var tip = document.createElement('div');
tip.className = 'aksrt-tooltip';
tip.textContent = el.getAttribute('data-tooltip');
document.body.appendChild(tip);
var rect = el.getBoundingClientRect();
tip.style.left = (rect.left + rect.width / 2 - tip.offsetWidth / 2) + 'px';
tip.style.top = (rect.top - tip.offsetHeight - 8 + window.pageYOffset) + 'px';
el._aksrtTooltip = tip;
});
el.addEventListener('mouseleave', function () {
if (el._aksrtTooltip) { el._aksrtTooltip.remove(); el._aksrtTooltip = null; }
});
});
// Lazy images
root.querySelectorAll('img.aksrt-lazy[data-src]').forEach(function (img) {
if (img.classList.contains('aksrt-loaded')) return;
if ('IntersectionObserver' in window) {
var obs = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
img.src = img.dataset.src;
img.classList.remove('aksrt-lazy');
img.classList.add('aksrt-loaded');
obs.unobserve(img);
});
}, { rootMargin: '200px' });
obs.observe(img);
} else {
img.src = img.dataset.src;
img.classList.remove('aksrt-lazy');
img.classList.add('aksrt-loaded');
}
});
// Swiper re-init
if (typeof Swiper !== 'undefined') {
root.querySelectorAll('[data-swiper-config]:not(.swiper-initialized)').forEach(function (el) {
try {
var cfg = JSON.parse(el.dataset.swiperConfig || '{}');
new Swiper(el, cfg);
} catch (ex) { /* */ }
});
}
// Countdowns
root.querySelectorAll('[data-countdown]').forEach(function (el) {
if (el.dataset.countdownBound) return;
el.dataset.countdownBound = '1';
var end = parseInt(el.getAttribute('data-countdown'), 10);
if (!end) return;
var iv = setInterval(function () {
var left = Math.max(0, end - Math.floor(Date.now() / 1000));
if (left <= 0) { clearInterval(iv); el.textContent = '00:00'; return; }
var m = Math.floor(left / 60);
var s = left % 60;
el.textContent = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
}, 1000);
});
};
// ══════════════════════════════════════════════════════════════
// §10.11 — minitouch: general direction gesture
// ══════════════════════════════════════════════════════════════
aksrt.minitouch = function (el, opts) {
opts = opts || {};
var direction = opts.direction || 'any';
var startX = 0, startY = 0, currentX = 0, currentY = 0;
var active = false;
el.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1) return;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
currentX = startX;
currentY = startY;
active = true;
if (typeof opts.onStart === 'function') {
var stop = opts.onStart({ x: startX, y: startY, el: el });
if (stop === false) active = false;
}
}, { passive: true });
el.addEventListener('touchmove', function (e) {
if (!active) return;
currentX = e.touches[0].clientX;
currentY = e.touches[0].clientY;
var dx = currentX - startX;
var dy = currentY - startY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) return;
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
var dir = getDirection(angle);
if (direction !== 'any' && dir !== direction) {
if (typeof opts.stop === 'function' && opts.stop({ direction: dir, dx: dx, dy: dy })) {
active = false;
return;
}
}
if (typeof opts.onIng === 'function') {
opts.onIng({ dx: dx, dy: dy, direction: dir, distance: dist, el: el });
}
}, { passive: true });
el.addEventListener('touchend', function () {
if (!active) return;
active = false;
var dx = currentX - startX;
var dy = currentY - startY;
var dist = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) * 180 / Math.PI;
var dir = getDirection(angle);
if (typeof opts.onEnd === 'function') {
opts.onEnd({ dx: dx, dy: dy, direction: dir, distance: dist, el: el });
}
}, { passive: true });
};
function getDirection(angle) {
if (angle >= -45 && angle < 45) return 'right';
if (angle >= 45 && angle < 135) return 'bottom';
if (angle >= -135 && angle < -45) return 'top';
return 'left';
}
// ══════════════════════════════════════════════════════════════
// Helpers
// ══════════════════════════════════════════════════════════════
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
// ── DOMContentLoaded ──
function initRuntime() {
updateScrollBus();
aksrt.autoFun(document);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initRuntime);
} else {
initRuntime();
}
})();