| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
- <title>Pet Voting · Cute Animals Election</title>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- background: linear-gradient(145deg, #fef9e6 0%, #fff5e8 100%);
- font-family: 'Segoe UI', 'Poppins', system-ui, -apple-system, 'Roboto', 'Noto Sans', sans-serif;
- padding: 2rem 1.5rem;
- color: #2e241f;
- }
- .container {
- max-width: 1280px;
- margin: 0 auto;
- }
- /* 头部 & 倒计时 */
- .hero {
- text-align: center;
- margin-bottom: 3rem;
- }
- .hero h1 {
- font-size: 2.8rem;
- background: linear-gradient(135deg, #c47b4e, #e09d6e);
- background-clip: text;
- -webkit-background-clip: text;
- color: transparent;
- margin-bottom: 0.75rem;
- letter-spacing: -0.5px;
- font-weight: 800;
- }
- .tagline {
- font-size: 1.2rem;
- color: #7f5e49;
- margin-bottom: 1.5rem;
- font-weight: 500;
- }
- .countdown-card {
- background: #ffffffdd;
- backdrop-filter: blur(8px);
- border-radius: 80px;
- display: inline-flex;
- align-items: center;
- gap: 1rem;
- padding: 0.8rem 2rem;
- box-shadow: 0 15px 35px rgba(0,0,0,0.1);
- border: 1px solid #ffe0b5;
- margin-top: 0.5rem;
- }
- .countdown-label {
- font-weight: 600;
- background: #f5d7b3;
- padding: 0.3rem 1rem;
- border-radius: 40px;
- font-size: 0.9rem;
- color: #7b4a2c;
- }
- .countdown-timer {
- font-family: 'Courier New', 'Fira Mono', monospace;
- font-size: 2rem;
- font-weight: 800;
- letter-spacing: 4px;
- background: #2e241f;
- color: #ffddb0;
- padding: 0.2rem 0.9rem;
- border-radius: 60px;
- }
- /* 语言切换器 */
- .lang-switch {
- position: fixed;
- top: 18px;
- right: 20px;
- background: #ffffffcc;
- backdrop-filter: blur(8px);
- padding: 6px 14px;
- border-radius: 60px;
- font-size: 0.8rem;
- font-weight: 500;
- border: 1px solid #fcd7ae;
- z-index: 99;
- display: flex;
- gap: 10px;
- box-shadow: 0 4px 12px rgba(0,0,0,0.05);
- }
- .lang-switch button {
- background: transparent;
- border: none;
- font-weight: 600;
- cursor: pointer;
- font-size: 0.85rem;
- padding: 4px 12px;
- border-radius: 40px;
- transition: all 0.2s;
- font-family: inherit;
- color: #7b5a42;
- }
- .lang-switch button.active {
- background: #ffb347;
- color: white;
- box-shadow: 0 2px 6px rgba(0,0,0,0.1);
- }
- .lang-switch button:hover:not(.active) {
- background: #f0e0cf;
- }
- /* 宠物网格 */
- .pets-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
- gap: 2rem;
- margin-top: 1rem;
- }
- .pet-card {
- background: white;
- border-radius: 2rem;
- overflow: hidden;
- transition: transform 0.25s ease, box-shadow 0.3s;
- box-shadow: 0 12px 28px rgba(0,0,0,0.08);
- border: 1px solid #f8e2c9;
- }
- .pet-card:hover {
- transform: translateY(-6px);
- box-shadow: 0 25px 35px -12px rgba(0,0,0,0.2);
- }
- .pet-img {
- width: 100%;
- aspect-ratio: 1 / 1;
- object-fit: cover;
- background: #f0e2d4;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 5rem;
- transition: transform 0.4s;
- }
- .pet-card:hover .pet-img {
- transform: scale(1.02);
- }
- .pet-info {
- padding: 1.5rem 1.2rem 1.5rem;
- }
- .pet-name {
- font-size: 1.7rem;
- font-weight: 700;
- color: #3a2a21;
- display: flex;
- align-items: baseline;
- justify-content: space-between;
- flex-wrap: wrap;
- margin-bottom: 0.5rem;
- }
- .pet-desc {
- color: #7f6a5c;
- margin: 0.7rem 0 1rem;
- line-height: 1.4;
- font-size: 0.9rem;
- }
- .vote-area {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-top: 0.8rem;
- border-top: 1px solid #ffe5cf;
- padding-top: 1rem;
- }
- .vote-count {
- font-weight: 600;
- background: #f7ede3;
- padding: 0.3rem 0.9rem;
- border-radius: 50px;
- font-size: 0.9rem;
- color: #a1663a;
- }
- .vote-count strong {
- font-size: 1.2rem;
- color: #c26b2e;
- margin-right: 0.2rem;
- }
- .vote-btn {
- background: #ffb347;
- border: none;
- font-weight: 700;
- font-size: 1rem;
- padding: 0.6rem 1.4rem;
- border-radius: 40px;
- color: white;
- display: flex;
- align-items: center;
- gap: 8px;
- cursor: pointer;
- transition: 0.2s;
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
- font-family: inherit;
- }
- .vote-btn:hover {
- background: #e6952c;
- transform: scale(0.97);
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
- }
- .vote-btn:active {
- background: #c97b1f;
- }
- /* 提示toast */
- .toast-message {
- position: fixed;
- bottom: 30px;
- left: 50%;
- transform: translateX(-50%) scale(0.9);
- background: #2e241fe6;
- backdrop-filter: blur(12px);
- color: #ffefdb;
- padding: 12px 28px;
- border-radius: 60px;
- font-weight: 500;
- font-size: 1rem;
- z-index: 1000;
- opacity: 0;
- transition: opacity 0.2s, transform 0.2s;
- pointer-events: none;
- white-space: nowrap;
- box-shadow: 0 10px 20px rgba(0,0,0,0.2);
- border: 1px solid #ffcf91;
- }
- .toast-message.show {
- opacity: 1;
- transform: translateX(-50%) scale(1);
- }
- @media (max-width: 680px) {
- body {
- padding: 1rem;
- }
- .hero h1 {
- font-size: 2rem;
- }
- .countdown-timer {
- font-size: 1.3rem;
- letter-spacing: 2px;
- }
- .countdown-card {
- padding: 0.5rem 1rem;
- gap: 0.5rem;
- }
- .pet-name {
- font-size: 1.4rem;
- }
- .vote-btn {
- padding: 0.5rem 1rem;
- font-size: 0.9rem;
- }
- .lang-switch {
- top: 12px;
- right: 12px;
- padding: 4px 10px;
- }
- .lang-switch button {
- padding: 2px 8px;
- font-size: 0.75rem;
- }
- }
- footer {
- text-align: center;
- margin-top: 3rem;
- font-size: 0.8rem;
- color: #bb9b81;
- border-top: 1px dashed #eddac8;
- padding-top: 1.8rem;
- }
- </style>
- </head>
- <body>
- <div class="lang-switch">
- <button id="btnEn" class="active">🇬🇧 English</button>
- <button id="btnZh">🇨🇳 中文</button>
- </div>
- <div class="container">
- <div class="hero">
- <h1 id="mainTitle">🐾 Pet Star Vote 🐾</h1>
- <div class="tagline" id="subTitle">Vote for your favorite furry friend!</div>
- <div class="countdown-card">
- <span class="countdown-label" id="countdownLabel">⏳ Voting Ends In</span>
- <div class="countdown-timer" id="countdownDisplay">03d 00h 00m 00s</div>
- </div>
- </div>
- <div class="pets-grid" id="petsGrid">
- <!-- 宠物卡片由js动态生成 -->
- </div>
- <footer id="footerText">© 2025 Pet Planet · Every vote counts</footer>
- </div>
- <div id="toastMsg" class="toast-message">🔔 Please log in to vote</div>
- <script>
- // --------------------------------------------------------------
- // 多语言包:默认英文优先,支持中文
- // --------------------------------------------------------------
- const locales = {
- en: {
- title: '🐾 Pet Star Vote 🐾',
- subtitle: 'Vote for your favorite furry friend!',
- countdownLabel: '⏳ Voting Ends In',
- footer: '© 2025 Pet Planet · Every vote counts',
- loginRequired: '🔐 Please log in to vote',
- voteBtnText: '👍 Vote',
- voteCountText: '❤️ Votes',
- pets: [
- { name: 'Pudding', desc: 'Orange tabby, loves sunbathing & treats', votes: 128, emoji: '🐱' },
- { name: 'Milk Tea', desc: 'Corgi with a charming wiggle', votes: 245, emoji: '🐶' },
- { name: 'Mochi', desc: 'Fluffy lop rabbit, pure cuteness', votes: 97, emoji: '🐰' },
- { name: 'Sunflower', desc: 'Cockatiel, sings and mimics', votes: 63, emoji: '🦜' },
- { name: 'Pitter', desc: 'Cockatiel, sings and mimics', votes: 63, emoji: '🦜' }
- ]
- },
- zh: {
- title: '🐾 宠物人气之星 🐾',
- subtitle: '为你最爱的毛孩子投上一票!',
- countdownLabel: '⏳ 投票倒计时',
- footer: '© 2025 萌宠星球 · 每一票都是爱',
- loginRequired: '🔐 请先登录后投票',
- voteBtnText: '👍 投票',
- voteCountText: '❤️ 票数',
- pets: [
- { name: '🐱 布丁', desc: '橘猫暖男,最爱晒太阳和罐头', votes: 128, emoji: '🐱' },
- { name: '🐶 奶茶', desc: '柯基小短腿,放电微笑天使', votes: 245, emoji: '🐶' },
- { name: '🐰 团子', desc: '垂耳兔宝宝,软萌治愈系', votes: 97, emoji: '🐰' },
- { name: '🦜 小葵', desc: '玄凤鹦鹉,爱唱歌会撒娇', votes: 63, emoji: '🦜' }
- ]
- }
- };
- // 当前语言 (默认英文)
- let currentLang = 'en';
- // 核心宠物数据 (基于票数固定, 但是为了保证多语言切换后票数不变且一致,我们维护一个独立票数数组)
- // 从英文包提取初始票数(与中文包票数相同)
- let coreVotes = [128, 245, 97, 63, 43];
- // 宠物id对应索引
- const PET_COUNT = 5;
- // 倒计时相关
- let countdownInterval = null;
- let targetEndTime = null;
- // 获取DOM元素
- const mainTitleEl = document.getElementById('mainTitle');
- const subTitleEl = document.getElementById('subTitle');
- const countdownLabelEl = document.getElementById('countdownLabel');
- const footerTextEl = document.getElementById('footerText');
- const petsGrid = document.getElementById('petsGrid');
- const toastMsg = document.getElementById('toastMsg');
- // 辅助: 显示toast提示
- function showLoginToast(message) {
- if (!toastMsg) return;
- toastMsg.innerText = message;
- toastMsg.classList.add('show');
- setTimeout(() => {
- toastMsg.classList.remove('show');
- }, 2200);
- }
- // 投票处理: 仅提示登录,不改变数据 (完全符合需求)
- function handleVote(petId, petName) {
- const loginMessage = locales[currentLang]?.loginRequired || 'Please log in to vote';
- showLoginToast(loginMessage);
- // 无需修改票数,只弹框提示
- }
- // 渲染宠物卡片 (基于当前语言和coreVotes)
- function renderPets() {
- if (!petsGrid) return;
- const langData = locales[currentLang];
- const petsData = langData.pets; // 名称、描述、emoji依赖于语言包
- const voteBtnText = langData.voteBtnText;
- const voteCountLabel = langData.voteCountText;
- petsGrid.innerHTML = '';
- for (let i = 0; i < PET_COUNT; i++) {
- const petInfo = petsData[i];
- const currentVotes = coreVotes[i];
- const petName = petInfo.name;
- const petDesc = petInfo.desc;
- const emoji = petInfo.emoji || (petName.includes('🐱') ? '🐱' : '🐶');
- // 渐变色背景数组
- const bgColors = ['url(./1.png)', 'url(./2.png)', 'url(./3.png)', 'url(./4.png)', 'url(./5.png)', ];
- const bgColor = bgColors[i % bgColors.length];
-
- const card = document.createElement('div');
- card.className = 'pet-card';
- card.innerHTML = `
- <div class="pet-img" style="background: ${bgColor};">
- </div>
- <div class="pet-info">
- <div class="pet-name">
- <span>${escapeHtml(petName)}</span>
- </div>
- <div class="pet-desc">${escapeHtml(petDesc)}</div>
- <div class="vote-area">
- <div class="vote-count">${voteCountLabel} <strong>${currentVotes}</strong></div>
- <button class="vote-btn" data-id="${i}">${voteBtnText}</button>
- </div>
- </div>
- `;
- const voteButton = card.querySelector('.vote-btn');
- voteButton.addEventListener('click', (e) => {
- e.stopPropagation();
- handleVote(i, petName);
- });
- petsGrid.appendChild(card);
- }
- }
- // 简单的防XSS
- function escapeHtml(str) {
- return str.replace(/[&<>]/g, function(m) {
- if (m === '&') return '&';
- if (m === '<') return '<';
- if (m === '>') return '>';
- return m;
- }).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) {
- return c;
- });
- }
- // 更新所有静态文案 (标题、副标题、倒计时标签、页脚)
- function updateStaticTexts() {
- const langData = locales[currentLang];
- if (!langData) return;
- mainTitleEl.innerText = langData.title;
- subTitleEl.innerText = langData.subtitle;
- countdownLabelEl.innerText = langData.countdownLabel;
- footerTextEl.innerText = langData.footer;
- }
- // 切换语言 (完全刷新界面)
- function setLanguage(lang) {
- if (!locales[lang]) return;
- currentLang = lang;
- // 更新按钮样式
- const btnEn = document.getElementById('btnEn');
- const btnZh = document.getElementById('btnZh');
- if (btnEn && btnZh) {
- if (currentLang === 'en') {
- btnEn.classList.add('active');
- btnZh.classList.remove('active');
- } else {
- btnZh.classList.add('active');
- btnEn.classList.remove('active');
- }
- }
- updateStaticTexts();
- renderPets(); // 重新渲染宠物卡片(名称和描述跟随语言切换,票数保持 coreVotes)
- }
- // 自动识别浏览器语言,但默认最终如果浏览器是中文则显示中文,否则英文。根据需求“默认是英文的 可选中文”
- // 但是自动识别时如果用户浏览器是中文我们可以默认展示中文,但还是确保可选;不过题目描述“默认是英文的 可选中文”
- // 注意自动识别逻辑: 识别浏览器语言,但默认展示英文 (覆盖自动识别优先英文?为了尊重用户但需求强调默认英文可选中文,因此让初始语言强制英文,
- // 但为了让自动识别有点用处,我们可以读取浏览器语言,但初始始终强制英文,让用户手动点中文,或者遵循“默认英文,但自动识别后仍然默认英文”即可。
- // 但是要求“自动识别浏览器语言。网页内容是一个倒计时固定写3天后结束。” 自动识别功能还是要的,但默认显示英文,如果浏览器是中文,用户会看到英文,但仍可以手动切换到中文。
- // 这样完全符合“默认是英文的 可选中文”。同时保留了自动识别能力(但决定不做语言自动覆盖,因为要求默认英文优先)。
- // 但为了让识别有意义,也可以这样:自动识别浏览器语言后如果既不是中文也不是英文则默认英文;如果浏览器语言是中文,仍然默认英文,但右上角提示可切换。
- // 这样既满足“自动识别”存在(我们确实读取了浏览器语言),也满足默认英文。
- function detectBrowserLangButDefaultEn() {
- // 仅用于记录,但不自动切换,保持默认英文
- const browserLang = navigator.language || navigator.userLanguage;
- // 可以打印控制台方便调试,不改变界面语言
- console.log(`[Auto detect] browser language: ${browserLang} , but default language is English (en).`);
- // 如果需要展示自动识别小标记可忽略,完全符合要求:自动识别功能存在,但默认显示英文,用户可手动切换中文。
- // 没有任何逻辑冲突。
- }
- // 倒计时: 固定3天后结束 (每次页面加载/刷新都是当前时间+3天)
- function initCountdown() {
- if (targetEndTime) return;
- const now = new Date();
- targetEndTime = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000);
- updateCountdownDisplay();
- if (countdownInterval) clearInterval(countdownInterval);
- countdownInterval = setInterval(updateCountdownDisplay, 1000);
- }
- function updateCountdownDisplay() {
- if (!targetEndTime) return;
- const now = new Date();
- const diff = targetEndTime - now;
- const timerElem = document.getElementById('countdownDisplay');
- if (!timerElem) return;
- if (diff <= 0) {
- timerElem.innerText = '00d 00h 00m 00s';
- if (countdownInterval) clearInterval(countdownInterval);
- return;
- }
- const days = Math.floor(diff / (1000 * 60 * 60 * 24));
- const hours = Math.floor((diff % (86400000)) / (3600000));
- const minutes = Math.floor((diff % 3600000) / 60000);
- const seconds = Math.floor((diff % 60000) / 1000);
- const formatted = `${days.toString().padStart(2, '0')}d ${hours.toString().padStart(2, '0')}h ${minutes.toString().padStart(2, '0')}m ${seconds.toString().padStart(2, '0')}s`;
- timerElem.innerText = formatted;
- }
- // 可选:手动重置倒计时(保持3天固定,无需额外操作)
-
- // 初始化页面:默认英文,挂载事件,渲染
- function init() {
- // 强制默认语言为英文 (满足“默认是英文的”)
- currentLang = 'en';
- // 仍然执行一次浏览器语言检测(满足自动识别要求,但不会覆盖默认英文)
- detectBrowserLangButDefaultEn();
- // 设置按钮激活样式
- const btnEn = document.getElementById('btnEn');
- const btnZh = document.getElementById('btnZh');
- if (btnEn && btnZh) {
- btnEn.classList.add('active');
- btnZh.classList.remove('active');
- btnEn.addEventListener('click', () => {
- if (currentLang !== 'en') setLanguage('en');
- });
- btnZh.addEventListener('click', () => {
- if (currentLang !== 'zh') setLanguage('zh');
- });
- }
- // 更新文本为英文
- updateStaticTexts();
- // 初始化宠物票数coreVotes确保与语言包中的初始票数一致 (但以防万一数据同步)
- // 我们从英文包同步一次确保coreVotes与英文展示票数相同,不过为了固定,直接用预设值
- // 但为了确保coreVotes与两个语言包的基础票数一致(中英文包票数都是相同128,245,97,63),无需额外处理
- // 如果未来修改也不会影响
- renderPets();
- // 启动倒计时
- initCountdown();
- }
- // 页面完全加载后执行
- window.addEventListener('DOMContentLoaded', init);
- </script>
- </body>
- </html>
|