feat(sponsor-list): implement new design with animated canvas background

This commit introduces a completely redesigned sponsor list page. The previous two-column layout is replaced with a
modern, single-column auto-scrolling list that unifies all sponsors. A dynamic canvas animation has been added to the
background for a more engaging visual experience. Previous versions of the page have been archived. This update also
includes data corrections for sponsor names and minor fixes to the related PPT assets.
This commit is contained in:
xiaomai
2025-09-16 23:48:44 +08:00
parent 71b2dcf0a5
commit bcbae992a3
16 changed files with 612 additions and 350 deletions

View File

@@ -1,47 +1,20 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>赞助人名单</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<link rel="stylesheet" href="style.css" />
<title>Sponsor List</title>
<link rel="stylesheet" href="style.css?t=1610" />
</head>
<body class="theme-default">
<body>
<canvas id="background"></canvas>
<div class="container">
<div class="header">
<h1>赞助人名单</h1>
<!-- <p class="subtitle">衷心感谢以下企业及个人的慷慨赞助</p> -->
<!--
<div class="stats">
<div id="totalSponsors"><i class="fas fa-users"></i> 赞助单位: 0</div>
<div id="totalAmount"><i class="fas fa-coins"></i> 总金额: RM 0</div>
<div id="totalSeats"><i class="fas fa-chair"></i> 席位总数: 0</div>
</div>
-->
</div>
<div class="main-content">
<div class="section">
<h2>赞助金额</h2>
<div class="scroll-container">
<div class="scroll-content" id="moneyList"></div>
</div>
</div>
<div class="section">
<h2>席位赞助 (RM2000)</h2>
<div class="scroll-container">
<div class="scroll-content" id="seatList"></div>
</div>
</div>
</div>
<div class="footer">
<p id="footerText">感谢所有赞助商对本次活动的大力支持 | 2023年</p>
</div>
<header>
<h1>柔 佛 永 平 關 聖 宮 聯 誼 會</h1>
<h2>赞助者名单 Sponsor List</h2>
</header>
<div id="sponsorList" class="scrolling-list"></div>
</div>
<script src="script.js"></script>
</body>

View File

@@ -1,92 +1,115 @@
// 自动更新年份
document.getElementById(
"footerText"
).innerHTML = `© ${new Date().getFullYear()} Tootaio.com 保留所有权利。| 由 <a href="https://tootaio.com" target="_blank" rel="noopener">Tootaio</a> 制作。`;
// 页面初始化
function initSponsorsAndSeats(sponsors, seats) {
const moneyList = document.getElementById("moneyList");
const seatList = document.getElementById("seatList");
// Sort by amount descending
sponsors.sort((a, b) => parseFloat(b.amount) - parseFloat(a.amount));
seats.sort((a, b) => parseInt(b.seat) - parseInt(a.seat));
let totalAmount = 0;
sponsors.forEach((s) => {
totalAmount += parseFloat(s.amount);
const div = document.createElement("div");
div.className = "sponsor-item";
div.innerHTML = `<span>${s.name}</span><span class="amount">RM ${Number(
s.amount
).toLocaleString()}</span>`;
moneyList.appendChild(div);
});
// 复制一份实现无缝滚动
moneyList.innerHTML += moneyList.innerHTML;
let totalSeats = 0;
seats.forEach((s) => {
totalSeats += parseInt(s.seat);
const div = document.createElement("div");
div.className = "seat-item";
div.innerHTML = `<span>${s.name}</span><span class="amount">${s.seat} 席</span>`;
seatList.appendChild(div);
});
seatList.innerHTML += seatList.innerHTML;
// 更新统计数据
const totalSponsors = document.getElementById("totalSponsors");
if (totalSponsors) {
totalSponsors.innerHTML = ""; // 清空现有内容
var totalSponsorsIcon = document.createElement("i");
totalSponsorsIcon.className = "fas fa-users";
totalSponsors.prepend(totalSponsorsIcon);
totalSponsors.appendChild(
document.createTextNode(` 赞助单位: ${sponsors.length}`)
);
}
const totalAmountEl = document.getElementById("totalAmount");
totalAmountEl.innerHTML = ""; // 清空现有内容
var totalAmountIcon = document.createElement("i");
totalAmountIcon.className = "fas fa-coins";
totalAmountEl.prepend(totalAmountIcon);
totalAmountEl.appendChild(
document.createTextNode(` 总金额: RM ${totalAmount.toLocaleString()}`)
);
const totalSeatsEl = document.getElementById("totalSeats");
if (totalSeatsEl) {
totalSeatsEl.innerHTML = ""; // 清空现有内容
var totalSeatsIcon = document.createElement("i");
totalSeatsIcon.className = "fas fa-chair";
totalSeatsEl.prepend(totalSeatsIcon);
totalSeatsEl.appendChild(
document.createTextNode(` 席位总数: ${totalSeats}`)
);
}
/**
* 格式化金额,默认 MYR (RM)
* @param {Number|String} amount 金额
* @param {String} currency 币种ISO 4217默认 MYR
* @param {String} locale 语言地区,默认 "ms-MY"
* @returns {String} 格式化后的金额
*/
function formatCurrency(amount, currency = "MYR", locale = "ms-MY") {
const num = Number(amount);
if (isNaN(num)) return String(amount); // 非数字直接返回原值
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(num);
}
// 🚀 动态加载 JSON 数据
const sponsorListDiv = document.getElementById("sponsorList");
Promise.all([
fetch("../data/sponsors.json").then((res) => res.json()),
fetch("../data/seats.json").then((res) => res.json()),
])
.then(([sponsors, seats]) => {
initSponsorsAndSeats(sponsors, seats);
})
.catch((err) => {
console.error("加载 JSON 数据失败,回退到本地 mock 数据", err);
]).then(([sponsors, seats]) => {
const sponsorList = sponsors.map((item) => ({
name: item.name,
amount: formatCurrency(item.amount), // 使用 Intl API
}));
// 备用 mock 数据(防止页面空白)
const mockSponsors = [
{ name: "亮湘厨中国烧烤", amount: 8000 },
{ name: "星空科技集团", amount: 15000 },
{ name: "未来教育基金会", amount: 20000 },
];
const mockSeats = [
{ name: "郑来兴", seat: 1 },
{ name: "未来教育基金会", seat: 5 },
];
const seatList = seats.map((item) => ({
name: item.name,
amount: `${item.seat}`,
}));
initSponsorsAndSeats(mockSponsors, mockSeats);
[...sponsorList, ...seatList].forEach((entry) => {
const card = document.createElement("div");
card.className = "sponsor-item card";
card.innerHTML = `<h2>${entry.name}</h2><p>${entry.amount}</p>`;
sponsorListDiv.appendChild(card);
});
});
// ================= Canvas 背景 =================
const canvas = document.getElementById("background");
const ctx = canvas.getContext("2d");
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
class Point {
constructor() {
this.radius = Math.random() * 4 + 2;
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.xSpeed = (Math.random() - 0.5) * 60;
this.ySpeed = (Math.random() - 0.5) * 60;
this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
this.lastDrawTime = null;
}
draw() {
const now = Date.now();
if (this.lastDrawTime) {
const dt = (now - this.lastDrawTime) / 1000;
this.x += this.xSpeed * dt;
this.y += this.ySpeed * dt;
}
if (this.x < 0 || this.x > canvas.width) this.xSpeed *= -1;
if (this.y < 0 || this.y > canvas.height) this.ySpeed *= -1;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowBlur = 15;
ctx.shadowColor = this.color;
ctx.fill();
this.lastDrawTime = now;
}
}
class Graph {
constructor(pointCount = 70, maxDistance = 120) {
this.points = Array.from({ length: pointCount }, () => new Point());
this.maxDist = maxDistance;
}
draw() {
requestAnimationFrame(() => this.draw());
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < this.points.length; i++) {
const p1 = this.points[i];
p1.draw();
for (let j = i + 1; j < this.points.length; j++) {
const p2 = this.points[j];
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < this.maxDist) {
const alpha = 1 - dist / this.maxDist;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.strokeStyle = `rgba(255, 255, 255, ${alpha})`;
ctx.lineWidth = 2 * alpha;
ctx.stroke();
}
}
}
}
}
new Graph().draw();

View File

@@ -1,237 +1,130 @@
:root {
/* 橙色主题 */
--primary-bg: linear-gradient(135deg, #332211, #cc5500);
--card-bg: rgba(0, 0, 0, 0.35);
--primary-text: #fff5e6;
--accent-color: #ff8c42;
--secondary-accent: #ffd700;
--header-color: #ffd700;
--stats-color: #ff8c42;
--item-bg: rgba(255, 255, 255, 0.07);
--sponsor-border: #ff8c42;
--seat-border: #ffd700;
--shadow-color: rgba(0, 0, 0, 0.55);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
transition: background-color 0.5s ease, color 0.5s ease, transform 0.3s ease;
}
body {
background: var(--primary-bg);
color: var(--primary-text);
font-family: "Microsoft YaHei", sans-serif;
padding: 20px;
overflow-x: hidden;
min-height: 100vh;
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
color: #fff;
overflow: hidden; /* 隐藏全局滚动条 */
}
canvas {
position: fixed;
top: 0;
left: 0;
z-index: -1;
width: 100vw;
height: 100vh;
background: linear-gradient(270deg, #600000, #606000);
background-size: 600% 600%;
/* animation: background 30s ease infinite; */
background: url("sponsor-bg.jpg") no-repeat center center fixed;
background-size: cover;
}
.container {
max-width: 1800px;
margin: auto;
position: relative;
width: 100%;
overflow: hidden;
position: relative;
box-sizing: border-box;
}
.header,
.footer {
text-align: center;
padding: 30px;
background: var(--card-bg);
border-radius: 20px;
box-shadow: 0 10px 30px var(--shadow-color);
backdrop-filter: blur(10px);
margin-bottom: 30px;
border: 1px solid rgba(255, 255, 255, 0.1);
header {
text-align: center;
margin: 0 0 20px;
position: sticky; /* 保持在页面顶部 */
top: 0;
z-index: 1; /* 确保在滚动列表之上 */
padding: 10px 0;
background-color: rgb(48, 0, 0, 0.3);
box-shadow: 0px 8px 4px 2px rgba(0, 0, 0, 0.5);
}
.footer {
margin-top: 30px;
padding: 20px;
header h1 {
font-size: 3rem;
color: #f1c40f;
text-shadow: 0 0 10px #f39c12;
animation: pulse 3s infinite alternate ease-in-out;
}
h1 {
font-size: 2rem;
color: var(--header-color);
margin-bottom: 15px;
text-shadow: 0 2px 10px var(--shadow-color);
letter-spacing: 1.5px;
header h2 {
text-align: center;
font-size: 2rem;
margin: 0 0 20px;
color: #87f10f;
text-shadow: 0 0 10px #25f312;
animation: bounce 1.5s infinite alternate ease-in-out;
}
a {
color: var(--accent-color);
text-decoration: none;
transition: color 0.3s ease;
.card {
margin: 32px auto;
padding: 16px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(5px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
width: 90%;
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.subtitle {
font-size: 1.5rem;
color: var(--primary-text);
opacity: 0.9;
margin-bottom: 25px;
.card:hover {
transform: scale(1.05);
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.3);
}
.stats {
display: flex;
justify-content: center;
gap: 50px;
margin: 25px 0;
.sponsor-item h2 {
font-size: 4.5rem;
margin: 0;
}
.stats div {
font-size: 3rem;
color: var(--stats-color);
font-weight: bold;
display: flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
background: var(--item-bg);
border-radius: 12px;
.sponsor-item p {
font-size: 4rem;
margin: 0;
color: #f1c40f;
font-weight: bolder;
}
.stats i {
font-size: 2.2rem;
}
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-top: 20px;
}
.section {
background: var(--card-bg);
border-radius: 20px;
padding: 30px;
height: 650px;
position: relative;
overflow: hidden;
box-shadow: 0 10px 30px var(--shadow-color);
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.section h2 {
text-align: center;
color: var(--accent-color);
margin-bottom: 25px;
font-size: 2.4rem;
text-shadow: 0 1px 5px var(--shadow-color);
position: relative;
padding-bottom: 12px;
}
.section h2:after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 100px;
height: 4px;
background: var(--secondary-accent);
border-radius: 4px;
}
.scroll-container {
height: 540px;
overflow: hidden;
position: relative;
border-radius: 15px;
margin-top: 15px;
}
.scroll-content {
position: absolute;
width: 100%;
}
.sponsor-item,
.seat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 25px;
margin: 15px 0;
background: var(--item-bg);
border-radius: 12px;
font-size: 1.7rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.sponsor-item:hover,
.seat-item:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px var(--shadow-color);
}
.sponsor-item {
border-left: 6px solid var(--sponsor-border);
}
.seat-item {
border-left: 6px solid var(--seat-border);
}
.amount {
color: var(--secondary-accent);
font-weight: bold;
font-size: 1.8rem;
}
.scroll-content {
animation: scroll 40s linear infinite;
}
.scroll-content:hover {
animation-play-state: paused;
.scrolling-list {
display: flex;
flex-direction: column;
animation: scroll 240s linear infinite;
overflow: hidden; /* 确保列表自身不显示滚动条 */
}
@keyframes scroll {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
0% {
transform: translateY(3%);
}
100% {
transform: translateY(-100%);
}
}
/* 响应式设计 */
@media (max-width: 1200px) {
.main-content {
grid-template-columns: 1fr;
}
h1 {
font-size: 3rem;
}
.stats {
gap: 20px;
}
@keyframes pulse {
0% {
transform: scale(1);
text-shadow: 0 0 10px #f39c12, 0 0 20px #f39c12;
}
100% {
transform: scale(1.2);
text-shadow: 0 0 20px #f1c40f, 0 0 40px #f39c12;
}
}
@media (max-width: 768px) {
.stats {
flex-direction: column;
gap: 15px;
}
.header,
.footer {
padding: 20px;
}
.section {
padding: 20px;
height: 500px;
}
.scroll-container {
height: 400px;
}
@keyframes bounce {
0% {
transform: translateY(0);
text-shadow: 0 0 10px #25f312, 0 0 20px #87f10f;
}
100% {
transform: translateY(-10px);
text-shadow: 0 0 20px #87f10f, 0 0 40px #25f312;
}
}
@keyframes background {
0%{background-position:0% 50%}
50%{background-position:100% 50%}
100%{background-position:0% 50%}
}