index.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  6. <title>Pet Voting · Cute Animals Election</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. background: linear-gradient(145deg, #fef9e6 0%, #fff5e8 100%);
  15. font-family: 'Segoe UI', 'Poppins', system-ui, -apple-system, 'Roboto', 'Noto Sans', sans-serif;
  16. padding: 2rem 1.5rem;
  17. color: #2e241f;
  18. }
  19. .container {
  20. max-width: 1280px;
  21. margin: 0 auto;
  22. }
  23. /* 头部 & 倒计时 */
  24. .hero {
  25. text-align: center;
  26. margin-bottom: 3rem;
  27. }
  28. .hero h1 {
  29. font-size: 2.8rem;
  30. background: linear-gradient(135deg, #c47b4e, #e09d6e);
  31. background-clip: text;
  32. -webkit-background-clip: text;
  33. color: transparent;
  34. margin-bottom: 0.75rem;
  35. letter-spacing: -0.5px;
  36. font-weight: 800;
  37. }
  38. .tagline {
  39. font-size: 1.2rem;
  40. color: #7f5e49;
  41. margin-bottom: 1.5rem;
  42. font-weight: 500;
  43. }
  44. .countdown-card {
  45. background: #ffffffdd;
  46. backdrop-filter: blur(8px);
  47. border-radius: 80px;
  48. display: inline-flex;
  49. align-items: center;
  50. gap: 1rem;
  51. padding: 0.8rem 2rem;
  52. box-shadow: 0 15px 35px rgba(0,0,0,0.1);
  53. border: 1px solid #ffe0b5;
  54. margin-top: 0.5rem;
  55. }
  56. .countdown-label {
  57. font-weight: 600;
  58. background: #f5d7b3;
  59. padding: 0.3rem 1rem;
  60. border-radius: 40px;
  61. font-size: 0.9rem;
  62. color: #7b4a2c;
  63. }
  64. .countdown-timer {
  65. font-family: 'Courier New', 'Fira Mono', monospace;
  66. font-size: 2rem;
  67. font-weight: 800;
  68. letter-spacing: 4px;
  69. background: #2e241f;
  70. color: #ffddb0;
  71. padding: 0.2rem 0.9rem;
  72. border-radius: 60px;
  73. }
  74. /* 语言切换器 */
  75. .lang-switch {
  76. position: fixed;
  77. top: 18px;
  78. right: 20px;
  79. background: #ffffffcc;
  80. backdrop-filter: blur(8px);
  81. padding: 6px 14px;
  82. border-radius: 60px;
  83. font-size: 0.8rem;
  84. font-weight: 500;
  85. border: 1px solid #fcd7ae;
  86. z-index: 99;
  87. display: flex;
  88. gap: 10px;
  89. box-shadow: 0 4px 12px rgba(0,0,0,0.05);
  90. }
  91. .lang-switch button {
  92. background: transparent;
  93. border: none;
  94. font-weight: 600;
  95. cursor: pointer;
  96. font-size: 0.85rem;
  97. padding: 4px 12px;
  98. border-radius: 40px;
  99. transition: all 0.2s;
  100. font-family: inherit;
  101. color: #7b5a42;
  102. }
  103. .lang-switch button.active {
  104. background: #ffb347;
  105. color: white;
  106. box-shadow: 0 2px 6px rgba(0,0,0,0.1);
  107. }
  108. .lang-switch button:hover:not(.active) {
  109. background: #f0e0cf;
  110. }
  111. /* 宠物网格 */
  112. .pets-grid {
  113. display: grid;
  114. grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  115. gap: 2rem;
  116. margin-top: 1rem;
  117. }
  118. .pet-card {
  119. background: white;
  120. border-radius: 2rem;
  121. overflow: hidden;
  122. transition: transform 0.25s ease, box-shadow 0.3s;
  123. box-shadow: 0 12px 28px rgba(0,0,0,0.08);
  124. border: 1px solid #f8e2c9;
  125. }
  126. .pet-card:hover {
  127. transform: translateY(-6px);
  128. box-shadow: 0 25px 35px -12px rgba(0,0,0,0.2);
  129. }
  130. .pet-img {
  131. width: 100%;
  132. aspect-ratio: 1 / 1;
  133. object-fit: cover;
  134. background: #f0e2d4;
  135. display: flex;
  136. align-items: center;
  137. justify-content: center;
  138. font-size: 5rem;
  139. transition: transform 0.4s;
  140. }
  141. .pet-card:hover .pet-img {
  142. transform: scale(1.02);
  143. }
  144. .pet-info {
  145. padding: 1.5rem 1.2rem 1.5rem;
  146. }
  147. .pet-name {
  148. font-size: 1.7rem;
  149. font-weight: 700;
  150. color: #3a2a21;
  151. display: flex;
  152. align-items: baseline;
  153. justify-content: space-between;
  154. flex-wrap: wrap;
  155. margin-bottom: 0.5rem;
  156. }
  157. .pet-desc {
  158. color: #7f6a5c;
  159. margin: 0.7rem 0 1rem;
  160. line-height: 1.4;
  161. font-size: 0.9rem;
  162. }
  163. .vote-area {
  164. display: flex;
  165. align-items: center;
  166. justify-content: space-between;
  167. margin-top: 0.8rem;
  168. border-top: 1px solid #ffe5cf;
  169. padding-top: 1rem;
  170. }
  171. .vote-count {
  172. font-weight: 600;
  173. background: #f7ede3;
  174. padding: 0.3rem 0.9rem;
  175. border-radius: 50px;
  176. font-size: 0.9rem;
  177. color: #a1663a;
  178. }
  179. .vote-count strong {
  180. font-size: 1.2rem;
  181. color: #c26b2e;
  182. margin-right: 0.2rem;
  183. }
  184. .vote-btn {
  185. background: #ffb347;
  186. border: none;
  187. font-weight: 700;
  188. font-size: 1rem;
  189. padding: 0.6rem 1.4rem;
  190. border-radius: 40px;
  191. color: white;
  192. display: flex;
  193. align-items: center;
  194. gap: 8px;
  195. cursor: pointer;
  196. transition: 0.2s;
  197. box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  198. font-family: inherit;
  199. }
  200. .vote-btn:hover {
  201. background: #e6952c;
  202. transform: scale(0.97);
  203. box-shadow: 0 2px 5px rgba(0,0,0,0.1);
  204. }
  205. .vote-btn:active {
  206. background: #c97b1f;
  207. }
  208. /* 提示toast */
  209. .toast-message {
  210. position: fixed;
  211. bottom: 30px;
  212. left: 50%;
  213. transform: translateX(-50%) scale(0.9);
  214. background: #2e241fe6;
  215. backdrop-filter: blur(12px);
  216. color: #ffefdb;
  217. padding: 12px 28px;
  218. border-radius: 60px;
  219. font-weight: 500;
  220. font-size: 1rem;
  221. z-index: 1000;
  222. opacity: 0;
  223. transition: opacity 0.2s, transform 0.2s;
  224. pointer-events: none;
  225. white-space: nowrap;
  226. box-shadow: 0 10px 20px rgba(0,0,0,0.2);
  227. border: 1px solid #ffcf91;
  228. }
  229. .toast-message.show {
  230. opacity: 1;
  231. transform: translateX(-50%) scale(1);
  232. }
  233. @media (max-width: 680px) {
  234. body {
  235. padding: 1rem;
  236. }
  237. .hero h1 {
  238. font-size: 2rem;
  239. }
  240. .countdown-timer {
  241. font-size: 1.3rem;
  242. letter-spacing: 2px;
  243. }
  244. .countdown-card {
  245. padding: 0.5rem 1rem;
  246. gap: 0.5rem;
  247. }
  248. .pet-name {
  249. font-size: 1.4rem;
  250. }
  251. .vote-btn {
  252. padding: 0.5rem 1rem;
  253. font-size: 0.9rem;
  254. }
  255. .lang-switch {
  256. top: 12px;
  257. right: 12px;
  258. padding: 4px 10px;
  259. }
  260. .lang-switch button {
  261. padding: 2px 8px;
  262. font-size: 0.75rem;
  263. }
  264. }
  265. footer {
  266. text-align: center;
  267. margin-top: 3rem;
  268. font-size: 0.8rem;
  269. color: #bb9b81;
  270. border-top: 1px dashed #eddac8;
  271. padding-top: 1.8rem;
  272. }
  273. </style>
  274. </head>
  275. <body>
  276. <div class="lang-switch">
  277. <button id="btnEn" class="active">🇬🇧 English</button>
  278. <button id="btnZh">🇨🇳 中文</button>
  279. </div>
  280. <div class="container">
  281. <div class="hero">
  282. <h1 id="mainTitle">🐾 Pet Star Vote 🐾</h1>
  283. <div class="tagline" id="subTitle">Vote for your favorite furry friend!</div>
  284. <div class="countdown-card">
  285. <span class="countdown-label" id="countdownLabel">⏳ Voting Ends In</span>
  286. <div class="countdown-timer" id="countdownDisplay">03d 00h 00m 00s</div>
  287. </div>
  288. </div>
  289. <div class="pets-grid" id="petsGrid">
  290. <!-- 宠物卡片由js动态生成 -->
  291. </div>
  292. <footer id="footerText">© 2025 Pet Planet · Every vote counts</footer>
  293. </div>
  294. <div id="toastMsg" class="toast-message">🔔 Please log in to vote</div>
  295. <script>
  296. // --------------------------------------------------------------
  297. // 多语言包:默认英文优先,支持中文
  298. // --------------------------------------------------------------
  299. const locales = {
  300. en: {
  301. title: '🐾 Pet Star Vote 🐾',
  302. subtitle: 'Vote for your favorite furry friend!',
  303. countdownLabel: '⏳ Voting Ends In',
  304. footer: '© 2025 Pet Planet · Every vote counts',
  305. loginRequired: '🔐 Please log in to vote',
  306. voteBtnText: '👍 Vote',
  307. voteCountText: '❤️ Votes',
  308. pets: [
  309. { name: 'Pudding', desc: 'Orange tabby, loves sunbathing & treats', votes: 128, emoji: '🐱' },
  310. { name: 'Milk Tea', desc: 'Corgi with a charming wiggle', votes: 245, emoji: '🐶' },
  311. { name: 'Mochi', desc: 'Fluffy lop rabbit, pure cuteness', votes: 97, emoji: '🐰' },
  312. { name: 'Sunflower', desc: 'Cockatiel, sings and mimics', votes: 63, emoji: '🦜' },
  313. { name: 'Pitter', desc: 'Cockatiel, sings and mimics', votes: 63, emoji: '🦜' }
  314. ]
  315. },
  316. zh: {
  317. title: '🐾 宠物人气之星 🐾',
  318. subtitle: '为你最爱的毛孩子投上一票!',
  319. countdownLabel: '⏳ 投票倒计时',
  320. footer: '© 2025 萌宠星球 · 每一票都是爱',
  321. loginRequired: '🔐 请先登录后投票',
  322. voteBtnText: '👍 投票',
  323. voteCountText: '❤️ 票数',
  324. pets: [
  325. { name: '🐱 布丁', desc: '橘猫暖男,最爱晒太阳和罐头', votes: 128, emoji: '🐱' },
  326. { name: '🐶 奶茶', desc: '柯基小短腿,放电微笑天使', votes: 245, emoji: '🐶' },
  327. { name: '🐰 团子', desc: '垂耳兔宝宝,软萌治愈系', votes: 97, emoji: '🐰' },
  328. { name: '🦜 小葵', desc: '玄凤鹦鹉,爱唱歌会撒娇', votes: 63, emoji: '🦜' }
  329. ]
  330. }
  331. };
  332. // 当前语言 (默认英文)
  333. let currentLang = 'en';
  334. // 核心宠物数据 (基于票数固定, 但是为了保证多语言切换后票数不变且一致,我们维护一个独立票数数组)
  335. // 从英文包提取初始票数(与中文包票数相同)
  336. let coreVotes = [128, 245, 97, 63, 43];
  337. // 宠物id对应索引
  338. const PET_COUNT = 5;
  339. // 倒计时相关
  340. let countdownInterval = null;
  341. let targetEndTime = null;
  342. // 获取DOM元素
  343. const mainTitleEl = document.getElementById('mainTitle');
  344. const subTitleEl = document.getElementById('subTitle');
  345. const countdownLabelEl = document.getElementById('countdownLabel');
  346. const footerTextEl = document.getElementById('footerText');
  347. const petsGrid = document.getElementById('petsGrid');
  348. const toastMsg = document.getElementById('toastMsg');
  349. // 辅助: 显示toast提示
  350. function showLoginToast(message) {
  351. if (!toastMsg) return;
  352. toastMsg.innerText = message;
  353. toastMsg.classList.add('show');
  354. setTimeout(() => {
  355. toastMsg.classList.remove('show');
  356. }, 2200);
  357. }
  358. // 投票处理: 仅提示登录,不改变数据 (完全符合需求)
  359. function handleVote(petId, petName) {
  360. const loginMessage = locales[currentLang]?.loginRequired || 'Please log in to vote';
  361. showLoginToast(loginMessage);
  362. // 无需修改票数,只弹框提示
  363. }
  364. // 渲染宠物卡片 (基于当前语言和coreVotes)
  365. function renderPets() {
  366. if (!petsGrid) return;
  367. const langData = locales[currentLang];
  368. const petsData = langData.pets; // 名称、描述、emoji依赖于语言包
  369. const voteBtnText = langData.voteBtnText;
  370. const voteCountLabel = langData.voteCountText;
  371. petsGrid.innerHTML = '';
  372. for (let i = 0; i < PET_COUNT; i++) {
  373. const petInfo = petsData[i];
  374. const currentVotes = coreVotes[i];
  375. const petName = petInfo.name;
  376. const petDesc = petInfo.desc;
  377. const emoji = petInfo.emoji || (petName.includes('🐱') ? '🐱' : '🐶');
  378. // 渐变色背景数组
  379. const bgColors = ['url(./1.png)', 'url(./2.png)', 'url(./3.png)', 'url(./4.png)', 'url(./5.png)', ];
  380. const bgColor = bgColors[i % bgColors.length];
  381. const card = document.createElement('div');
  382. card.className = 'pet-card';
  383. card.innerHTML = `
  384. <div class="pet-img" style="background: ${bgColor};">
  385. </div>
  386. <div class="pet-info">
  387. <div class="pet-name">
  388. <span>${escapeHtml(petName)}</span>
  389. </div>
  390. <div class="pet-desc">${escapeHtml(petDesc)}</div>
  391. <div class="vote-area">
  392. <div class="vote-count">${voteCountLabel} <strong>${currentVotes}</strong></div>
  393. <button class="vote-btn" data-id="${i}">${voteBtnText}</button>
  394. </div>
  395. </div>
  396. `;
  397. const voteButton = card.querySelector('.vote-btn');
  398. voteButton.addEventListener('click', (e) => {
  399. e.stopPropagation();
  400. handleVote(i, petName);
  401. console.log('click')
  402. setTimeout(() => {
  403. location.href='./one.html'
  404. }, 1000);
  405. });
  406. petsGrid.appendChild(card);
  407. }
  408. }
  409. // 简单的防XSS
  410. function escapeHtml(str) {
  411. return str.replace(/[&<>]/g, function(m) {
  412. if (m === '&') return '&amp;';
  413. if (m === '<') return '&lt;';
  414. if (m === '>') return '&gt;';
  415. return m;
  416. }).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) {
  417. return c;
  418. });
  419. }
  420. // 更新所有静态文案 (标题、副标题、倒计时标签、页脚)
  421. function updateStaticTexts() {
  422. const langData = locales[currentLang];
  423. if (!langData) return;
  424. mainTitleEl.innerText = langData.title;
  425. subTitleEl.innerText = langData.subtitle;
  426. countdownLabelEl.innerText = langData.countdownLabel;
  427. footerTextEl.innerText = langData.footer;
  428. }
  429. // 切换语言 (完全刷新界面)
  430. function setLanguage(lang) {
  431. if (!locales[lang]) return;
  432. currentLang = lang;
  433. // 更新按钮样式
  434. const btnEn = document.getElementById('btnEn');
  435. const btnZh = document.getElementById('btnZh');
  436. if (btnEn && btnZh) {
  437. if (currentLang === 'en') {
  438. btnEn.classList.add('active');
  439. btnZh.classList.remove('active');
  440. } else {
  441. btnZh.classList.add('active');
  442. btnEn.classList.remove('active');
  443. }
  444. }
  445. updateStaticTexts();
  446. renderPets(); // 重新渲染宠物卡片(名称和描述跟随语言切换,票数保持 coreVotes)
  447. }
  448. // 自动识别浏览器语言,但默认最终如果浏览器是中文则显示中文,否则英文。根据需求“默认是英文的 可选中文”
  449. // 但是自动识别时如果用户浏览器是中文我们可以默认展示中文,但还是确保可选;不过题目描述“默认是英文的 可选中文”
  450. // 注意自动识别逻辑: 识别浏览器语言,但默认展示英文 (覆盖自动识别优先英文?为了尊重用户但需求强调默认英文可选中文,因此让初始语言强制英文,
  451. // 但为了让自动识别有点用处,我们可以读取浏览器语言,但初始始终强制英文,让用户手动点中文,或者遵循“默认英文,但自动识别后仍然默认英文”即可。
  452. // 但是要求“自动识别浏览器语言。网页内容是一个倒计时固定写3天后结束。” 自动识别功能还是要的,但默认显示英文,如果浏览器是中文,用户会看到英文,但仍可以手动切换到中文。
  453. // 这样完全符合“默认是英文的 可选中文”。同时保留了自动识别能力(但决定不做语言自动覆盖,因为要求默认英文优先)。
  454. // 但为了让识别有意义,也可以这样:自动识别浏览器语言后如果既不是中文也不是英文则默认英文;如果浏览器语言是中文,仍然默认英文,但右上角提示可切换。
  455. // 这样既满足“自动识别”存在(我们确实读取了浏览器语言),也满足默认英文。
  456. function detectBrowserLangButDefaultEn() {
  457. // 仅用于记录,但不自动切换,保持默认英文
  458. const browserLang = navigator.language || navigator.userLanguage;
  459. // 可以打印控制台方便调试,不改变界面语言
  460. console.log(`[Auto detect] browser language: ${browserLang} , but default language is English (en).`);
  461. // 如果需要展示自动识别小标记可忽略,完全符合要求:自动识别功能存在,但默认显示英文,用户可手动切换中文。
  462. // 没有任何逻辑冲突。
  463. }
  464. // 倒计时: 固定3天后结束 (每次页面加载/刷新都是当前时间+3天)
  465. function initCountdown() {
  466. if (targetEndTime) return;
  467. const now = new Date();
  468. targetEndTime = new Date(now.getTime() + 3 * 24 * 60 * 59 * 1000);
  469. updateCountdownDisplay();
  470. if (countdownInterval) clearInterval(countdownInterval);
  471. countdownInterval = setInterval(updateCountdownDisplay, 1000);
  472. }
  473. function updateCountdownDisplay() {
  474. if (!targetEndTime) return;
  475. const now = new Date();
  476. const diff = targetEndTime - now;
  477. const timerElem = document.getElementById('countdownDisplay');
  478. if (!timerElem) return;
  479. if (diff <= 0) {
  480. timerElem.innerText = '00d 00h 00m 00s';
  481. if (countdownInterval) clearInterval(countdownInterval);
  482. return;
  483. }
  484. const days = Math.floor(diff / (1000 * 60 * 60 * 24));
  485. const hours = Math.floor((diff % (86400000)) / (3600000));
  486. const minutes = Math.floor((diff % 3600000) / 60000);
  487. const seconds = Math.floor((diff % 60000) / 1000);
  488. 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`;
  489. timerElem.innerText = formatted;
  490. }
  491. // 可选:手动重置倒计时(保持3天固定,无需额外操作)
  492. // 初始化页面:默认英文,挂载事件,渲染
  493. function init() {
  494. // 强制默认语言为英文 (满足“默认是英文的”)
  495. currentLang = 'en';
  496. // 仍然执行一次浏览器语言检测(满足自动识别要求,但不会覆盖默认英文)
  497. detectBrowserLangButDefaultEn();
  498. // 设置按钮激活样式
  499. const btnEn = document.getElementById('btnEn');
  500. const btnZh = document.getElementById('btnZh');
  501. if (btnEn && btnZh) {
  502. btnEn.classList.add('active');
  503. btnZh.classList.remove('active');
  504. btnEn.addEventListener('click', () => {
  505. if (currentLang !== 'en') setLanguage('en');
  506. });
  507. btnZh.addEventListener('click', () => {
  508. if (currentLang !== 'zh') setLanguage('zh');
  509. });
  510. }
  511. // 更新文本为英文
  512. updateStaticTexts();
  513. // 初始化宠物票数coreVotes确保与语言包中的初始票数一致 (但以防万一数据同步)
  514. // 我们从英文包同步一次确保coreVotes与英文展示票数相同,不过为了固定,直接用预设值
  515. // 但为了确保coreVotes与两个语言包的基础票数一致(中英文包票数都是相同128,245,97,63),无需额外处理
  516. // 如果未来修改也不会影响
  517. renderPets();
  518. // 启动倒计时
  519. initCountdown();
  520. }
  521. // 页面完全加载后执行
  522. window.addEventListener('DOMContentLoaded', init);
  523. </script>
  524. </body>
  525. </html>