/**
* Главная страница — интерактив, порт React-поведения на ванильный JS.
* Данные и SVG-иконки приходят из window.DOTA2_HOME (inc/enqueue.php).
*/
(function () {
'use strict';
var DATA = window.DOTA2_HOME || {};
var EASE = 'cubic-bezier(0.22, 1, 0.36, 1)';
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function icon(name, cls) {
var svg = (DATA.icons && DATA.icons[name]) || '';
if (!svg) return '';
return cls ? svg.replace('/g, '>').replace(/"/g, '"');
}
/* ---------------- reveal on scroll (порт motion whileInView) ---------------- */
(function initReveal() {
var els = document.querySelectorAll('[data-reveal]');
if (!els.length) return;
if (reduceMotion || typeof IntersectionObserver === 'undefined') return;
els.forEach(function (el) {
var type = el.getAttribute('data-reveal');
var y = parseFloat(el.getAttribute('data-reveal-y') || '0');
var x = parseFloat(el.getAttribute('data-reveal-x') || '0');
var dur = parseFloat(el.getAttribute('data-reveal-dur') || '400');
var delay = parseFloat(el.getAttribute('data-reveal-delay') || '0');
var amount = parseFloat(el.getAttribute('data-reveal-amount') || '0.15');
el.__reveal = { type: type, y: y, x: x, dur: dur, delay: delay, amount: amount };
if (type === 'bar') {
el.style.transform = 'scaleY(0)';
} else {
el.style.opacity = '0';
if (y || x) el.style.transform = 'translate(' + x + 'px,' + y + 'px)';
}
});
var reveal = function (entry, io) {
var el = entry.target;
var r = el.__reveal;
// как framer viewport.amount: показываем только когда видна заметная часть
if (!entry.isIntersecting || entry.intersectionRatio < r.amount) return;
io.unobserve(el);
el.style.transition = 'opacity ' + r.dur + 'ms ' + EASE + ' ' + r.delay + 'ms, transform ' + r.dur + 'ms ' + EASE + ' ' + r.delay + 'ms';
requestAnimationFrame(function () {
el.style.opacity = '';
el.style.transform = r.type === 'bar' ? 'scaleY(1)' : 'none';
if (el.querySelector) {
el.querySelectorAll('[data-podium-bar]').forEach(function (bar) {
bar.style.transition = 'transform 700ms ' + EASE + ' 150ms';
bar.style.transform = 'scaleX(1)';
});
}
window.setTimeout(function () {
el.style.transition = '';
el.style.transform = '';
}, r.dur + r.delay + 60);
});
};
var observers = {};
els.forEach(function (el) {
el.querySelectorAll && el.querySelectorAll('[data-podium-bar]').forEach(function (bar) {
bar.style.transform = 'scaleX(0)';
bar.style.transformOrigin = 'left';
});
var amount = el.__reveal.amount;
if (!observers[amount]) {
observers[amount] = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) { reveal(entry, observers[amount]); });
}, { threshold: amount });
}
observers[amount].observe(el);
});
})();
/* ---------------- animated metrics (порт animated-metric.tsx) ---------------- */
(function initMetrics() {
var els = document.querySelectorAll('[data-metric]');
if (!els.length || typeof IntersectionObserver === 'undefined') return;
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
io.unobserve(el);
var value = parseFloat(el.getAttribute('data-metric-value') || '0');
var prefix = el.getAttribute('data-metric-prefix') || '';
var suffix = el.getAttribute('data-metric-suffix') || '';
if (reduceMotion) {
el.textContent = prefix + value.toFixed(1) + suffix;
return;
}
var start = performance.now();
var duration = 700;
function tick(now) {
var p = Math.min(1, (now - start) / duration);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = prefix + (value * eased).toFixed(1) + suffix;
if (p < 1) requestAnimationFrame(tick);
}
el.textContent = prefix + '0.0' + suffix;
requestAnimationFrame(tick);
});
}, { threshold: 0.4 });
els.forEach(function (el) { io.observe(el); });
})();
/* ---------------- animated background (порт animated-dota-background.tsx) ---------------- */
(function initBackground() {
var root = document.querySelector('[data-dota-bg]');
if (!root) return;
if (!reduceMotion && root.querySelectorAll) {
root.querySelectorAll('[data-bg-pulse]').forEach(function (el) {
var parts = (el.getAttribute('data-bg-pulse') || '').split(',');
var dur = parseFloat(parts[0] || '18') * 1000;
var lo = parseFloat(parts[1] || '0.5');
var hi = parseFloat(parts[2] || '0.95');
if (el.animate) {
el.animate([{ opacity: lo }, { opacity: hi }, { opacity: lo }], { duration: dur, iterations: Infinity, easing: 'ease-in-out' });
}
});
}
var coarse = window.matchMedia('(pointer: coarse)').matches;
var mobile = window.matchMedia('(max-width: 767px)').matches;
if (reduceMotion || coarse || mobile) return;
var layers = root.querySelectorAll('[data-bg-parallax]');
var raf = 0;
window.addEventListener('mousemove', function (e) {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(function () {
var px = (e.clientX / window.innerWidth - 0.5) * 2;
var py = (e.clientY / window.innerHeight - 0.5) * 2;
layers.forEach(function (layer) {
var m = parseFloat(layer.getAttribute('data-bg-parallax') || '0');
layer.style.transition = 'transform 900ms cubic-bezier(0.16, 1, 0.3, 1)';
layer.style.transform = 'translate(' + px * m + 'px,' + py * m + 'px)';
});
});
}, { passive: true });
})();
/* ---------------- scroll down cue ---------------- */
(function initScrollCue() {
var btn = document.querySelector('[data-scroll-cue]');
if (!btn) return;
// Как в React (framer-motion): стрелка «плавает» независимо от prefers-reduced-motion.
var bob = btn.querySelector('[data-cue-bob]');
if (bob && bob.animate) {
bob.animate(
[
{ transform: 'translateY(0)', opacity: 0.8 },
{ transform: 'translateY(10px)', opacity: 1 },
{ transform: 'translateY(0)', opacity: 0.8 }
],
{ duration: 2800, iterations: Infinity, easing: 'ease-in-out' }
);
}
btn.addEventListener('click', function () {
var next = document.getElementById('tournaments') || document.querySelector('[data-next-section]');
if (next) {
var top = next.getBoundingClientRect().top + window.scrollY;
window.scrollTo({ top: Math.max(0, top - 80), behavior: 'smooth' });
return;
}
window.scrollBy({ top: window.innerHeight * 0.85, behavior: 'smooth' });
});
})();
/* ---------------- featured news carousel (порт featured-news-carousel.tsx) ---------------- */
(function initFeaturedCarousel() {
var root = document.querySelector('[data-featured-carousel]');
if (!root) return;
var track = root.querySelector('[data-carousel-track]');
var slides = root.querySelectorAll('[data-carousel-slide]');
var dots = root.querySelectorAll('[data-carousel-dot]');
var counter = root.querySelector('[data-carousel-counter]');
var delay = parseInt(root.getAttribute('data-autoplay') || '5000', 10);
var count = slides.length;
var current = 0;
var timer = 0;
var hovered = false;
var visible = true;
var DOT_ACTIVE = ['scale-110', 'border-accent-hot', 'bg-accent-hot', 'shadow-[0_0_10px_2px_color-mix(in_srgb,var(--accent-hot)_55%,transparent)]'];
var DOT_IDLE = ['border-white/25', 'bg-white/10', 'hover:border-[color-mix(in_srgb,var(--accent-hot)_60%,transparent)]', 'hover:bg-white/20'];
function pad(n) { return String(n).padStart(2, '0'); }
function render() {
// Движение трека — через Web Animations API, как embla в React (JS-движок,
// не зависит от глобального прибития CSS-transition при prefers-reduced-motion).
var target = 'translateX(-' + current * 100 + '%)';
if (track.animate && track.style.transform !== target) {
var from = getComputedStyle(track).transform;
track.style.transform = target;
track.animate(
[ { transform: 'none' === from ? 'translateX(0px)' : from }, { transform: target } ],
{ duration: 700, easing: EASE }
);
} else {
track.style.transform = target;
}
slides.forEach(function (slide, i) {
if (i === current) slide.removeAttribute('aria-hidden');
else slide.setAttribute('aria-hidden', 'true');
var img = slide.querySelector('[data-carousel-img]');
if (img) {
img.classList.toggle('scale-[1.05]', i === current);
img.classList.toggle('scale-[1.09]', i !== current);
}
});
dots.forEach(function (dot, i) {
var active = i === current;
dot.setAttribute('aria-selected', active ? 'true' : 'false');
DOT_ACTIVE.forEach(function (c) { dot.classList.toggle(c, active); });
DOT_IDLE.forEach(function (c) { dot.classList.toggle(c, !active); });
var ring = dot.querySelector('[data-carousel-ring]');
if (active && !ring) {
dot.insertAdjacentHTML('beforeend', '');
} else if (!active && ring) {
ring.remove();
}
});
if (counter) counter.textContent = pad(current + 1) + ' / ' + pad(count);
}
function goTo(i) {
current = (i + count) % count;
render();
}
function stop() { window.clearInterval(timer); timer = 0; }
function play() {
// Автоплей — как embla-carousel-autoplay в React: работает всегда.
stop();
timer = window.setInterval(function () {
if (!hovered && visible) goTo(current + 1);
}, delay);
}
root.querySelector('[data-carousel-prev]').addEventListener('click', function () { goTo(current - 1); play(); });
root.querySelector('[data-carousel-next]').addEventListener('click', function () { goTo(current + 1); play(); });
dots.forEach(function (dot, i) {
dot.addEventListener('click', function () { goTo(i); play(); });
});
root.addEventListener('mouseenter', function () { hovered = true; });
root.addEventListener('mouseleave', function () { hovered = false; });
root.addEventListener('focusin', function () { hovered = true; });
root.addEventListener('focusout', function () { hovered = false; });
if (typeof IntersectionObserver !== 'undefined') {
new IntersectionObserver(function (entries) {
visible = !!(entries[0] && entries[0].isIntersecting);
}, { threshold: 0.15 }).observe(root);
}
play();
})();
/* ---------------- match tabs (порт match-tabs.tsx) ---------------- */
(function initMatchTabs() {
var section = document.querySelector('[data-matches-section]');
if (!section) return;
var tabs = section.querySelectorAll('[data-match-tab]');
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var value = tab.getAttribute('data-match-tab');
tabs.forEach(function (t) {
var active = t === tab;
t.setAttribute('aria-selected', active ? 'true' : 'false');
t.classList.toggle('text-accent-hot', active);
t.classList.toggle('text-txt-3', !active);
t.classList.toggle('hover:text-txt-2', !active);
var ind = t.querySelector('[data-tab-indicator]');
if (ind) ind.hidden = !active;
});
section.querySelectorAll('[data-match-list]').forEach(function (list) {
list.hidden = list.getAttribute('data-match-list') !== value;
});
});
});
})();
/* ---------------- top heroes role filter ---------------- */
(function initTopHeroes() {
var section = document.querySelector('[data-top-heroes]');
if (!section) return;
var buttons = section.querySelectorAll('[data-top-role]');
var ACTIVE = ['bg-[color-mix(in_srgb,var(--accent-hot)_18%,transparent)]', 'text-txt-1', 'shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--accent-hot)_45%,transparent)]'];
var IDLE = ['text-txt-3', 'hover:bg-white/[0.04]', 'hover:text-txt-1'];
buttons.forEach(function (btn) {
btn.addEventListener('click', function () {
var role = btn.getAttribute('data-top-role');
buttons.forEach(function (b) {
var active = b === btn;
b.setAttribute('aria-pressed', active ? 'true' : 'false');
ACTIVE.forEach(function (c) { b.classList.toggle(c, active); });
IDLE.forEach(function (c) { b.classList.toggle(c, !active); });
});
section.querySelectorAll('[data-top-panel]').forEach(function (panel) {
var show = panel.getAttribute('data-top-panel') === role;
panel.hidden = !show;
if (show && !reduceMotion) {
panel.style.opacity = '0';
panel.style.transform = 'translateY(8px)';
panel.style.transition = 'opacity 300ms ' + EASE + ', transform 300ms ' + EASE;
requestAnimationFrame(function () {
panel.style.opacity = '1';
panel.style.transform = 'none';
});
window.setTimeout(function () {
panel.style.cssText = '';
}, 360);
}
});
section.querySelectorAll('[data-top-ranking]').forEach(function (list) {
list.hidden = list.getAttribute('data-top-ranking') !== role;
});
});
});
})();
/* ---------------- scroll-карусели (замена embla dragFree) ---------------- */
function bindScrollCarousel(root) {
var viewport = root.querySelector('[data-carousel-viewport]');
var prev = root.querySelector('[data-carousel-prev]');
var next = root.querySelector('[data-carousel-next]');
if (!viewport || !prev || !next) return null;
function sync() {
var max = viewport.scrollWidth - viewport.clientWidth - 1;
prev.disabled = viewport.scrollLeft <= 0;
next.disabled = viewport.scrollLeft >= max;
}
prev.addEventListener('click', function () {
viewport.scrollBy({ left: -Math.max(220, viewport.clientWidth * 0.6), behavior: reduceMotion ? 'auto' : 'smooth' });
});
next.addEventListener('click', function () {
viewport.scrollBy({ left: Math.max(220, viewport.clientWidth * 0.6), behavior: reduceMotion ? 'auto' : 'smooth' });
});
viewport.addEventListener('scroll', sync, { passive: true });
window.addEventListener('resize', sync);
sync();
return { sync: sync, viewport: viewport };
}
/* ---------------- trending builds ---------------- */
(function initBuilds() {
var section = document.querySelector('[data-trending-builds]');
if (!section) return;
var carousel = bindScrollCarousel(section.querySelector('[data-build-carousel]'));
var roleButtons = section.querySelectorAll('[data-build-role]');
var cards = section.querySelectorAll('[data-build-card]');
var wraps = section.querySelectorAll('[data-build-card-wrap]');
var panels = section.querySelectorAll('[data-build-panel]');
var empty = section.querySelector('[data-build-empty]');
var CARD_ON = ['border-[color-mix(in_srgb,var(--accent-hot)_60%,transparent)]', 'shadow-[0_0_22px_rgba(228,73,48,0.16)]'];
var CARD_OFF = ['border-white/8', 'hover:border-white/20'];
function selectBuild(id) {
cards.forEach(function (card) {
var on = card.getAttribute('data-build-card') === id;
card.setAttribute('aria-pressed', on ? 'true' : 'false');
CARD_ON.forEach(function (c) { card.classList.toggle(c, on); });
CARD_OFF.forEach(function (c) { card.classList.toggle(c, !on); });
});
var shown = null;
panels.forEach(function (panel) {
var on = panel.getAttribute('data-build-panel') === id;
panel.hidden = !on;
if (on) shown = panel;
});
if (empty) empty.hidden = !!shown;
if (shown && !reduceMotion) {
shown.style.opacity = '0';
shown.style.transform = 'translateX(10px)';
shown.style.transition = 'opacity 300ms ' + EASE + ', transform 300ms ' + EASE;
requestAnimationFrame(function () {
shown.style.opacity = '1';
shown.style.transform = 'none';
});
window.setTimeout(function () { shown.style.cssText = ''; }, 360);
}
}
cards.forEach(function (card) {
card.addEventListener('click', function () {
selectBuild(card.getAttribute('data-build-card'));
});
});
roleButtons.forEach(function (btn) {
btn.addEventListener('click', function () {
var role = btn.getAttribute('data-build-role');
roleButtons.forEach(function (b) {
var active = b === btn;
b.setAttribute('aria-pressed', active ? 'true' : 'false');
b.classList.toggle('text-accent-hot', active);
b.classList.toggle('text-txt-2', !active);
b.classList.toggle('hover:text-txt-1', !active);
var pill = b.querySelector('[data-role-pill]');
if (pill) pill.hidden = !active;
var svg = b.querySelector('svg');
if (svg) {
svg.classList.toggle('text-accent-hot', active);
svg.classList.toggle('text-txt-3', !active);
}
});
var first = null;
wraps.forEach(function (wrap) {
var show = role === 'all' || wrap.getAttribute('data-role') === role;
wrap.hidden = !show;
if (show && !first) {
var card = wrap.querySelector('[data-build-card]');
if (card) first = card.getAttribute('data-build-card');
}
});
if (first) {
selectBuild(first);
} else {
panels.forEach(function (panel) { panel.hidden = true; });
if (empty) empty.hidden = false;
}
if (carousel) carousel.sync();
});
});
})();
/* ---------------- counter picks ---------------- */
(function initCounters() {
var section = document.querySelector('[data-counter-picks]');
if (!section) return;
bindScrollCarousel(section.querySelector('[data-counter-carousel]'));
var tabs = section.querySelectorAll('[data-counter-metric]');
var track = section.querySelector('[data-counter-track]');
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var metric = tab.getAttribute('data-counter-metric');
tabs.forEach(function (t) {
var active = t === tab;
t.setAttribute('aria-pressed', active ? 'true' : 'false');
t.classList.toggle('text-txt-1', active);
t.classList.toggle('text-txt-3', !active);
t.classList.toggle('hover:text-txt-2', !active);
var pill = t.querySelector('[data-metric-pill]');
if (pill) pill.hidden = !active;
});
var items = Array.prototype.slice.call(track.querySelectorAll('[data-counter-item]'));
items.sort(function (a, b) {
var key = metric === 'winRate' ? 'data-win-rate' : 'data-pick-rate';
return parseFloat(b.getAttribute(key)) - parseFloat(a.getAttribute(key));
});
items.forEach(function (item) {
var value = metric === 'winRate' ? item.getAttribute('data-win-rate') : item.getAttribute('data-pick-rate');
item.querySelector('[data-counter-value]').textContent = value + '%';
item.querySelector('[data-counter-label]').textContent = metric === 'winRate' ? 'Win Rate' : 'Pick Rate';
track.appendChild(item);
});
});
});
section.querySelectorAll('[data-counter-filter]').forEach(function (cell) {
var toggle = cell.querySelector('[data-filter-toggle]');
var list = cell.querySelector('[data-filter-list]');
var value = cell.querySelector('[data-filter-value]');
toggle.addEventListener('click', function () {
var open = list.hidden;
list.hidden = !open;
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
});
cell.querySelectorAll('[data-filter-option]').forEach(function (opt) {
opt.addEventListener('click', function () {
var next = opt.getAttribute('data-filter-option');
value.textContent = next;
cell.querySelectorAll('[data-filter-option]').forEach(function (o) {
var on = o === opt;
o.classList.toggle('text-accent-hot', on);
o.classList.toggle('text-txt-2', !on);
});
list.hidden = true;
toggle.setAttribute('aria-expanded', 'false');
});
});
});
var method = section.querySelector('[data-counter-method]');
if (method) {
var mToggle = method.querySelector('[data-method-toggle]');
var mPanel = method.querySelector('[data-method-panel]');
var mIcon = mToggle.querySelector('svg:last-of-type');
mToggle.addEventListener('click', function () {
var open = mPanel.hidden;
mPanel.hidden = !open;
mToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
if (mIcon) mIcon.classList.toggle('rotate-45', open);
});
}
})();
/* ---------------- leaderboard (порт leaderboard-table.tsx) ---------------- */
(function initLeaderboard() {
var section = document.querySelector('[data-leaderboard]');
if (!section || !DATA.players) return;
var state = { region: 'all', role: 'all', sort: 'mmr', direction: 'desc' };
var tbody = section.querySelector('[data-lb-tbody]');
var mobile = section.querySelector('[data-lb-mobile]');
var emptyEl = section.querySelector('[data-lb-empty]');
var countEl = section.querySelector('[data-lb-count]');
var resetBtn = section.querySelector('[data-lb-reset]');
var COLS = 'grid grid-cols-[52px_minmax(150px,1.4fr)_minmax(130px,1fr)_84px_120px_104px_92px_140px_86px] items-center gap-3 px-4';
var fmt = function (n) { return Number(n).toLocaleString('en-US'); };
function rowTone(rank) {
if (rank === 1) return 'border-l-2 border-l-accent-hot bg-[linear-gradient(90deg,rgba(228,73,48,0.06),transparent_45%)]';
if (rank === 2) return 'border-l-2 border-l-white/20';
if (rank === 3) return 'border-l-2 border-l-[rgba(176,110,63,0.5)]';
return 'border-l-2 border-l-transparent';
}
function rankBadge(rank) {
if (rank > 3) return '' + rank + '';
var tone = rank === 1
? 'border-[rgba(212,165,86,0.55)] text-[#e3bd75] shadow-[0_0_14px_rgba(212,165,86,0.16)] bg-[linear-gradient(150deg,rgba(212,165,86,0.18),transparent)]'
: rank === 2
? 'border-white/22 text-[#d6d2cb] bg-[linear-gradient(150deg,rgba(255,255,255,0.10),transparent)]'
: 'border-[rgba(176,110,63,0.45)] text-[#c58a5c] bg-[linear-gradient(150deg,rgba(176,110,63,0.16),transparent)]';
return '' + rank + '';
}
function avatar(p) {
// Реальное фото игрока — приоритетно, иначе инициалы.
if (p.photo) {
return '';
}
return '';
}
function teamCell(p) {
if (!p.team) return 'Free agent';
var logo = p.team.logo
? ''
: '' + esc(p.team.name.slice(0, 2)) + '';
return '' + logo + '' + esc(p.team.name) + '';
}
function regionCell(region) {
var tone = (DATA.regionTone && DATA.regionTone[region]) || 'from-white/20 to-white/5';
return '' + esc(region) + '';
}
function roleCell(role) {
var meta = DATA.roleMeta[role];
return '' + icon(meta.icon, 'h-3.5 w-3.5 text-txt-3') + esc(meta.label) + '';
}
function formCell(form) {
var label = form.map(function (r) { return r === 'W' ? 'Win' : 'Loss'; }).join(', ');
var cells = form.map(function (r) {
var cls = r === 'W'
? 'border-[rgba(66,201,139,0.28)] bg-[rgba(66,201,139,0.12)] text-[#42c98b]'
: 'border-[rgba(228,73,48,0.28)] bg-[rgba(228,73,48,0.10)] text-[#e4795f]';
return '';
}).join('');
return '' + cells + '';
}
function trendCell(trend) {
if (trend === undefined || trend === null) return '—';
var pos = trend > 0;
var neg = trend < 0;
var cls = pos ? 'text-[#42c98b]' : neg ? 'text-[#e44930]' : 'text-txt-3';
var arrow = pos ? '▲' : neg ? '▼' : '—';
var value = pos ? '+' + fmt(trend) : fmt(trend);
return ' ' + value + '';
}
function filtered() {
var list = DATA.players.filter(function (p) {
return (state.region === 'all' || p.region === state.region) && (state.role === 'all' || p.role === state.role);
});
var value = function (p) {
return state.sort === 'mmr' ? (p.mmr || 0) : state.sort === 'winRate' ? (p.winRate || 0) : (p.trend || 0);
};
list.sort(function (a, b) { return state.direction === 'desc' ? value(b) - value(a) : value(a) - value(b); });
return list.slice(0, 10);
}
function render() {
var players = filtered();
tbody.innerHTML = players.map(function (p) {
return '
' + esc(hero.name) + '
' + '' + esc(hero.roleLabel) + '
' + '