Files
2026-08-04 11:01:46 +08:00

266 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const menuButton = document.querySelector('.menu-toggle');
const nav = document.querySelector('.nav');
const header = document.querySelector('.site-header');
const scrollProgress = document.querySelector('.scroll-progress');
const EN_TO_ZH = {
'Opening October 2027': '预计 2027 年 10 月开业',
'Old Klang Road, Kuala Lumpur': '吉隆坡旧巴生路',
'Venue': '场馆介绍', 'Courts': '球场', 'Spaces': '空间规划', 'Academy': '羽球学院', 'Community': '社区活动', 'Contact': '联系我们',
'Register interest': '登记意向', 'Register interest →': '发送邮件咨询 →',
"Kuala Lumpur's new court-sports destination": '吉隆坡全新综合球类运动目的地',
'Your court.': '你的球场。', 'Your community.': '你的社区。',
'A purpose-built home for badminton, pickleball and tennis - created for players, families, juniors and teams.': '为羽毛球、匹克球与网球量身打造,为球员、家庭、青少年和团队提供共同的运动主场。',
'Explore academy': '了解学院', 'Three sports': '三种运动', 'One home': '一个主场',
'Grand opening': '预计开业', 'October 2027': '2027 年 10 月',
'Indoor': '室内', 'badminton courts': '羽毛球场', 'pickleball courts': '匹克球场', 'tennis courts': '网球场',
'Professional spaces.': '专业运动空间。', 'Designed for everyone.': '为每一个人打造。',
'The venue': '场馆介绍', 'Built around how': '真正围绕', 'people': '人们的', 'really play.': '运动方式打造。',
"A modern indoor sports centre planned for one of Kuala Lumpur's most established and connected neighbourhoods.": '一座规划于吉隆坡成熟、交通便利社区中的现代室内体育中心。',
'From casual evening games to focused academy training, every floor brings courts, support spaces and community amenities together in one accessible destination.': '从轻松的晚间约球到系统的学院训练,每一层都把球场、辅助空间与社区配套集中在同一个便捷目的地。',
'Entrance perspective': '入口效果图', 'Front elevation': '建筑正立面', 'Street context': '街景效果图',
'Inside the venue': '走进场馆', 'Step inside the': '走进', 'game.': '运动现场。',
'Explore the indoor court environments referenced in the proposal - spaces planned for everyday play, coaching and competition.': '查看提案中的场内环境,为日常约球、专业训练和赛事活动规划的运动空间。',
'Indoor badminton courts': '室内羽毛球场', 'Pickleball courts': '匹克球场', 'Indoor tennis courts': '室内网球场', 'Indoor multi-court space': '室内多用途球场', 'Outdoor tennis courts': '室外网球场',
'Three ways to move': '三种运动方式', 'Choose your': '选择你的', 'court.': '球场。',
'Dedicated indoor spaces planned for year-round play, training and competition.': '为全年运动、训练和比赛规划的专业室内空间。',
'14 courts': '14 个球场', '5 courts': '5 个球场', '4 courts': '4 个球场',
'01 / Badminton': '01 / 羽毛球', '02 / Pickleball': '02 / 匹克球', '03 / Tennis': '03 / 网球',
'Fast. Focused.': '快速。专注。', 'Made for Malaysia.': '为马来西亚而生。',
'Fourteen indoor courts form the heart of the venue, supporting everyday bookings, academy programmes and tournament play.': '14 个室内羽毛球场构成场馆核心,满足日常预约、学院课程及赛事活动。',
'Register for court updates →': '登记球场资讯 →',
'Social by nature.': '天生适合社交。', 'Serious when you are.': '也能认真竞技。',
'Five indoor pickleball courts - convertible for badminton - give new players and regular groups room to play their way.': '5 个室内匹克球场可转换为羽毛球场,让新手与固定球友都能自在开打。',
'Join the community →': '加入社区 →',
'More court time.': '更多上场时间。', 'Whatever the weather.': '无惧天气变化。',
'Four indoor tennis courts provide a dependable setting for lessons, friendly matches and performance sessions.': '4 个室内网球场为课程、友谊赛和进阶训练提供稳定环境。',
'Ask about tennis →': '咨询网球场 →',
'More than a court': '不只是球场', 'Everything your game needs,': '运动所需的一切,', 'under one roof.': '都在同一屋檐下。',
'Reception & waiting area': '前台与等候区', 'Badminton pro shop': '羽毛球用品店', 'Changing rooms': '更衣室', 'Fitness gym': '健身房', 'Meeting room': '会议室', 'Lounge & food bar': '休息室与餐饮吧', 'Court separation nets': '球场隔离网', 'Fire-safety access': '消防通道',
'Space planning': '空间规划', 'A destination built': '逐层规划的', 'across every level.': '综合运动目的地。',
'Ground': '底层', 'Arrival & first courts': '接待与首层球场', 'Level 1': '一楼', 'Play, train & recharge': '运动、训练与休息', 'Level 2': '二楼', 'Expanded court capacity': '扩展球场容量', 'Upper level': '上层', 'The full venue programme': '完整场馆布局',
'Arrival, reception, waiting area, pro shop, changing rooms, badminton courts and tennis courts.': '设置场馆入口、前台、等候区、用品店、更衣室、羽毛球场与网球场。',
'Badminton and pickleball courts supported by changing rooms, meeting room, fitness gym, lounge and food bar.': '羽毛球与匹克球场配套更衣室、会议室、健身房、休息室与餐饮吧。',
'Additional badminton, pickleball and tennis capacity with dedicated circulation and court separation.': '增加羽毛球、匹克球与网球场地,并配置独立动线与球场隔离。',
"The upper-level court plan completes the building's multi-sport programme and maximises playable space.": '上层球场完善整栋建筑的综合运动布局,并最大化可运动空间。',
'Simple court booking': '轻松预约球场', 'From “let\'s play”': '从“约场球吧”', 'to': '到', 'court confirmed.': '确认球场。',
'Choose your sport': '选择运动项目', 'Select your area': '选择地区', 'Pick date & time': '选择日期与时间', 'Confirm your court': '确认时间与场地', 'Pay online': '线上付款',
'The digital booking experience is planned to make court access easy, anytime and anywhere.': '数字预约流程让用户随时随地轻松预订球场。',
'JUN Badminton Academy': 'JUN 羽毛球学院', 'Skills for the court.': '练就球场技能。', 'Character for life.': '塑造终身品格。',
'Tailored programmes for ages 4 to 50, from beginners taking their first steps to advanced players preparing for competition.': '为 4 至 50 岁不同水平的学员提供定制课程,从初学入门到进阶备赛。',
'Kids & adult group classes': '儿童与成人团体课程', 'Private one-to-one training': '私人一对一训练', 'Singles & doubles programmes': '单打与双打课程', 'Corporate & school training': '企业与学校训练',
'Enquire about training →': '咨询训练课程 →', 'Founder & head coach': '创办人兼主教练', 'Former Kedah state player': '前吉打州代表队球员', 'BAM Level 1-certified coach': '马来西亚羽总一级认证教练',
'Innovative drills': '创新训练', 'Fitness programmes': '体能课程', 'Mental conditioning': '心理训练', 'Local competitions': '本地赛事',
'A venue with a pulse': '充满活力的场馆', 'Play here.': '在这里运动。', 'Belong here.': '在这里找到归属。',
'The proposal is built around more than bookings: it imagines an active, connected local sports community.': '项目不只关注预约,更希望打造一个活跃、紧密连接的本地运动社区。',
'Community events': '社区活动', 'Regular activities that bring neighbours, families and players together.': '通过定期活动连接邻里、家庭和球友。',
'Seasonal programmes': '季节性活动', 'Campaigns, clinics and formats that keep the calendar fresh year-round.': '以主题推广、训练营和多种赛制丰富全年活动。',
'Leagues & competition': '联赛与竞技', 'Pathways to play casually, test your level and compete with confidence.': '从休闲约球、水平测试到正式竞技,提供清晰进阶路径。',
'Business partnerships': '商业合作', 'Space for corporate play, school programmes and local collaborations.': '承接企业活动、学校课程及本地合作项目。',
'Old Klang Road / 旧巴生路': '旧巴生路 / Old Klang Road', 'Connected to the city.': '连接城市。', 'Rooted in the neighbourhood.': '扎根社区。',
"One of Kuala Lumpur's oldest major corridors, Old Klang Road sits between the city and Petaling Jaya, surrounded by mature residential and commercial communities.": '旧巴生路是吉隆坡历史悠久的重要道路之一,连接市区与八打灵再也,周边拥有成熟住宅和商业社区。',
'Links to Federal Highway, NPE and LDP': '连接联邦大道、NPE 与 LDP', 'A multicultural neighbourhood with a strong local food culture': '多元族群共融,并拥有丰富本地美食文化', 'Accessible to families, students, residents and corporate groups': '方便家庭、学生、居民与企业团队到访',
'Road to opening': '开业进程', 'Building towards': '稳步迈向', 'Planning permission': '规划许可', 'Earthwork & building plans': '土方与建筑计划', 'Construction': '场馆施工', 'Completion & handover': '完工审批与交付', 'Grand opening': '正式开业',
'Four-month planning-permission phase within the wider project programme.': '项目规划中的四个月规划许可申请阶段。', 'Approval work for earthworks and the building plan.': '完成土方工程与建筑计划审批。', 'Six months planned for venue construction.': '预计用六个月完成场馆施工。', 'CCC approval, completion and handover.': '完成合规证书审批、完工与交付。', 'The first serves, rallies and community games begin.': '迎来首场发球、对打与社区活动。',
'Get on court early': '抢先上场', 'Be the first to play.': '成为首批球友。', 'Be part of the community.': '加入我们的社区。', 'Register for opening updates, academy news and early booking information.': '登记获取开业动态、学院资讯与优先预约信息。',
'Choose the channel that works best for you. Every contact detail can be copied individually.': '选择最方便的联系方式,每条信息都可以单独复制。', 'Training programmes': '训练课程', 'Court enquiries': '球场咨询', 'Corporate & school sessions': '企业与学校课程', 'View contact details →': '查看联系方式 →',
'Contact JUN Academy': '联系 JUN 学院', 'Choose a channel.': '选择联系方式。', 'Copy and connect.': '复制后自行联系。', 'Copy any contact detail below, then reach out using the app you prefer.': '单独复制下方任意联系信息,再使用你常用的应用与我们联系。',
'Phone': '电话', 'Email': '邮箱', 'Xiaohongshu': '小红书', 'Address': '地址', 'Copy': '复制',
'Your name': '姓名', 'Email address': '电子邮箱', "I'm interested in": '感兴趣的内容', 'Name': '姓名',
'Court bookings': '球场预约', 'JUN Badminton Academy': 'JUN 羽毛球学院', 'Corporate or school programmes': '企业或学校课程', 'Opening updates': '开业动态',
'A new home for court sports at Old Klang Road, Kuala Lumpur.': '位于吉隆坡旧巴生路的全新球类运动主场。', 'JUN Academy': 'JUN 学院', 'Explore': '网站导航', 'Location': '场馆位置',
'Ground Floor, Jalan Perdana 1,': '底层,Jalan Perdana 1', 'Taman Segar Perdana, 43200 Cheras, Selangor': 'Taman Segar Perdana43200 蕉赖,雪兰莪',
'Proposed venue information based on the current project proposal. Plans and opening schedule are subject to final approvals.': '场馆资料来自当前项目提案,具体规划与开业时间以最终审批为准。'
};
const ORIGINAL_TEXT = new WeakMap();
function translateTextNodes(isChinese) {
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
const nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach((node) => {
if (['SCRIPT', 'STYLE'].includes(node.parentElement?.tagName)) return;
if (node.parentElement?.closest('.footer-contact') || node.parentElement?.closest('.contact-value')) return;
if (!isChinese) {
if (ORIGINAL_TEXT.has(node)) node.nodeValue = ORIGINAL_TEXT.get(node);
return;
}
if (!ORIGINAL_TEXT.has(node)) ORIGINAL_TEXT.set(node, node.nodeValue);
const original = ORIGINAL_TEXT.get(node);
const value = original.trim();
if (value && EN_TO_ZH[value]) node.nodeValue = original.replace(value, EN_TO_ZH[value]);
});
}
function setLanguage(language) {
const isChinese = language === 'zh';
translateTextNodes(isChinese);
document.documentElement.lang = isChinese ? 'zh-CN' : 'en';
document.querySelectorAll('.language-toggle').forEach((button) => {
button.textContent = isChinese ? 'EN' : '中文';
button.setAttribute('aria-label', isChinese ? 'Switch to English' : '切换为中文');
});
const nameInput = document.querySelector('input[name="name"]');
if (nameInput) nameInput.placeholder = isChinese ? '姓名' : 'Name';
const activePlanCopy = document.querySelector('.plan-tab.active')?.dataset.copy;
const planDescription = document.querySelector('#plan-copy');
if (activePlanCopy && planDescription) planDescription.textContent = isChinese ? (EN_TO_ZH[activePlanCopy] || activePlanCopy) : activePlanCopy;
const formStatus = document.querySelector('.form-status');
if (formStatus) formStatus.textContent = '';
localStorage.setItem('site-language', language);
}
document.querySelectorAll('.language-toggle').forEach((button) => {
button.addEventListener('click', () => {
setLanguage(document.documentElement.lang.startsWith('zh') ? 'en' : 'zh');
});
});
setLanguage(localStorage.getItem('site-language') === 'zh' ? 'zh' : 'en');
menuButton?.addEventListener('click', () => {
const open = nav.classList.toggle('open');
menuButton.setAttribute('aria-expanded', String(open));
});
nav?.querySelectorAll('a').forEach((link) => {
link.addEventListener('click', () => {
nav.classList.remove('open');
menuButton?.setAttribute('aria-expanded', 'false');
});
});
let headerAnchor = header?.offsetTop ?? 0;
window.addEventListener('scroll', () => {
if (!header) return;
header.classList.toggle('is-sticky', window.scrollY > headerAnchor + 160);
document.body.style.paddingTop = header.classList.contains('is-sticky') ? `${header.offsetHeight}px` : '0';
const scrollable = document.documentElement.scrollHeight - window.innerHeight;
const progress = scrollable > 0 ? Math.min(1, Math.max(0, window.scrollY / scrollable)) : 0;
if (scrollProgress) scrollProgress.style.transform = `scaleX(${progress})`;
}, { passive: true });
const planImage = document.querySelector('#plan-image');
const planCopy = document.querySelector('#plan-copy');
document.querySelectorAll('.plan-tab').forEach((tab) => {
tab.addEventListener('click', () => {
document.querySelectorAll('.plan-tab').forEach((item) => {
item.classList.remove('active');
item.setAttribute('aria-selected', 'false');
});
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
planImage.style.opacity = '0';
setTimeout(() => {
planImage.src = tab.dataset.plan;
planImage.alt = `${tab.querySelector('strong').textContent} venue plan`;
const planText = tab.dataset.copy;
planCopy.textContent = document.documentElement.lang.startsWith('zh') ? (EN_TO_ZH[planText] || planText) : planText;
planImage.style.opacity = '1';
}, 160);
});
});
const lightbox = document.querySelector('#lightbox');
const lightboxImage = lightbox?.querySelector('img');
document.querySelectorAll('.gallery-item').forEach((item) => {
item.addEventListener('click', () => {
lightboxImage.src = item.dataset.image;
lightbox.showModal();
});
});
lightbox?.querySelector('button').addEventListener('click', () => lightbox.close());
lightbox?.addEventListener('click', (event) => {
if (event.target === lightbox) lightbox.close();
});
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
revealObserver.unobserve(entry.target);
}
});
}, { threshold: 0.08, rootMargin: '0px 0px -30px' });
document.querySelectorAll('.reveal').forEach((element) => revealObserver.observe(element));
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const countSection = document.querySelector('.court-counts');
const countNumbers = [...document.querySelectorAll('.court-count strong')];
if (countSection && !reducedMotion) {
const countObserver = new IntersectionObserver((entries) => {
if (!entries.some((entry) => entry.isIntersecting)) return;
countNumbers.forEach((number) => {
const target = Number(number.textContent);
const start = performance.now();
const duration = 850;
number.classList.add('counting');
const update = (now) => {
const elapsed = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - elapsed, 3);
number.textContent = String(Math.round(target * eased));
if (elapsed < 1) requestAnimationFrame(update);
else number.classList.remove('counting');
};
requestAnimationFrame(update);
});
countObserver.disconnect();
}, { threshold: .35 });
countObserver.observe(countSection);
}
const contactModal = document.querySelector('#contact-modal');
const copyStatus = contactModal?.querySelector('.copy-status');
document.querySelectorAll('.contact-modal-trigger').forEach((button) => {
button.addEventListener('click', () => {
if (!contactModal?.open) contactModal.showModal();
});
});
contactModal?.querySelector('.modal-close').addEventListener('click', () => contactModal.close());
contactModal?.addEventListener('click', (event) => {
if (event.target === contactModal) contactModal.close();
});
async function copyContactText(text) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (_) {
// Fall through to the file:// compatible copy method.
}
}
const field = document.createElement('textarea');
field.value = text;
field.setAttribute('readonly', '');
field.style.position = 'fixed';
field.style.opacity = '0';
document.body.appendChild(field);
field.select();
const copied = document.execCommand('copy');
field.remove();
return copied;
}
document.querySelectorAll('.copy-contact').forEach((button) => {
button.addEventListener('click', async () => {
const copied = await copyContactText(button.dataset.copy);
const isChinese = document.documentElement.lang.startsWith('zh');
if (copyStatus) {
copyStatus.textContent = copied
? (isChinese ? `已复制:${button.dataset.copy}` : `Copied: ${button.dataset.copy}`)
: (isChinese ? '复制失败,请手动选择文字复制。' : 'Copy failed. Please select and copy the text manually.');
}
if (!copied) return;
button.textContent = isChinese ? '已复制' : 'Copied';
button.classList.add('copied');
setTimeout(() => {
button.textContent = isChinese ? '复制' : 'Copy';
button.classList.remove('copied');
}, 1600);
});
});