(function () { 'use strict'; var ready = function (callback) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', callback); return; } callback(); }; function initPageLoader() { var loader = document.querySelector('.rensei-tech-loader'); if (!loader) { return; } var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; var minimumDisplay = reducedMotion ? 0 : Math.max(0, 650 - (window.performance ? window.performance.now() : 650)); window.setTimeout(function () { loader.classList.add('is-complete'); window.setTimeout(function () { if (loader.parentNode) { loader.parentNode.removeChild(loader); } }, reducedMotion ? 0 : 520); }, minimumDisplay); } function initReadingProgress() { var bar = document.querySelector('.rensei-reading-progress span'); var article = document.querySelector('.rensei-lab-article'); var ticking = false; if (!bar || !article) { return; } function update() { var rect = article.getBoundingClientRect(); var start = window.pageYOffset + rect.top - (window.innerHeight * 0.2); var end = start + article.offsetHeight - (window.innerHeight * 0.65); var progress = Math.max(0, Math.min(1, (window.pageYOffset - start) / Math.max(1, end - start))); bar.style.transform = 'scaleX(' + progress.toFixed(4) + ')'; ticking = false; } function requestUpdate() { if (!ticking) { window.requestAnimationFrame(update); ticking = true; } } window.addEventListener('scroll', requestUpdate, { passive: true }); window.addEventListener('resize', requestUpdate); update(); } function copyText(text) { if (navigator.clipboard && window.isSecureContext) { return navigator.clipboard.writeText(text); } return new Promise(function (resolve, reject) { var field = document.createElement('textarea'); field.value = text; field.setAttribute('readonly', ''); field.style.position = 'fixed'; field.style.opacity = '0'; document.body.appendChild(field); field.select(); try { document.execCommand('copy'); resolve(); } catch (error) { reject(error); } document.body.removeChild(field); }); } function initCopyLink() { var buttons = document.querySelectorAll('[data-rensei-copy-link]'); buttons.forEach(function (button) { button.addEventListener('click', function () { copyText(button.getAttribute('data-rensei-copy-link') || window.location.href).then(function () { var original = button.getAttribute('aria-label') || 'Sao chép liên kết'; button.classList.add('is-copied'); button.setAttribute('aria-label', 'Đã sao chép liên kết'); window.setTimeout(function () { button.classList.remove('is-copied'); button.setAttribute('aria-label', original); }, 1800); }); }); }); } function initConsultationButtons() { var panel = document.getElementById('rensei-consultation-form'); var panelToggle = panel ? panel.querySelector('.dang-ky') : null; function closePanel(restoreFocus) { if (!panel) { return; } panel.classList.remove('apperance-form'); document.body.classList.remove('rensei-consultation-open'); if (panelToggle) { panelToggle.setAttribute('aria-expanded', 'false'); if (restoreFocus) { panelToggle.focus(); } } } document.querySelectorAll('[data-rensei-consultation-open]').forEach(function (button) { button.addEventListener('click', function (event) { if (!panel) { return; } event.preventDefault(); panel.classList.add('apperance-form'); document.body.classList.add('rensei-consultation-open'); var toggle = panel.querySelector('.dang-ky'); if (toggle) { toggle.setAttribute('aria-expanded', 'true'); } window.setTimeout(function () { var field = panel.querySelector('input:not([type="hidden"]), textarea, select'); if (field) { field.focus(); } }, 250); }); }); document.querySelectorAll('[data-rensei-consultation-close]').forEach(function (button) { button.addEventListener('click', function () { closePanel(true); }); }); var overlay = document.querySelector('.panel-overlay'); if (overlay) { overlay.addEventListener('click', function () { closePanel(false); }); } } function initFacebookPage() { var container = document.querySelector('.rensei-facebook-embed[data-facebook-page]'); if (!container) { return; } function loadPage() { if (container.classList.contains('is-loaded')) { return; } var pageUrl = container.getAttribute('data-facebook-page'); var width = Math.max(280, Math.min(500, Math.round(container.getBoundingClientRect().width))); var iframe = document.createElement('iframe'); iframe.title = 'Fanpage Rensei Việt Nam'; iframe.loading = 'lazy'; iframe.scrolling = 'no'; iframe.frameBorder = '0'; iframe.allow = 'autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share'; iframe.src = 'https://www.facebook.com/plugins/page.php?href=' + encodeURIComponent(pageUrl) + '&tabs=timeline&width=' + width + '&height=214&small_header=true&adapt_container_width=true&hide_cover=false&show_facepile=true'; container.classList.add('is-loaded'); container.appendChild(iframe); } if ('IntersectionObserver' in window) { var observer = new IntersectionObserver(function (entries) { if (entries[0].isIntersecting) { loadPage(); observer.disconnect(); } }, { rootMargin: '300px 0px' }); observer.observe(container); } else { loadPage(); } } function initVisitorCounter() { var counter = document.querySelector('[data-rensei-visitor-count]'); if (!counter) { return; } function fetchCount() { var endpoint = counter.getAttribute('data-endpoint'); if (!endpoint || !window.fetch) { return; } var dayKey = 'rensei-visit-' + new Date().toISOString().slice(0, 10); var method = 'POST'; try { if (window.localStorage.getItem(dayKey)) { method = 'GET'; } } catch (error) { method = 'POST'; } fetch(endpoint, { method: method, credentials: 'same-origin', headers: { Accept: 'application/json' } }).then(function (response) { if (!response.ok) { throw new Error('Visitor counter request failed'); } return response.json(); }).then(function (payload) { if (typeof payload.total === 'number') { counter.textContent = new Intl.NumberFormat('vi-VN').format(payload.total); } if (method === 'POST') { try { window.localStorage.setItem(dayKey, '1'); } catch (error) { // The server still deduplicates anonymous daily visits. } } }).catch(function () { // Keep the server-rendered total when the API is unavailable. }); } if ('IntersectionObserver' in window) { var observer = new IntersectionObserver(function (entries) { if (entries[0].isIntersecting) { fetchCount(); observer.disconnect(); } }, { rootMargin: '400px 0px' }); observer.observe(counter); } else { fetchCount(); } } function initHomeSearchIntro() { var intro = document.querySelector('.home .rensei-home-search-intro'); var firstRow = document.querySelector('.home .entry-content > .vc_row'); if (intro && firstRow && firstRow.parentNode) { firstRow.parentNode.insertBefore(intro, firstRow.nextSibling); } } function initHomeJourney() { var main = document.querySelector('.home .site-main, .home #main'); var rows = main ? main.querySelectorAll('.vc_row') : []; var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (!main || rows.length < 2 || reducedMotion) { return; } var rail = document.createElement('div'); rail.className = 'rensei-journey-rail'; rail.setAttribute('aria-hidden', 'true'); rail.innerHTML = ''; main.insertBefore(rail, main.firstChild); var svg = rail.querySelector('svg'); var track = rail.querySelector('.rensei-journey-track'); var progressPath = rail.querySelector('.rensei-journey-progress'); var nodes = rail.querySelector('.rensei-journey-nodes'); var spark = rail.querySelector('.rensei-journey-spark'); var pathLength = 1; var ticking = false; function draw() { var mainRect = main.getBoundingClientRect(); var width = Math.max(1, main.clientWidth); var height = Math.max(1, main.scrollHeight); var leftA = width < 768 ? 18 : Math.max(34, width * 0.035); var leftB = width < 768 ? 42 : Math.max(72, width * 0.075); var points = []; rows.forEach(function (row, index) { var rect = row.getBoundingClientRect(); points.push({ x: index % 2 === 0 ? leftA : leftB, y: rect.top - mainRect.top + (rect.height / 2) }); }); var d = 'M ' + points[0].x + ' 0 L ' + points[0].x + ' ' + points[0].y; for (var index = 1; index < points.length; index += 1) { var previous = points[index - 1]; var point = points[index]; var middle = (previous.y + point.y) / 2; d += ' C ' + previous.x + ' ' + middle + ', ' + point.x + ' ' + middle + ', ' + point.x + ' ' + point.y; } d += ' L ' + points[points.length - 1].x + ' ' + height; svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height); track.setAttribute('d', d); progressPath.setAttribute('d', d); nodes.innerHTML = ''; points.forEach(function (point, index) { var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', point.x); circle.setAttribute('cy', point.y); circle.setAttribute('r', index % 3 === 0 ? '6' : '4'); nodes.appendChild(circle); }); pathLength = progressPath.getTotalLength() || 1; update(); } function update() { var rect = main.getBoundingClientRect(); var start = window.pageYOffset + rect.top - (window.innerHeight * 0.55); var end = start + main.scrollHeight - (window.innerHeight * 0.35); var amount = Math.max(0, Math.min(1, (window.pageYOffset - start) / Math.max(1, end - start))); var point = progressPath.getPointAtLength(pathLength * amount); progressPath.style.strokeDashoffset = (1 - amount).toFixed(4); spark.setAttribute('cx', point.x); spark.setAttribute('cy', point.y); rail.style.setProperty('--rensei-journey-progress', amount.toFixed(4)); ticking = false; } function requestUpdate() { if (!ticking) { window.requestAnimationFrame(update); ticking = true; } } window.addEventListener('scroll', requestUpdate, { passive: true }); window.addEventListener('resize', function () { window.requestAnimationFrame(draw); }); window.addEventListener('load', draw); if (document.fonts && document.fonts.ready) { document.fonts.ready.then(draw); } window.setTimeout(draw, 600); draw(); } ready(function () { initPageLoader(); initReadingProgress(); initCopyLink(); initConsultationButtons(); initFacebookPage(); initVisitorCounter(); initHomeSearchIntro(); initHomeJourney(); }); })();