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

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>