696 lines
19 KiB
JavaScript
696 lines
19 KiB
JavaScript
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
let intouchBtn = document.getElementById("get-in-touch");
|
|
let intouchPopup = document.getElementById("get-in-touch-page");
|
|
let closeBtns = document.getElementById("close-btn");
|
|
|
|
|
|
// Show login popup
|
|
intouchBtn.addEventListener("click", function () {
|
|
intouchPopup.classList.add("active");
|
|
// alert("clicked ")
|
|
|
|
});
|
|
|
|
// Hide both popups when clicking any close button
|
|
closeBtns.addEventListener("click", function () {
|
|
intouchPopup.classList.remove("active");
|
|
|
|
});
|
|
|
|
|
|
|
|
// Hide popups when clicking outside them
|
|
window.addEventListener("click", function (event) {
|
|
if (event.target.classList.contains("get-in-touch-page")) {
|
|
intouchPopup.classList.remove("active");
|
|
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
// university image animation
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
const section = document.getElementById('animated-section');
|
|
const leftItems = document.querySelectorAll('.left-group .line-item');
|
|
const rightItems = document.querySelectorAll('.right-group .line-item');
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
leftItems.forEach((item, i) => {
|
|
setTimeout(() => item.classList.add('visible'), i * 300);
|
|
});
|
|
rightItems.forEach((item, i) => {
|
|
setTimeout(() => item.classList.add('visible'), i * 300);
|
|
});
|
|
} else {
|
|
leftItems.forEach(item => item.classList.remove('visible'));
|
|
rightItems.forEach(item => item.classList.remove('visible'));
|
|
}
|
|
});
|
|
}, { threshold: 0.5 });
|
|
|
|
observer.observe(section);
|
|
|
|
});
|
|
|
|
|
|
|
|
// tabs in free resources
|
|
function showTab(id) {
|
|
// Remove active class from all buttons
|
|
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
|
|
// Hide all tab contents
|
|
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
|
|
|
|
// Show the selected tab
|
|
document.getElementById(id).classList.add('active');
|
|
}
|
|
|
|
// NEW: Attach click event on all tab buttons
|
|
document.querySelectorAll('.tab-btn').forEach(button => {
|
|
button.addEventListener('click', function () {
|
|
// Remove active from all
|
|
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
|
|
// Add active to clicked button
|
|
this.classList.add('active');
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
// counter section
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const counters = document.querySelectorAll(".counter");
|
|
|
|
const observer = new IntersectionObserver(entries => {
|
|
entries.forEach(entry => {
|
|
const counter = entry.target;
|
|
|
|
if (entry.isIntersecting) {
|
|
if (!counter.classList.contains('started')) {
|
|
startCounting(counter);
|
|
counter.classList.add('started');
|
|
}
|
|
} else {
|
|
resetCounter(counter);
|
|
counter.classList.remove('started');
|
|
}
|
|
});
|
|
});
|
|
|
|
counters.forEach(counter => {
|
|
observer.observe(counter);
|
|
});
|
|
|
|
function startCounting(counter) {
|
|
let start = 0;
|
|
const targetNumber = parseInt(counter.getAttribute("data-target"));
|
|
const duration = 2000; // 2 seconds
|
|
const increment = targetNumber / (duration / 16);
|
|
|
|
const timer = setInterval(() => {
|
|
start += increment;
|
|
if (start >= targetNumber) {
|
|
counter.textContent = targetNumber;
|
|
clearInterval(timer);
|
|
} else {
|
|
counter.textContent = Math.floor(start);
|
|
}
|
|
}, 16);
|
|
}
|
|
|
|
function resetCounter(counter) {
|
|
counter.textContent = 0;
|
|
}
|
|
});
|
|
|
|
|
|
// course finder ball bouncing
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
const canvas = document.getElementById('flagCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const ballSection = document.getElementById('ball-section');
|
|
|
|
// Get responsive ball size
|
|
function getBallSize() {
|
|
const width = window.innerWidth;
|
|
if (width < 480) return 20;
|
|
if (width < 768) return 30;
|
|
return 40;
|
|
}
|
|
|
|
let ballSize = getBallSize();
|
|
|
|
// Responsive canvas sizing
|
|
function resizeCanvas() {
|
|
canvas.width = ballSection.clientWidth;
|
|
canvas.height = ballSection.clientHeight;
|
|
ballSize = getBallSize();
|
|
}
|
|
resizeCanvas();
|
|
window.addEventListener('resize', resizeCanvas);
|
|
|
|
const gravity = 0.25;
|
|
const friction = 0.8;
|
|
const elasticity = 0.75;
|
|
const flags = ['us', 'gb', 'np', 'dk', 'ca', 'au'];
|
|
let balls = [];
|
|
|
|
class Ball {
|
|
constructor(x, y, radius, imageSrc) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.radius = radius;
|
|
this.dx = (Math.random() - 0.5) * 2;
|
|
this.dy = 0;
|
|
this.image = new Image();
|
|
this.image.src = imageSrc;
|
|
this.dragging = false;
|
|
this.offsetX = 0;
|
|
this.offsetY = 0;
|
|
this.elasticity = elasticity;
|
|
this.hasSettled = false;
|
|
}
|
|
|
|
draw() {
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
|
ctx.closePath();
|
|
ctx.clip();
|
|
|
|
const img = this.image;
|
|
const size = Math.min(img.width, img.height);
|
|
const sx = (img.width - size) / 2;
|
|
const sy = (img.height - size) / 2;
|
|
|
|
ctx.drawImage(
|
|
img, sx, sy, size, size,
|
|
this.x - this.radius, this.y - this.radius,
|
|
this.radius * 2, this.radius * 2
|
|
);
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
update(others) {
|
|
if (!this.dragging) {
|
|
this.dy += gravity;
|
|
this.x += this.dx;
|
|
this.y += this.dy;
|
|
|
|
// Floor
|
|
if (this.y + this.radius > canvas.height) {
|
|
this.y = canvas.height - this.radius;
|
|
this.dy *= -this.elasticity;
|
|
this.dx *= friction;
|
|
if (Math.abs(this.dy) < 0.5) {
|
|
this.dy = 0;
|
|
this.hasSettled = true;
|
|
}
|
|
}
|
|
|
|
// Walls
|
|
if (this.x - this.radius < 0) {
|
|
this.x = this.radius;
|
|
this.dx *= -friction;
|
|
} else if (this.x + this.radius > canvas.width) {
|
|
this.x = canvas.width - this.radius;
|
|
this.dx *= -friction;
|
|
}
|
|
}
|
|
|
|
// Collisions
|
|
if (this.dragging || !this.hasSettled) {
|
|
for (let other of others) {
|
|
if (other !== this) {
|
|
this.resolveBallCollision(other);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.draw();
|
|
}
|
|
|
|
resolveBallCollision(other) {
|
|
const dx = other.x - this.x;
|
|
const dy = other.y - this.y;
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
const minDist = this.radius + other.radius;
|
|
|
|
if (dist < minDist) {
|
|
const nx = dx / dist;
|
|
const ny = dy / dist;
|
|
const overlap = minDist - dist;
|
|
|
|
const impulse = 0.5;
|
|
this.x -= nx * overlap * impulse;
|
|
this.y -= ny * overlap * impulse;
|
|
other.x += nx * overlap * impulse;
|
|
other.y += ny * overlap * impulse;
|
|
|
|
const tx = -ny;
|
|
const ty = nx;
|
|
|
|
const dpTan1 = this.dx * tx + this.dy * ty;
|
|
const dpTan2 = other.dx * tx + other.dy * ty;
|
|
|
|
const dpNorm1 = this.dx * nx + this.dy * ny;
|
|
const dpNorm2 = other.dx * nx + other.dy * ny;
|
|
|
|
this.dx = tx * dpTan1 + nx * dpNorm2;
|
|
this.dy = ty * dpTan1 + ny * dpNorm2;
|
|
other.dx = tx * dpTan2 + nx * dpNorm1;
|
|
other.dy = ty * dpTan2 + ny * dpNorm1;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createBalls() {
|
|
balls = [];
|
|
for (let i = 0; i < 30; i++) {
|
|
const flag = flags[i % flags.length];
|
|
const x = Math.random() * (canvas.width - ballSize * 2) + ballSize;
|
|
const y = -Math.random() * 300;
|
|
balls.push(new Ball(x, y, ballSize, `assets/images/logo/country/${flag}.png`));
|
|
}
|
|
}
|
|
|
|
createBalls();
|
|
|
|
let draggedBall = null;
|
|
let lastMouse = { x: 0, y: 0 };
|
|
|
|
canvas.addEventListener('pointerdown', (e) => {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const scaleX = canvas.width / rect.width;
|
|
const scaleY = canvas.height / rect.height;
|
|
const mouseX = (e.clientX - rect.left) * scaleX;
|
|
const mouseY = (e.clientY - rect.top) * scaleY;
|
|
|
|
for (let ball of balls) {
|
|
const dx = mouseX - ball.x;
|
|
const dy = mouseY - ball.y;
|
|
if (Math.sqrt(dx * dx + dy * dy) < ball.radius) {
|
|
ball.dragging = true;
|
|
draggedBall = ball;
|
|
ball.offsetX = dx;
|
|
ball.offsetY = dy;
|
|
ball.hasSettled = false;
|
|
lastMouse = { x: mouseX, y: mouseY };
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
canvas.addEventListener('pointermove', (e) => {
|
|
if (draggedBall) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const scaleX = canvas.width / rect.width;
|
|
const scaleY = canvas.height / rect.height;
|
|
const mouseX = (e.clientX - rect.left) * scaleX;
|
|
const mouseY = (e.clientY - rect.top) * scaleY;
|
|
|
|
draggedBall.x = mouseX - draggedBall.offsetX;
|
|
draggedBall.y = mouseY - draggedBall.offsetY;
|
|
draggedBall.dx = (mouseX - lastMouse.x) * 0.4;
|
|
draggedBall.dy = (mouseY - lastMouse.y) * 0.4;
|
|
lastMouse = { x: mouseX, y: mouseY };
|
|
}
|
|
});
|
|
|
|
window.addEventListener('pointerup', () => {
|
|
if (draggedBall) {
|
|
draggedBall.dragging = false;
|
|
draggedBall = null;
|
|
}
|
|
});
|
|
|
|
// Animation loop
|
|
let animationId = null;
|
|
let isAnimating = false;
|
|
|
|
function animate() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
balls.forEach(ball => ball.update(balls));
|
|
animationId = requestAnimationFrame(animate);
|
|
}
|
|
|
|
// Intersection Observer
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
if (!isAnimating) {
|
|
createBalls();
|
|
isAnimating = true;
|
|
animate();
|
|
}
|
|
} else {
|
|
if (isAnimating) {
|
|
cancelAnimationFrame(animationId);
|
|
isAnimating = false;
|
|
}
|
|
}
|
|
});
|
|
}, { threshold: 0.1 });
|
|
|
|
observer.observe(ballSection);
|
|
});
|
|
|
|
|
|
|
|
// scroll detect in slider
|
|
// JS script
|
|
// JS for updating label on scroll
|
|
window.addEventListener('load', () => {
|
|
console.log("Window loaded. Running scroll indicator script.");
|
|
|
|
const scrollLabel = document.getElementById('scroll-indicator-label');
|
|
console.log("Scroll Label Element:", scrollLabel);
|
|
|
|
const sections = document.querySelectorAll('.scroll-section');
|
|
console.log("Found Sections:", sections);
|
|
|
|
if (!scrollLabel || sections.length === 0) {
|
|
console.error("Could not find scroll label or sections to observe. Exiting.");
|
|
return;
|
|
}
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
console.log("IntersectionObserver callback fired:", entries);
|
|
entries.forEach(entry => {
|
|
console.log("Observing entry:", entry.target, "Is intersecting:", entry.isIntersecting);
|
|
if (entry.isIntersecting) {
|
|
// --- CHANGE IS HERE ---
|
|
const sectionId = entry.target.id; // Get the ID of the intersecting section
|
|
// --- END OF CHANGE ---
|
|
|
|
console.log("Intersecting section ID:", sectionId);
|
|
|
|
if (sectionId) { // Check if ID exists
|
|
// Optional: Format the ID if needed (example: replace hyphens, capitalize)
|
|
// let displayText = sectionId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
// scrollLabel.textContent = displayText;
|
|
|
|
// Simple version: Use the ID directly
|
|
scrollLabel.textContent = sectionId;
|
|
console.log("Updated scroll label text to:", sectionId);
|
|
|
|
} else {
|
|
console.warn("Intersecting section is missing an ID attribute:", entry.target);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
{
|
|
threshold: 0.5 // Trigger when 50% of the section is visible
|
|
// Consider adjusting rootMargin if you have sticky headers/footers
|
|
// rootMargin: "-50px 0px -50px 0px" // Example: ignore top/bottom 50px
|
|
}
|
|
);
|
|
|
|
sections.forEach(section => {
|
|
if (!section.id) {
|
|
console.warn("Section is missing an ID, it won't update the label:", section);
|
|
}
|
|
console.log("Observing section:", section);
|
|
observer.observe(section);
|
|
});
|
|
|
|
console.log("Intersection observer setup complete.");
|
|
});
|
|
|
|
// Optional: Add a check outside the load event to see if the script file itself is loaded
|
|
console.log("Scroll indicator script file loaded.");
|
|
|
|
|
|
// aeroplane flying
|
|
const plane = document.getElementById("plane");
|
|
|
|
let planeX = 100;
|
|
let planeYBase = 200; // starting height
|
|
let vx;
|
|
|
|
if (window.innerWidth < 600) {
|
|
// mobile
|
|
vx = 1;
|
|
} else if (window.innerWidth < 1200) {
|
|
// tablet
|
|
vx = 2;
|
|
} else {
|
|
// desktop
|
|
vx = 3;
|
|
}
|
|
// horizontal speed
|
|
let direction = 1; // 1 = right, -1 = left
|
|
// const amplitude = 150;
|
|
let amplitude;
|
|
|
|
if (window.innerWidth < 768) {
|
|
// mobile
|
|
amplitude = 90;
|
|
} else {
|
|
// desktop
|
|
amplitude = 150;
|
|
}
|
|
// const numWaves = 2;
|
|
|
|
let numWaves;
|
|
|
|
if (window.innerWidth < 768) {
|
|
// mobile
|
|
numWaves = 1;
|
|
} else {
|
|
// desktop
|
|
numWaves = 3;
|
|
}
|
|
|
|
let dodgeOffset = 0;
|
|
const dodgeDecay = 0.9;
|
|
|
|
let downwardMovement = 0; // current Y offset
|
|
let targetDownwardMovement = 0; // target Y offset
|
|
let downwardStep = 200; // increase per edge hit
|
|
let goingDown = true;
|
|
|
|
function updatePosition() {
|
|
const w = window.innerWidth;
|
|
const h = document.body.scrollHeight;
|
|
const maxPlaneY = h - 200;
|
|
|
|
// bounce left/right
|
|
if (planeX <= 0 || planeX >= w - plane.width) {
|
|
vx *= -1;
|
|
direction *= -1;
|
|
plane.style.transform = `scaleX(${direction > 0 ? 1 : -1})`;
|
|
|
|
if (goingDown) {
|
|
targetDownwardMovement += downwardStep;
|
|
if (planeYBase + targetDownwardMovement >= maxPlaneY) {
|
|
targetDownwardMovement = maxPlaneY - planeYBase;
|
|
goingDown = false;
|
|
}
|
|
} else {
|
|
targetDownwardMovement -= downwardStep;
|
|
if (targetDownwardMovement <= 0) {
|
|
targetDownwardMovement = 0;
|
|
goingDown = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
planeX += vx;
|
|
|
|
// 🟢 Smoothly move downwardMovement towards targetDownwardMovement
|
|
downwardMovement += (targetDownwardMovement - downwardMovement) * 0.05;
|
|
|
|
// progress across screen
|
|
let progress = planeX / (w - plane.width);
|
|
if (direction < 0) {
|
|
progress = 1 - progress;
|
|
}
|
|
|
|
const angle = progress * numWaves * 2 * Math.PI;
|
|
const waveY = Math.sin(angle) * amplitude;
|
|
|
|
const finalY = planeYBase + downwardMovement + waveY + dodgeOffset;
|
|
|
|
plane.style.left = planeX + 'px';
|
|
plane.style.top = finalY + 'px';
|
|
|
|
dodgeOffset *= dodgeDecay;
|
|
|
|
requestAnimationFrame(updatePosition);
|
|
}
|
|
|
|
document.addEventListener("mousemove", function(e) {
|
|
const viewportOffset = plane.getBoundingClientRect();
|
|
const planeCenterX = viewportOffset.left + plane.width / 2;
|
|
const planeCenterY = viewportOffset.top + plane.height / 2;
|
|
const dx = e.clientX - planeCenterX;
|
|
const dy = e.clientY - planeCenterY;
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (dist < 100) {
|
|
dodgeOffset -= (dy / dist) * 10;
|
|
}
|
|
});
|
|
|
|
updatePosition();
|
|
|
|
|
|
|
|
// gallery image
|
|
const images = Array.from(document.querySelectorAll('.gallery img'));
|
|
const lightbox = document.getElementById('lightbox');
|
|
const lightboxImg = document.getElementById('lightbox-img');
|
|
let currentIndex = 0;
|
|
|
|
images.forEach((img, index) => {
|
|
img.addEventListener('click', () => {
|
|
currentIndex = index;
|
|
showImage();
|
|
});
|
|
});
|
|
|
|
function showImage() {
|
|
lightboxImg.src = images[currentIndex].src;
|
|
lightbox.classList.add('active');
|
|
}
|
|
|
|
function closeLightbox() {
|
|
lightbox.classList.remove('active');
|
|
}
|
|
|
|
function changeSlide(step) {
|
|
currentIndex = (currentIndex + step + images.length) % images.length;
|
|
showImage();
|
|
}
|
|
|
|
document.addEventListener('keydown', e => {
|
|
if (!lightbox.classList.contains('active')) return;
|
|
if (e.key === 'ArrowRight') changeSlide(1);
|
|
if (e.key === 'ArrowLeft') changeSlide(-1);
|
|
if (e.key === 'Escape') closeLightbox();
|
|
});
|
|
|
|
|
|
// cost calculator progress
|
|
|
|
let currentStep = 1;
|
|
const totalSteps = 5;
|
|
const monkey = document.getElementById('monkey').querySelector('img');
|
|
const monkeyContainer = document.getElementById('monkey');
|
|
const nextBtn = document.querySelector('.next-btn button');
|
|
const bananalast = document.getElementById('b5');
|
|
|
|
|
|
const monkeyImages = [
|
|
"assets/images/icons/monkey1.png",
|
|
"assets/images/icons/monkey2.png",
|
|
"assets/images/icons/monkey3.png",
|
|
"assets/images/icons/monkey4.png",
|
|
"assets/images/icons/monkey5.png",
|
|
"assets/images/icons/monkey6.png",
|
|
"assets/images/icons/monkey7.png",
|
|
];
|
|
|
|
nextBtn.addEventListener('click', () => {
|
|
if (currentStep < totalSteps) {
|
|
currentStep++;
|
|
|
|
// Move monkey
|
|
if (window.innerWidth <= 540) {
|
|
const percent = ((currentStep -1) / (totalSteps - 0.2)) * 100;
|
|
monkeyContainer.style.left = percent + '%';
|
|
}
|
|
else if (window.innerWidth <= 768) {
|
|
const percent = ((currentStep -1) / (totalSteps - 0.3)) * 100;
|
|
monkeyContainer.style.left = percent + '%';
|
|
}
|
|
else if (window.innerWidth <= 992) {
|
|
const percent = ((currentStep -1) / (totalSteps - 0.7)) * 100;
|
|
monkeyContainer.style.left = percent + '%';
|
|
}
|
|
else if (window.innerWidth <= 1180) {
|
|
const percent = ((currentStep -1) / (totalSteps - 0.2)) * 100;
|
|
monkeyContainer.style.left = percent + '%';
|
|
}
|
|
else{
|
|
const percent = ((currentStep -1) / (totalSteps - 0.5)) * 100;
|
|
monkeyContainer.style.left = percent + '%';
|
|
}
|
|
|
|
|
|
// Change monkey image
|
|
monkey.src = monkeyImages[currentStep - 1];
|
|
|
|
// Update step content
|
|
const currentContent = document.getElementById('step' + (currentStep - 1));
|
|
const nextContent = document.getElementById('step' + currentStep);
|
|
if (currentContent && nextContent) {
|
|
currentContent.classList.remove('active');
|
|
nextContent.classList.add('active');
|
|
}
|
|
|
|
// At final step (Step 5), hide Next and show Done
|
|
if (currentStep === totalSteps) {
|
|
nextBtn.style.display = 'none';
|
|
doneBtn.style.display = 'block';
|
|
}
|
|
}w
|
|
});
|
|
doneBtn.addEventListener('click', () => {
|
|
bananalast.classList.add('active');
|
|
|
|
if (window.innerWidth <= 992) {
|
|
// On mobile: show 7th image and move down
|
|
monkey.src = monkeyImages[6]; // 7th image (index 6)
|
|
monkeyContainer.style.top = '142%';
|
|
monkeyContainer.style.left = '40%'; // Optional: keep it centered or adjust as needed
|
|
} else {
|
|
// On desktop: show 6th image and move right
|
|
monkey.src = monkeyImages[5]; // 6th image (index 5)
|
|
monkeyContainer.style.left = '110%';
|
|
monkeyContainer.style.top = '-120%'; // Reset top if changed previously
|
|
}
|
|
});
|
|
|
|
// Final monkey image
|
|
|
|
|
|
|
|
|
|
// document.addEventListener("DOMContentLoaded", function () {
|
|
// const animatedSections = document.querySelectorAll('[data-custom-animations="true"]');
|
|
|
|
// const observer = new IntersectionObserver(entries => {
|
|
// entries.forEach(entry => {
|
|
// const el = entry.target;
|
|
|
|
// if (entry.isIntersecting) {
|
|
// el.classList.add("lqd-animations-done"); // Add class when in view
|
|
// console.log('added')
|
|
// } else {
|
|
// el.classList.remove("lqd-animations-done"); // Remove class when out of view
|
|
// console.log('removed')
|
|
// }
|
|
// });
|
|
// }, {
|
|
// threshold: 0.1 // You can adjust this if needed
|
|
// });
|
|
|
|
// animatedSections.forEach(section => observer.observe(section));
|
|
// });
|
|
|
|
|