02-light-minimal.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  6. <title>考试投屏时钟</title>
  7. <style>
  8. *{box-sizing:border-box}html,body{height:100%;margin:0}body{min-height:100%;overflow:hidden;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Arial,sans-serif}.screen{height:100%;display:grid;grid-template-rows:auto 1fr auto;padding:clamp(18px,3.2vw,48px);gap:clamp(14px,2vw,28px)}.nav,.dock{display:flex;align-items:center;justify-content:space-between;gap:18px}.brand{display:flex;align-items:center;gap:12px;min-width:0}.logo{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;font-weight:800}.brand-title{font-size:clamp(15px,1.4vw,22px);font-weight:650;letter-spacing:-.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pill{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}.dot{width:8px;height:8px;border-radius:50%}.hero{display:grid;place-items:center;text-align:center;min-height:0}.hero-inner{width:min(1500px,100%)}.eyebrow{margin:0 0 clamp(10px,1.4vw,22px);font-size:clamp(20px,2.25vw,40px);font-weight:520;letter-spacing:.16em;text-transform:uppercase}.clock-card{position:relative;margin:auto;overflow:hidden}.clock{position:relative;margin:0;font-family:"DIN Alternate","Arial Narrow","Helvetica Neue",Arial,"PingFang SC","Microsoft YaHei",sans-serif;font-variant-numeric:tabular-nums;font-size:clamp(88px,17.2vw,268px);font-weight:700;line-height:.92;letter-spacing:.018em;word-spacing:.03em}.seconds{display:inline;color:inherit;font-size:1em;margin-left:0;letter-spacing:inherit;vertical-align:baseline;text-shadow:inherit}.date{margin:clamp(16px,2vw,30px) 0 0;font-size:clamp(22px,2.6vw,46px);font-weight:500;letter-spacing:.06em}.timer{display:none;width:min(1160px,96%);margin:clamp(22px,3vh,44px) auto 0;padding:clamp(18px,2.6vw,34px)}.timer-top{display:grid;grid-template-columns:1fr auto;align-items:end;gap:18px;text-align:left}.timer-label{font-size:clamp(18px,1.8vw,32px);letter-spacing:.12em;font-weight:620}.remain{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-variant-numeric:tabular-nums;font-size:clamp(44px,7.2vw,116px);font-weight:850;line-height:.9;letter-spacing:-.06em}.progress{height:clamp(12px,1vw,18px);margin-top:22px;overflow:hidden}.bar{height:100%;width:100%;transition:width .35s linear,background .3s ease}.dock{padding:14px 18px}.help{font-size:clamp(13px,1.2vw,18px);line-height:1.65}.help b{font-weight:650}.actions{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end;align-items:center}button,a.btn{appearance:none;text-decoration:none;padding:12px 18px;font-size:15px;line-height:1;cursor:pointer;transition:.18s ease}.title-input{width:190px;max-width:32vw;padding:11px 16px;font-size:15px;line-height:1;outline:none;transition:.18s ease}.title-input::placeholder{opacity:.65}button:hover,a.btn:hover{transform:translateY(-1px)}@media(max-width:760px){body{overflow:auto}.screen{min-height:100%;height:auto}.nav,.dock{align-items:flex-start}.pill.hide-sm{display:none}.timer-top{grid-template-columns:1fr;text-align:center}.timer-label{text-align:center}.dock{display:block;text-align:center}.actions{justify-content:center;margin-top:14px}.title-input{width:100%;max-width:280px}.clock{font-size:clamp(78px,22vw,160px)}}
  9. body{color:#111827;background:#f5f7fb}body:before{content:"";position:fixed;inset:0;background:radial-gradient(circle at 20% 15%,#dbeafe,transparent 32%),radial-gradient(circle at 85% 70%,#dcfce7,transparent 32%),#f8fafc;z-index:-1}.orb{display:none}.nav,.dock{border:1px solid #e5e7eb;border-radius:24px;background:rgba(255,255,255,.86);box-shadow:0 18px 50px rgba(15,23,42,.08)}.logo{background:#111827;color:#fff}.pill,button,a.btn,.title-input{border:1px solid #e5e7eb;background:#fff;color:#111827;border-radius:999px}.dot{background:#16a34a}.eyebrow{color:#64748b}.date,.help,.timer-label{color:#475569}.clock-card{padding:clamp(22px,4.8vw,72px) clamp(18px,5vw,82px);border-radius:36px;background:#fff;border:1px solid #e5e7eb;box-shadow:0 28px 80px rgba(15,23,42,.12)}.clock{color:#020617}.timer{border-radius:28px;background:#fff;border:1px solid #e5e7eb}.progress{border-radius:999px;background:#e5e7eb}.bar{border-radius:999px;background:linear-gradient(90deg,#2563eb,#22c55e)}button.primary,a.primary{background:#111827;color:#fff;border-color:#111827}
  10. body.warn .bar{background:linear-gradient(90deg,#ffd60a,#ff9f0a)}body.danger .bar{background:linear-gradient(90deg,#ff453a,#ff9f0a)}body.danger .remain,body.danger .seconds{color:#ff453a}body.done .remain{color:#ff453a}
  11. /* 笔记本/教室投屏适配:常见 1366×768、1440×900、1280×720,避免底部被遮挡 */
  12. @media (max-width: 1500px), (max-height: 900px){
  13. .screen{padding:clamp(12px,2.1vw,30px);gap:clamp(8px,1.25vw,18px);grid-template-rows:auto minmax(0,1fr) auto}
  14. .nav{min-height:52px;padding:10px 16px;border-radius:20px}
  15. .dock{padding:10px 14px;border-radius:20px;gap:12px}
  16. .logo{width:32px;height:32px;font-size:15px}.brand-title{font-size:clamp(14px,1.25vw,18px)}
  17. .pill{padding:9px 12px;font-size:13px}.eyebrow{font-size:clamp(16px,1.65vw,26px);margin-bottom:clamp(8px,1vw,14px);letter-spacing:.12em}
  18. .clock-card{padding:clamp(16px,2.8vw,38px) clamp(18px,3.8vw,56px)}
  19. .clock{font-size:clamp(76px,13.4vw,188px);line-height:.9;letter-spacing:.008em}
  20. .date{font-size:clamp(17px,1.75vw,28px);margin-top:clamp(10px,1.2vw,18px)}
  21. .timer{width:min(980px,96%);margin-top:clamp(12px,1.8vh,22px);padding:clamp(12px,1.8vw,22px)}
  22. .timer-label{font-size:clamp(15px,1.35vw,22px)}.remain{font-size:clamp(34px,5vw,70px)}.progress{height:10px;margin-top:14px}
  23. .help{font-size:clamp(12px,1vw,15px);line-height:1.45}.actions{gap:8px}button,a.btn,.title-input{padding:9px 12px;font-size:13px}.title-input{width:150px}
  24. }
  25. @media (max-height: 760px){
  26. .screen{padding:10px 16px;gap:8px}.nav{min-height:46px;padding:8px 14px}.dock{padding:8px 12px}.pill.hide-sm{display:none}
  27. .eyebrow{font-size:16px;margin-bottom:7px}.clock-card{padding:14px 28px}.clock{font-size:clamp(68px,12.2vw,158px)}.date{font-size:18px;margin-top:8px}
  28. .timer{margin-top:10px;padding:10px 16px}.remain{font-size:clamp(30px,4.5vw,58px)}.timer-label{font-size:15px}.progress{height:8px;margin-top:10px}
  29. .help{display:none}button,a.btn,.title-input{padding:8px 10px;font-size:12px}.title-input{width:132px}.logo{width:30px;height:30px}.brand-title{font-size:14px}
  30. }
  31. @media (max-height: 680px){
  32. .dock{display:none}.screen{grid-template-rows:auto minmax(0,1fr);padding:8px 14px}.clock{font-size:clamp(62px,11.8vw,142px)}.timer{padding:8px 14px}.date{font-size:16px}
  33. }
  34. @media (min-width: 900px) and (max-width: 1368px){
  35. .clock{font-size:clamp(72px,12.6vw,172px)}.clock-card{padding:18px 42px}.hero-inner{width:min(1180px,100%)}
  36. }
  37. </style>
  38. </head>
  39. <body>
  40. <div class="orb one"></div><div class="orb two"></div>
  41. <div class="screen">
  42. <header class="nav">
  43. <div class="brand"><div class="logo">考</div><div class="brand-title" id="examTitle">考试投屏时钟</div></div>
  44. <div style="display:flex;gap:10px;align-items:center;min-width:0">
  45. <div class="pill hide-sm"><span class="dot"></span><span id="statusText">北京时间 · 实时同步</span></div>
  46. <div class="pill" id="ampm">--</div>
  47. </div>
  48. </header>
  49. <main class="hero">
  50. <div class="hero-inner">
  51. <p class="eyebrow" id="pageTitle">EXAM CLOCK</p>
  52. <section class="clock-card">
  53. <p class="clock"><span id="hhmm">--:--</span>:<span class="seconds" id="ss">--</span></p>
  54. <div class="date" id="dateText">----</div>
  55. </section>
  56. <section class="timer" id="timerPanel">
  57. <div class="timer-top">
  58. <div class="timer-label" id="timerLabel">距离考试结束</div>
  59. <div class="remain" id="remainText">--:--:--</div>
  60. </div>
  61. <div class="progress"><div class="bar" id="bar"></div></div>
  62. </section>
  63. </div>
  64. </main>
  65. <footer class="dock">
  66. <div class="help" id="hintText">投屏建议点击 <b>全屏显示</b>;地址后加 <b>?duration=120</b> 可显示 120 分钟倒计时。</div>
  67. <div class="actions">
  68. <input class="title-input" id="titleInput" type="text" maxlength="30" placeholder="输入标题" autocomplete="off">
  69. <button type="button" onclick="applyTitle()">添加标题</button>
  70. <button class="primary" type="button" onclick="toggleFullScreen()">全屏显示</button>
  71. <a class="btn" href="?duration=120">120分钟倒计时</a>
  72. <button type="button" onclick="location.href=location.pathname">只显示时钟</button>
  73. </div>
  74. </footer>
  75. </div>
  76. <script>
  77. const $=(id)=>document.getElementById(id);
  78. const params=new URLSearchParams(location.search);
  79. const title=params.get('title')||'考试投屏时钟';
  80. const label=params.get('label')||'距离考试结束';
  81. const durationMin=Number(params.get('duration')||0);
  82. const endParam=params.get('end');
  83. const startTs=Date.now();
  84. let endTs=null;
  85. $('examTitle').textContent=title; document.title=title; $('timerLabel').textContent=label;
  86. $('titleInput').value=params.get('title')||'';
  87. if(params.get('title')) $('pageTitle').textContent=title; else $('pageTitle').textContent='EXAM CLOCK';
  88. if(endParam){const parsed=new Date(endParam.replace(' ','T')).getTime(); if(!Number.isNaN(parsed)) endTs=parsed;}
  89. else if(durationMin>0){endTs=startTs+durationMin*60*1000;}
  90. if(endTs){$('timerPanel').style.display='block';$('hintText').innerHTML='倒计时已开启;可用参数:<b>?duration=分钟&title=标题</b>,或点击全屏投放。';}
  91. function pad(n){return String(n).padStart(2,'0')}
  92. function formatRemain(ms){const total=Math.max(0,Math.ceil(ms/1000));const h=Math.floor(total/3600),m=Math.floor((total%3600)/60),s=total%60;return `${pad(h)}:${pad(m)}:${pad(s)}`}
  93. function tick(){
  94. const now=new Date(),w=['星期日','星期一','星期二','星期三','星期四','星期五','星期六'];
  95. $('hhmm').textContent=`${pad(now.getHours())}:${pad(now.getMinutes())}`;$('ss').textContent=pad(now.getSeconds());
  96. $('dateText').textContent=`${now.getFullYear()}年${pad(now.getMonth()+1)}月${pad(now.getDate())}日 ${w[now.getDay()]}`;
  97. $('ampm').textContent=now.getHours()<12?'上午':'下午';
  98. if(endTs){const remain=endTs-now.getTime(),total=Math.max(1,endTs-startTs),pct=Math.max(0,Math.min(100,remain/total*100));
  99. $('remainText').textContent=remain<=0?'考试结束':formatRemain(remain);$('bar').style.width=pct+'%';
  100. document.body.classList.toggle('warn',pct<=25&&pct>10);document.body.classList.toggle('danger',pct<=10&&pct>0);document.body.classList.toggle('done',remain<=0);
  101. $('statusText').textContent=remain<=0?'考试已结束':'倒计时进行中';
  102. }
  103. }
  104. function applyTitle(){
  105. const value=$('titleInput').value.trim();
  106. const url=new URL(location.href);
  107. if(value) url.searchParams.set('title',value); else url.searchParams.delete('title');
  108. location.href=url.pathname+url.search;
  109. }
  110. $('titleInput').addEventListener('keydown',function(e){if(e.key==='Enter')applyTitle();});
  111. function toggleFullScreen(){if(!document.fullscreenElement)document.documentElement.requestFullscreen?.();else document.exitFullscreen?.();}
  112. tick();setInterval(tick,250);
  113. </script>
  114. </body>
  115. </html>