Premium design & development studio, Dubai. Serving global clients since 2018. →
*
* FIX v9:
* - BLOCK 2: Cards default to position 0 (flush left) on load.
* Mouse parallax only activates once the cursor enters the slider.
* On mouseleave, cards smoothly return to position 0 (flush left).
*/
document.addEventListener('DOMContentLoaded', function () {
var SLIDE_DUR = 2000;
function cssVar(n, fb) {
return getComputedStyle(document.documentElement).getPropertyValue(n).trim() || fb;
}
/* BLOCK 1 — AUTO PROGRESS BAR + IMAGE CYCLING */
document.querySelectorAll('.hero_projects_card').forEach(function (card) {
var visual = card.querySelector('.hero_projects_visual');
var images = Array.from(card.querySelectorAll('.hero_projects_image'));
var logo = card.querySelector('.hero_projects_logo');
if (!visual || !images.length) return;
var old = visual.querySelectorAll('.hero_projects_progress, .n4-progress-bar');
old.forEach(function (el) { el.remove(); });
var bar = document.createElement('div');
bar.className = 'n4-progress-bar';
var fills = [];
for (var i = 0; i < images.length; i++) {
var seg = document.createElement('div'); seg.className = 'n4-pb-seg';
var fill = document.createElement('div'); fill.className = 'n4-pb-fill';
seg.appendChild(fill); bar.appendChild(seg); fills.push(fill);
}
visual.insertBefore(bar, visual.firstChild);
var cur = 0, timer = null;
function showSlide(idx) {
images.forEach(function (img, n) { img.style.opacity = n === idx ? '1' : '0'; });
fills.forEach(function (f, n) { f.style.transition = 'none'; f.style.width = n < idx ? '100%' : '0%'; });
requestAnimationFrame(function () {
requestAnimationFrame(function () {
fills[idx].style.transition = 'width ' + SLIDE_DUR + 'ms linear';
fills[idx].style.width = '100%';
});
});
}
function startCycle() {
clearTimeout(timer); showSlide(cur);
timer = setTimeout(function next() { cur = (cur + 1) % images.length; showSlide(cur); timer = setTimeout(next, SLIDE_DUR); }, SLIDE_DUR);
}
function stopCycle() {
clearTimeout(timer); cur = 0;
bar.style.opacity = '0';
if (logo) logo.style.opacity = '1';
images.forEach(function (img) { img.style.opacity = '0'; });
fills.forEach(function (f) { f.style.transition = 'none'; f.style.width = '0%'; });
card.querySelectorAll('video').forEach(function (v) { v.pause(); });
}
card.addEventListener('mouseenter', function () {
bar.style.opacity = '1';
if (logo) logo.style.opacity = '0';
card.querySelectorAll('video').forEach(function (v) { v.play().catch(function () {}); });
startCycle();
});
card.addEventListener('mouseleave', stopCycle);
});
/* BLOCK 2 — DESKTOP MOUSE PARALLAX (> 992px)
v9 FIX:
- Default position is 0 (cards flush left) — no movement on load.
- Parallax only runs while mouse is inside the slider.
- On mouseleave, cards snap back to 0 (flush left).
- Left edge = 0, right edge = -cachedHidden. Hard clamped. */
(function () {
var EDGE_PCT = 0.02; /* 2% inward on each side — set to 0 for flush */
var active = false, ready = false;
var cachedHidden = 0, lastListW = 0;
var edgeOffset = 0; /* px = list.offsetWidth * EDGE_PCT */
var pendingX = null, rafQueued = false;
var cards = [], list = null;
window.enableMouseFollow = function () {
ready = true;
if (window.innerWidth > 992) init();
};
function recache() {
if (!list || !cards.length) return;
var firstRect = cards[0].getBoundingClientRect();
var lastRect = cards[cards.length - 1].getBoundingClientRect();
cachedHidden = Math.max(0, (lastRect.right - firstRect.left) - list.offsetWidth);
edgeOffset = list.offsetWidth * EDGE_PCT;
lastListW = list.offsetWidth;
}
function resetCards() {
/* Snap all cards back to natural position (flush left) */
for (var i = 0; i < cards.length; i++) {
cards[i].style.transform = 'translateX(0px)';
}
pendingX = null;
}
function applyMove() {
rafQueued = false;
if (pendingX === null || !list || !active) return;
if (list.offsetWidth !== lastListW) recache();
var rect = list.getBoundingClientRect();
/* norm: -1 = far left edge, 0 = centre, +1 = far right edge */
var norm = Math.max(-1, Math.min(1,
(pendingX - rect.left - rect.width / 2) / (rect.width / 2)
));
/* Full symmetric map:
norm -1 -> mv = 0 (first card flush left)
norm 0 -> mv = -cachedHidden/2
norm +1 -> mv = -cachedHidden (last card flush right) */
var mv = ((norm + 1) / 2) * (-cachedHidden);
/* 2% inward clamp on both sides — same as v8:
left stop: -edgeOffset (2% in from first card)
right stop: -(cachedHidden - edgeOffset) (2% in from last card) */
if (cachedHidden > edgeOffset * 2) {
mv = Math.max(-(cachedHidden - edgeOffset), Math.min(-edgeOffset, mv));
} else {
mv = Math.max(-cachedHidden, Math.min(0, mv));
}
for (var i = 0; i < cards.length; i++) {
cards[i].style.transform = 'translateX(' + mv + 'px)';
}
}
function init() {
list = document.querySelector('[data-hero-slider]');
if (!list) return;
cards = Array.from(list.querySelectorAll('[data-hero-card]'));
if (!cards.length) {
cards = Array.from(list.children).filter(function (c) {
return c.tagName !== 'SCRIPT' && !c.classList.contains('u-display-none');
});
}
if (!cards.length) return;
/* Ensure cards start at position 0 */
resetCards();
if (document.readyState === 'complete') {
setTimeout(recache, 300);
} else {
window.addEventListener('load', function () { setTimeout(recache, 300); });
}
document.addEventListener('mousemove', function (e) {
if (!ready || window.innerWidth <= 992) return;
var rect = list.getBoundingClientRect();
var inside = e.clientX >= rect.left && e.clientX <= rect.right
&& e.clientY >= rect.top && e.clientY <= rect.bottom;
if (!inside) {
if (active) {
/* Mouse just left — reset cards to flush left */
active = false;
resetCards();
}
return;
}
active = true;
pendingX = e.clientX;
if (!rafQueued) { rafQueued = true; requestAnimationFrame(applyMove); }
});
}
window.addEventListener('resize', function () {
if (window.innerWidth > 992 && ready) { setTimeout(recache, 100); }
});
setTimeout(function () {
window.enableMouseFollow();
window.enableHeroSlider && window.enableHeroSlider();
}, 100);
})();
/* BLOCK 3 — MOBILE DRAG SLIDER (<= 992px) */
(function () {
if (window.__heroSliderInitialized_h7s9x) return;
window.__heroSliderInitialized_h7s9x = true;
var inst = null, ready = false;
window.enableHeroSlider = function () {
ready = true;
if (window.innerWidth <= 992) initSlider();
};
function initSlider() {
var list = document.querySelector('[data-hero-slider]');
if (!list || inst) return;
var autoplay = list.dataset.autoplay !== 'false';
var infinite = list.dataset.infinite !== 'false';
var canDrag = list.dataset.heroDraggable !== 'false';
var orig = Array.from(list.querySelectorAll('[data-hero-card]'));
if (!orig.length) {
Array.from(list.children).forEach(function (c) {
if (c.tagName !== 'SCRIPT' && !c.classList.contains('u-display-none'))
c.setAttribute('data-hero-card', '');
});
orig = Array.from(list.querySelectorAll('[data-hero-card]'));
}
if (!orig.length) return;
list.style.setProperty('gap', list.dataset.heroGap || '2rem');
if (infinite) {
orig.map(function (c) { return c.cloneNode(true); }).forEach(function (c) { list.appendChild(c); });
orig.map(function (c) { return c.cloneNode(true); }).forEach(function (c) { list.appendChild(c); });
}
var last = orig[orig.length - 1];
var lw = last.offsetLeft + last.offsetWidth - orig[0].offsetLeft;
var spd = parseFloat(list.dataset.heroSpeed) || 80;
var dirn = list.dataset.heroDirection || 'left';
var ppf = spd / 60;
var slow = parseFloat(list.dataset.heroSlowSpeed) || 0.2;
var hvr = list.dataset.heroHover || 'pause';
var DRAG_MULT = 1.4;
var st = { pos:0, drag:false, started:false, anim:autoplay, raf:null, mult:1 };
var sx=0, sy=0, ds=0, vt=[], ct=0, cx=0, cy=0;
function clamp(p) {
if (!infinite) { var mx=-(lw-list.offsetWidth); return Math.max(mx,Math.min(0,p)); }
while (p<-lw) p+=lw; while (p>0) p-=lw; return p;
}
function calcVel() {
if (vt.length<2) return 0;
var r=vt.slice(-5),tot=0;
for(var i=1;i