feat(event): add 30th anniversary landing and sponsor pages

This commit introduces a new set of pages for the Yong Peng Hash House Harriers (HHH) 30th Anniversary event.

- Adds a main landing page with a modern design to serve as the central hub for the event.
- Creates a dynamic sponsor list page using Vue.js, featuring a vertically scrolling marquee to acknowledge supporters.
- Includes the raw data for sponsors in `supporter.json`.

Additionally, several improvements have been made to the existing `photowall` page:

- Refactors the scrolling text marquee for a more robust and seamless animation.
- Moves the marquee from the bottom to the top of the page for better visibility.
- Implements a fix for browser video autoplay restrictions by enabling audio and starting playback upon the first user interaction.
This commit is contained in:
xiaomai
2025-11-08 10:57:13 +08:00
parent e15d9a881c
commit 70e33b5cb7
5 changed files with 916 additions and 80 deletions

0
20251108/HHH Normal file
View File

491
20251108/index.html Normal file
View File

@@ -0,0 +1,491 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>永平捷兔会30周年庆大跑晚宴</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
}
body {
background: linear-gradient(
135deg,
#0c0c0c 0%,
#1a1a2e 50%,
#1e0b0b 100%
);
color: #e0e0e0;
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
/* 装饰性背景元素 */
.bg-pattern {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%23d4af37' fill-rule='evenodd'/%3E%3C/svg%3E");
z-index: 0;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
position: relative;
z-index: 1;
}
/* 头部样式 */
header {
text-align: center;
padding: 2rem 0;
position: relative;
}
.logo {
font-size: 4.5rem;
font-weight: bold;
margin-bottom: 1rem;
color: #d4af37;
text-transform: uppercase;
position: relative;
display: inline-block;
text-shadow: 0 0 10px rgba(212, 175, 55, 0.5);
letter-spacing: 4px;
}
.tagline {
font-size: 1.8rem;
font-weight: 300;
letter-spacing: 3px;
color: #e8e8e8;
margin-bottom: 1rem;
}
.anniversary {
font-size: 8rem;
font-weight: bold;
color: #d4af37;
text-shadow: 0 0 20px rgba(212, 175, 55, 0.7);
margin: 2rem 0;
position: relative;
display: inline-block;
}
.anniversary::before,
.anniversary::after {
content: "";
position: absolute;
top: 50%;
width: 200px;
height: 3px;
background: linear-gradient(90deg, transparent, #d4af37, transparent);
}
.anniversary::before {
left: -220px;
}
.anniversary::after {
right: -220px;
}
.event-title {
font-size: 3.5rem;
font-weight: 500;
color: #e8e8e8;
margin-bottom: 2rem;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
}
/* 主要内容区域 */
.main-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 2rem 0;
}
.welcome-text {
text-align: center;
max-width: 900px;
margin-bottom: 4rem;
background: rgba(30, 30, 46, 0.6);
padding: 2.5rem;
border-radius: 15px;
border: 1px solid rgba(212, 175, 55, 0.3);
backdrop-filter: blur(10px);
}
.welcome-text h2 {
font-size: 2.5rem;
font-weight: 400;
margin-bottom: 1.5rem;
color: #e8e8e8;
}
.welcome-text p {
font-size: 1.4rem;
line-height: 1.8;
color: #c0c0c0;
}
/* 导航卡片区域 */
.nav-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 2.5rem;
width: 100%;
max-width: 1200px;
}
.nav-card {
background: rgba(30, 30, 46, 0.7);
border: 2px solid rgba(212, 175, 55, 0.4);
border-radius: 15px;
padding: 2.5rem;
text-align: center;
transition: all 0.4s ease;
position: relative;
overflow: hidden;
backdrop-filter: blur(10px);
}
.nav-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
135deg,
rgba(212, 175, 55, 0.1) 0%,
transparent 50%
);
z-index: -1;
}
.nav-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4);
border-color: rgba(212, 175, 55, 0.7);
}
.card-icon {
font-size: 4rem;
color: #d4af37;
margin-bottom: 1.5rem;
}
.nav-card h3 {
font-size: 2rem;
font-weight: 500;
margin-bottom: 1.5rem;
color: #e8e8e8;
}
.nav-card p {
font-size: 1.2rem;
color: #b0b0b0;
margin-bottom: 2rem;
line-height: 1.6;
}
.nav-link {
display: inline-block;
padding: 1.2rem 2.5rem;
background: transparent;
color: #d4af37;
border: 2px solid #d4af37;
border-radius: 40px;
text-decoration: none;
font-weight: 500;
font-size: 1.3rem;
letter-spacing: 1px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.nav-link::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(212, 175, 55, 0.2),
transparent
);
transition: all 0.6s;
}
.nav-link:hover {
background: rgba(212, 175, 55, 0.1);
box-shadow: 0 0 20px rgba(212, 175, 55, 0.4);
}
.nav-link:hover::before {
left: 100%;
}
/* 底部样式 */
footer {
text-align: center;
padding: 3rem 0 2rem;
margin-top: 4rem;
border-top: 1px solid rgba(255, 255, 255, 0.05);
color: #888;
font-size: 1.1rem;
}
/* 彩带装饰 */
.ribbon {
position: absolute;
width: 150px;
height: 150px;
background: #d4af37;
transform: rotate(45deg);
top: -75px;
right: -75px;
z-index: 2;
}
.ribbon::before,
.ribbon::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
background: #d4af37;
}
.ribbon::before {
transform: rotate(90deg);
}
/* 响应式设计 */
@media (max-width: 1200px) {
.logo {
font-size: 3.5rem;
}
.anniversary {
font-size: 6rem;
}
.anniversary::before,
.anniversary::after {
width: 150px;
}
.anniversary::before {
left: -170px;
}
.anniversary::after {
right: -170px;
}
.event-title {
font-size: 2.8rem;
}
}
@media (max-width: 768px) {
.logo {
font-size: 2.8rem;
}
.anniversary {
font-size: 5rem;
}
.anniversary::before,
.anniversary::after {
width: 100px;
}
.anniversary::before {
left: -120px;
}
.anniversary::after {
right: -120px;
}
.event-title {
font-size: 2.2rem;
}
.welcome-text h2 {
font-size: 2rem;
}
.nav-cards {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.container {
padding: 1rem;
}
header {
padding: 1.5rem 0;
}
.logo {
font-size: 2.2rem;
}
.anniversary {
font-size: 4rem;
}
.anniversary::before,
.anniversary::after {
width: 60px;
}
.anniversary::before {
left: -70px;
}
.anniversary::after {
right: -70px;
}
.event-title {
font-size: 1.8rem;
}
}
/* 跑步剪影装饰 */
.runner-silhouette {
position: absolute;
bottom: 0;
left: 5%;
width: 200px;
height: 200px;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M20,70 C30,50 40,30 50,20 C60,30 70,50 80,70" stroke="%23d4af37" stroke-width="3" fill="none"/><circle cx="20" cy="70" r="5" fill="%23d4af37"/><circle cx="80" cy="70" r="5" fill="%23d4af37"/></svg>')
no-repeat center;
opacity: 0.2;
z-index: 0;
}
.runner-silhouette.right {
left: auto;
right: 5%;
transform: scaleX(-1);
}
</style>
</head>
<body>
<div class="bg-pattern"></div>
<div class="runner-silhouette"></div>
<div class="runner-silhouette right"></div>
<div class="container">
<header>
<h1 class="logo">永平捷兔会</h1>
<p class="tagline">三十载奔跑,荣耀同行</p>
<div class="anniversary">30</div>
<h2 class="event-title">周年庆大跑晚宴</h2>
</header>
<main class="main-content">
<div class="welcome-text">
<h2>欢迎莅临30周年庆典</h2>
<p>
三十载风雨同舟,感恩每一位捷兔会员的陪伴与支持。今晚,让我们共同庆祝这个荣耀时刻,回顾辉煌历程,展望美好未来!
</p>
</div>
<div class="nav-cards">
<div class="nav-card">
<div class="ribbon"></div>
<div class="card-icon">🏃‍♂️</div>
<h3>活动流程</h3>
<p>查看大跑活动路线、时间安排及晚宴流程,不错过每一个精彩环节。</p>
<a href="./eventSchedule" class="nav-link">查看流程</a>
</div>
<div class="nav-card">
<div class="card-icon">💎</div>
<h3>赞助商列表</h3>
<p>
感谢所有赞助商对本次活动的大力支持,让我们共同见证他们的品牌魅力。
</p>
<a href="./sponsorList" class="nav-link">查看赞助商</a>
</div>
<div class="nav-card">
<div class="card-icon">🏆</div>
<h3>荣誉殿堂</h3>
<p>回顾捷兔会30年辉煌历程见证会员们的荣耀时刻与珍贵回忆。</p>
<a href="./hallOfFame" class="nav-link">进入殿堂</a>
</div>
</div>
</main>
<footer>
<p>&copy; 2023 永平捷兔会 30周年庆典. 保留所有权利.</p>
</footer>
</div>
<script>
// 添加页面加载动画效果
document.addEventListener("DOMContentLoaded", function () {
// 延迟显示内容以增强加载体验
const container = document.querySelector(".container");
container.style.opacity = "0";
setTimeout(() => {
container.style.transition = "opacity 1.5s ease";
container.style.opacity = "1";
}, 300);
// 为导航链接添加点击效果
const navLinks = document.querySelectorAll(".nav-link");
navLinks.forEach((link) => {
link.addEventListener("click", function (e) {
e.preventDefault();
// 添加点击反馈
this.style.transform = "scale(0.95)";
// 模拟页面跳转延迟
setTimeout(() => {
window.location.href = this.getAttribute("href");
}, 300);
});
});
// 添加数字30的脉冲动画
const anniversary = document.querySelector(".anniversary");
setInterval(() => {
anniversary.style.transform = "scale(1.05)";
setTimeout(() => {
anniversary.style.transform = "scale(1)";
}, 500);
}, 3000);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sponsor List</title>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
.sponsor-marquee-track {
animation: sponsor-marquee-move-text 120s linear infinite;
}
.special-marquee-track {
animation: special-marquee-move-text 30s linear infinite;
}
@keyframes sponsor-marquee-move-text {
to {
transform: translateY(-50%);
}
}
@keyframes special-marquee-move-text {
to {
transform: translateX(-50%);
}
}
</style>
</head>
<body>
<div id="app" class="w-screen h-screen overflow-clip select-none">
<div class="flex w-full h-full">
<!-- <div class="flex-2 bg-amber-50"></div> -->
<div
class="flex-8 relative bg-linear-to-b from-pink-900 to-pink-500 px-6"
>
<!-- 上:可滚动 / 填满剩余空间 -->
<div class="absolute inset-0 overflow-hidden">
<div class="sponsor-marquee-track">
<div
v-for="([key, value], index) in sponsorsTwice"
:key="key + '-' + index"
>
<div v-if="!value.showTogether" class="pt-8 space-y-8">
<div v-for="sponsor in value.list" :key="sponsor">
<div
class="backdrop-blur-xl bg-linear-to-br from-pink-500/20 to-white/10 border border-white/30 rounded-2xl p-8 shadow-2xl"
>
<div
:class="['text-center font-bold mb-4 text-amber-400', (value.titleFontSize || 'text-4xl')]"
>
{{ formatRm(key) }}
</div>
<div
:class="['text-center text-5xl text-white text-shadow-amber-400 text-shadow-lg']"
>
{{ sponsor }}
</div>
</div>
</div>
</div>
<div v-else class="pt-8 space-y-8">
<div
class="backdrop-blur-xl bg-linear-to-br from-white/20 to-white/10 border border-white/30 rounded-2xl p-8 shadow-2xl"
>
<div
:class="['text-center font-bold mb-4', (value.titleFontSize || 'text-4xl')]"
>
{{ formatRm(key) }}
</div>
<div
class="flex flex-wrap gap-x-8 gap-y-4 p-4 justify-start"
>
<div
v-for="sponsor in value.list"
:key="sponsor"
class="text-2xl border-white border px-4 py-2 rounded-md shadow-md text-white"
>
{{sponsor}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 下:特别鸣谢 -->
<div
class="absolute bottom-0 left-0 right-0 bg-white/30 backdrop-blur-md rounded-t-xl border-t border-white/40 shadow-lg"
>
<div class="flex items-center">
<div class="p-6 text-3xl font-bold text-nowrap">特别鸣谢:</div>
<div
class="overflow-hidden flex-1 mask-x-from-95% mask-x-to-100%"
>
<div class="flex w-max pl-4 gap-4 special-marquee-track">
<div
class="text-3xl px-6 py-2 border border-gray-500 rounded-full"
v-for="specialSponsor in specialSponsorTwice"
:key="specialSponsor"
>
{{specialSponsor}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const eventTitle = ref("永平捷兔会 30 周年庆大跑晚宴");
const formatRm = (s) =>
s.replace(
/RM\s*([0-9,]+(?:\.\d+)?)/i,
(_, num) =>
"RM" + Number(num.replace(/,/g, "")).toLocaleString("en-MY")
);
const sponsors = ref({
Rm5000: { titleFontSize: "text-9xl", list: ["Top Gan"] },
Rm2000: {
titleFontSize: "text-8xl",
list: [
"Ketua Kampung",
"校长",
"Natural 9",
"明盛弟",
"联合周",
"新成酒家",
],
},
Rm1600: { titleFontSize: "text-7xl", list: ["Moon"] },
Rm1200: { titleFontSize: "text-6xl", list: ["Labis Bon"] },
Rm1000: { titleFontSize: "text-5xl", list: ["Angel"] },
Rm800: {
list: [
"拿督",
"Mari Chan",
"霖主席",
"MDL阿德",
"Cool Lo",
"刘薇薇",
],
},
Rm500: { list: ["Hun Shap Tou", "Good Man", "学生妹", "三太子"] },
Rm400: {
showTogether: true,
list: [
"Founder Koh",
"Founder Ang",
"Founder Koid",
"Superman",
"榴梿",
"阳阳",
"Lawyer",
"小老板",
"富婆",
"Farmer",
"Farmer嫂",
"Durian King",
"大傻",
"Datin",
"Mohamad Ali",
"Wireman",
"Ki Ka Poh",
"TV",
"Sexy",
"Uncle Low",
"小黑",
"黑夫人",
"Jimmy",
"Ah Boon",
"米桶",
"Pasar Malam",
"Public Ong",
"Pet pet",
"梅惠",
"910",
"伟哥",
"仙女",
"Boss",
"彩虹",
"花瓶",
"黑珍珠",
"木薯老板",
"美国佬",
"三公子",
"来",
"宝强",
"Steven",
"老二",
"车斗Lau",
"海南Huat",
"龙门铁宝",
"山竹祥",
"旺庆",
"福承",
"立家",
"鸿兴",
"江老板",
"Wong Long",
"健芳",
"杨文德",
"古早味",
"猪笼",
"Bangkali Pusing",
"999",
"012",
"Wu Wei Xiong",
"Fan Shu",
"肥福",
"Kulai:阿祥",
"Kulai:Robert",
"国宝",
"Puki Ayam",
"爱情鸟",
"William Soh",
"Darren",
"林总",
"High More",
"Tiger",
"Lim Kopi",
"林董",
"Lighting Chan",
"Jag",
"Corina",
"Jiu Xiao",
"Love Bird",
"Sepuluh Dua",
"哈哈",
"狗爷",
],
},
Rm200: {
showTogether: true,
list: [
"兰总",
"Kampopo",
"Lim Kee Meng",
"003",
"Tan Brother",
"E-Sun",
"Kami",
"Kami嫂",
"美发师",
"美女",
"Oong Lai",
"乃乃",
"Naluri",
"妹子",
"Joan",
"Kai De Tan",
"企鹅",
"老二",
"二娘",
"DJ Yap",
"菜头",
"Ketam",
"Roket",
"土豪",
"天鹅",
"老板娘",
"Momo",
"Happyman",
"阿琳",
"花木兰",
"财政",
"财政夫人",
"走火",
"表妹",
"鸡脚老大",
"阿锦",
"维哥",
"牛车轮",
"陈进平",
],
},
});
const specialSponsor = ref([
"HEINEKEN MARKETING MALAYSIA SDN.BHD.: 100 件 T-Shirt & 500 件小毛巾",
"花奇 Nou: 大蛋糕一个",
"绝世旅游 500 - 550 环保袋",
]);
const specialSponsorTwice = computed(() => [
...specialSponsor.value,
...specialSponsor.value,
]);
const sponsorsTwice = computed(() => {
// 转成数组,因为对象遍历时 Vue 不保证顺序
const entries = Object.entries(sponsors.value);
// 重复一次,实现无缝衔接
return [...entries, ...entries];
});
return {
eventTitle,
formatRm,
sponsorsTwice,
specialSponsorTwice,
};
},
}).mount("#app");
</script>
</body>
</html>

23
20251108/supporter.json Normal file
View File

@@ -0,0 +1,23 @@
{
"Rm5000": ["Top Gan"],
"Rm2000": ["Ketua Kampung", "校长", "Natural 9", "明盛弟", "联合周", "新成酒家"],
"Rm1600": ["Moon"],
"Rm1200": ["Labis Bon"],
"Rm1000": ["Angel"],
"Rm800": ["拿督", "Mari Chan", "霖主席", "MDL阿德", "Cool Lo", "刘薇薇"],
"Rm500": ["Hun Shap Tou", "Good Man", "学生妹", "三太子"],
"Rm400": [
"Founder Koh", "Founder Ang", "Founder Koid", "Superman", "榴梿", "阳阳", "Lawyer", "小老板", "富婆", "Farmer", "Farmer嫂", "Durian King", "大傻", "Datin", "Mohamad Ali", "Wireman", "Ki Ka Poh", "TV", "Sexy", "Uncle Low", "小黑", "黑夫人", "Jimmy", "Ah Boon", "米桶", "Pasar Malam", "Public Ong", "Pet pet", "梅惠", "910", "伟哥", "仙女", "Boss", "彩虹", "花瓶", "黑珍珠", "木薯老板", "美国佬", "三公子", "来", "宝强", "Steven", "老二", "车斗Lau", "海南Huat", "龙门铁宝", "山竹祥", "旺庆", "福承", "立家", "鸿兴", "江老板", "Wong Long",
"健芳", "杨文德", "古早味", "猪笼", "Bangkali Pusing", "999", "012", "Wu Wei Xiong", "Fan Shu", "肥福", "Kulai:阿祥", "Kulai:Robert", "国宝", "Puki Ayam", "爱情鸟", "William Soh", "Darren", "林总", "High More", "Tiger", "Lim Kopi", "林董", "Lighting Chan", "Jag", "Corina", "Jiu Xiao", "Love Bird", "Sepuluh Dua", "哈哈", "狗爷"
],
"Rm200": [
"兰总", "Kampopo", "Lim Kee Meng", "003", "Tan Brother", "E-Sun", "Kami", "Kami嫂", "美发师", "美女", "Oong Lai", "乃乃", "Naluri", "妹子", "Joan", "Kai De Tan", "企鹅", "老二", "二娘", "DJ Yap", "菜头", "Ketam", "Roket", "土豪", "天鹅", "老板娘", "Momo", "Happyman", "阿琳", "花木兰", "财政", "财政夫人", "走火", "表妹", "鸡脚老大", "阿锦", "维哥", "牛车轮", "陈进平"
],
"其他": {
"报效": [
"HEINEKEN MARKETING MALAYSIA SDN.BHD.1)100件 T-Shirt. 2)500件小毛巾.",
"花奇Nou 大蛋糕一个",
"绝世旅游500-550环保袋"
]
}
}