index.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <?php
  2. $blogs = DB::selectAll("select blogs.id, title, poster, post_time from important_blogs, blogs where is_hidden = 0 and important_blogs.blog_id = blogs.id order by level desc, important_blogs.blog_id desc limit 8");
  3. $activateusers = DB::selectAll("select submitter, count(*) as cnt from submissions where datediff(now(), submit_time) <= 30 group by submitter order by cnt desc limit 12;");
  4. $topusers = DB::selectAll("select u.username, u.rating, u.rating_rd, u.rating_vol, u.motto, u.qq, count(*) cnt from user_info u inner join contests_registrants c on u.username = c.username inner join contests s on s.id = c.contest_id where datediff(now(), s.start_time) <= 180 group by c.username having cnt >= 3 order by rating desc, username asc limit 12;");
  5. $hotusers = DB::selectAll("select username, rating, rating_rd, rating_vol, qq, motto from user_info order by rating_vol desc, username asc limit 12;");
  6. $topblogs = DB::selectAll("select id from blogs where is_hidden = false and zan >= 10 order by zan desc limit 10;");
  7. $recentblogs = DB::selectAll("select id from blogs where is_hidden = false and zan < 10 order by post_time desc limit 10;");
  8. $songs_data = htmlspecialchars(json_encode(DB::selectAll("select * from songs;")), ENT_QUOTES, 'UTF-8');
  9. // 最近比赛:未结束的按开始时间升序取,不足 5 场时补最近结束的
  10. $recent_contests = array();
  11. $result = DB::selectAll("select * from contests where status != 'finished' order by start_time asc, id asc limit 5");
  12. foreach ($result as $contest) {
  13. genMoreContestInfo($contest);
  14. $recent_contests[] = $contest;
  15. }
  16. if (count($recent_contests) < 5) {
  17. $need = 5 - count($recent_contests);
  18. $result = DB::selectAll("select * from contests where status = 'finished' order by start_time desc, id desc limit $need");
  19. foreach ($result as $contest) {
  20. genMoreContestInfo($contest);
  21. $recent_contests[] = $contest;
  22. }
  23. }
  24. ?>
  25. <?php echoUOJPageHeader(UOJConfig::$data['profile']['oj-name-short']) ?>
  26. <div id="songs-data" data-songs='<?php echo $songs_data; ?>'></div>
  27. <div class="row my-4 wyoj-topcard">
  28. <div class="card-body">
  29. <div class="row">
  30. <div class="col-sm-12 col-md-9">
  31. <table class="table table-sm">
  32. <tbody>
  33. <?php $now_cnt = 0; ?>
  34. <?php foreach ($blogs as $blog): ?>
  35. <?php
  36. $now_cnt++;
  37. $new_tag = '';
  38. if ((time() - strtotime($blog['post_time'])) / 3600 / 24 <= 7) {
  39. $new_tag = '<sup style="color:red">&nbsp;new</sup>';
  40. }
  41. ?>
  42. <tr>
  43. <td><a href="/blogs/<?= $blog['id'] ?>"><?= $blog['title'] ?></a><?= $new_tag ?></td>
  44. <td>by <?= getUserLink($blog['poster']) ?></td>
  45. <td><small><?= $blog['post_time'] ?></small></td>
  46. </tr>
  47. <?php endforeach ?>
  48. <?php for ($i = $now_cnt + 1; $i <= 8; $i++): ?>
  49. <tr><td colspan="233">&nbsp;</td></tr>
  50. <?php endfor ?>
  51. <tr><td class="text-right" colspan="233">
  52. <span>本歌曲由 </span><span id="recommender"></span><span> 推荐</span>
  53. </td></tr>
  54. <tr><td class="text-right" colspan="233">
  55. <a href="https://git.ryp.org.cn/wyoj/" class="text-info font-weight-bold">WyOJ 以及所有相关项目均为自由软件。</a>
  56. <a href="/announcements"><?= UOJLocale::get('all the announcements') ?></a>
  57. </td></tr>
  58. <tr>
  59. <td class="text-right" colspan="233">
  60. <span class="text-danger font-weight-bold">声明:本网站属个人非经营性网站,仅用于学习交流,无任何盈利目的,与任何组织无关</span>
  61. <a href="/certs-manage/songs">我要推歌</a>
  62. </td>
  63. </tr>
  64. </tbody>
  65. </table>
  66. </div>
  67. <div class="col-xs-6 col-sm-4 col-md-3">
  68. <div class="card" style="max-width:300px; width:100%; border-radius:0; ">
  69. <div class="image-frame" style="aspect-ratio:1/1; position:relative;">
  70. <img id="photo" src="" alt="" style="width:100%; height:100%; object-fit:cover; display:block; cursor:pointer; -webkit-tap-highlight-color:transparent;">
  71. </div>
  72. <div class="intro" style="padding:1.6rem 1.8rem 1.2rem; text-align:center; background:#ffffff;">
  73. <p id="name" style="font-size:1.0rem; font-weight:800; letter-spacing:1px; margin-bottom:0.5rem; line-height:1.2; margin-block:0;"></p>
  74. <p id="amb" style="font-size:0.7rem; font-weight:400; color:#4f5d69; margin-bottom:0.25rem; line-height:1.5; border-top:1px dashed #cdd9e3; padding-top:0.8rem; white-space:pre-wrap; margin-block:0;"></p>
  75. <p id="writer" style="font-size:0.7rem; font-weight:400; color:#4f5d69; margin-bottom:0.25rem; line-height:1.5; white-space:pre-wrap; margin-block:0;"></p>
  76. </div>
  77. <div class="button-wrapper" style="padding:0.2rem 1.8rem 2rem 1.8rem; background:#ffffff;">
  78. <style>
  79. #randomBtn{
  80. display: inline-flex;
  81. align-items: center;
  82. justify-content: center;
  83. gap: 0.6rem;
  84. width: 100%;
  85. background: #1f2f3b;
  86. color: white;
  87. padding: 0.7rem 0;
  88. cursor: pointer;
  89. transition: all 0.2s ease;
  90. letter-spacing: 0.5px;
  91. border: 1px solid rgba(255, 255, 255, 0.2);
  92. backdrop-filter: blur(4px);
  93. font-family: inherit;
  94. line-height: 1;
  95. }
  96. </style>
  97. <button id="randomBtn">换一首</button>
  98. </div>
  99. </div>
  100. <script>
  101. (function() {
  102. let dataDiv = document.getElementById('songs-data');
  103. let library = JSON.parse(dataDiv.dataset.songs);
  104. const photoImg = document.getElementById('photo');
  105. const nameEl = document.getElementById('name');
  106. const ambEl = document.getElementById('amb');
  107. const writerEl = document.getElementById('writer');
  108. const randomBtn = document.getElementById('randomBtn');
  109. const recommenderEl = document.getElementById('recommender');
  110. let lastIndex = -1;
  111. let currentMusicLink = library[0].musiclink;
  112. photoImg.addEventListener('mousedown', function(e) {
  113. this.style.opacity = '0.8';
  114. });
  115. photoImg.addEventListener('mouseup', function(e) {
  116. this.style.opacity = '';
  117. });
  118. photoImg.addEventListener('mouseleave', function(e) {
  119. this.style.opacity = '';
  120. });
  121. photoImg.addEventListener('touchstart', function(e) {
  122. this.style.opacity = '0.8';
  123. });
  124. photoImg.addEventListener('touchend', function(e) {
  125. this.style.opacity = '';
  126. });
  127. photoImg.addEventListener('touchcancel', function(e) {
  128. this.style.opacity = '';
  129. });
  130. function setBtnActive() {
  131. randomBtn.style.background = '#17242e';
  132. randomBtn.style.transform = 'scale(0.98)';
  133. randomBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
  134. }
  135. function setBtnNormal() {
  136. randomBtn.style.background = '#1f2f3b';
  137. randomBtn.style.transform = 'scale(1)';
  138. randomBtn.style.boxShadow = 'none';
  139. }
  140. randomBtn.addEventListener('mousedown', setBtnActive);
  141. randomBtn.addEventListener('mouseup', setBtnNormal);
  142. randomBtn.addEventListener('mouseleave', setBtnNormal);
  143. randomBtn.addEventListener('touchstart', setBtnActive);
  144. randomBtn.addEventListener('touchend', setBtnNormal);
  145. randomBtn.addEventListener('touchcancel', setBtnNormal);
  146. photoImg.addEventListener('click', function() {
  147. if (currentMusicLink) {
  148. window.open(currentMusicLink, '_blank');
  149. }
  150. });
  151. function randmusic() {
  152. let randid = lastIndex;
  153. while (randid === lastIndex) {
  154. randid = Math.floor(Math.random() * library.length);
  155. }
  156. if (lastIndex == -1) randid = 3;
  157. lastIndex = randid;
  158. const ys = library[randid];
  159. photoImg.src = ys.cover;
  160. photoImg.alt = ys.name;
  161. nameEl.textContent = ys.name;
  162. ambEl.textContent = ys.album;
  163. writerEl.textContent = ys.writer;
  164. recommenderEl.textContent = ys.user;
  165. currentMusicLink = ys.click;
  166. }
  167. randmusic();
  168. randomBtn.addEventListener('click', function() {
  169. randmusic();
  170. });
  171. })();
  172. </script>
  173. </div>
  174. </div>
  175. </div>
  176. </div>
  177. <?php
  178. // ---- 日期 ----
  179. $todayTs = time();
  180. $dayNum = (int)date('j', $todayTs);
  181. $monthNum = (int)date('n', $todayTs);
  182. $wday = (int)date('w', $todayTs);
  183. $monthCN = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
  184. $weekCN = ['日','一','二','三','四','五','六'];
  185. $monthText = $monthCN[$monthNum - 1] . '月';
  186. $weekText = '星期' . $weekCN[$wday];
  187. // ---- NOIP 倒计时 ----
  188. $noipTs = strtotime('2026-11-28');
  189. $todayMidnight = strtotime(date('Y-m-d', $todayTs));
  190. $daysLeft = (int)floor(($noipTs - $todayMidnight) / 86400);
  191. ?>
  192. <?php
  193. $todayTs = time();
  194. $dayNum = (int)date('j', $todayTs);
  195. $monthNum = (int)date('n', $todayTs);
  196. $wday = (int)date('w', $todayTs);
  197. $monthCN = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
  198. $weekCN = ['日','一','二','三','四','五','六'];
  199. $bigMonths = [1,3,5,7,8,10,12];
  200. if ($monthNum == 2) {
  201. $sizeChar = '平'; // 闰年可改成 '闰'
  202. } elseif (in_array($monthNum, $bigMonths)) {
  203. $sizeChar = '大';
  204. } else {
  205. $sizeChar = '小';
  206. }
  207. $monthText = $monthCN[$monthNum - 1] . '月' . $sizeChar;
  208. $weekText = '星期' . $weekCN[$wday];
  209. $todayMidnight = strtotime(date('Y-m-d', $todayTs));
  210. $noipTs = strtotime('2026-11-28');
  211. $daysLeft = max(0, (int)floor(($noipTs - $todayMidnight) / 86400));
  212. $csj1Ts = strtotime('2026-09-19');
  213. $csp1daysLeft = max(0, (int)floor(($csp1Ts - $todayMidnight) / 86400));
  214. $csp2Ts = strtotime('2026-10-31');
  215. $csp2daysLeft = max(0, (int)floor(($csp2Ts - $todayMidnight) / 86400));
  216. $gk27Ts = strtotime('2027-06-07');
  217. $gk27daysLeft = max(0, (int)floor(($gk27Ts - $todayMidnight) / 86400));
  218. $gk28Ts = strtotime('2028-06-07');
  219. $gk28daysLeft = max(0, (int)floor(($gk28Ts - $todayMidnight) / 86400));
  220. ?>
  221. <div class="row my-4">
  222. <!-- ============ 左列:日程 + 博客 ============ -->
  223. <div class="col-12 col-lg-9">
  224. <!-- 黑色日期卡片 -->
  225. <div class="mb-4 wyoj-date-card" id="wyoj-date-card"
  226. style="position:relative;overflow:hidden;background:#000;color:#fff;padding:2rem 1.5rem;border-radius:0;text-shadow:0 1px 3px rgba(0,0,0,0.75);">
  227. <!-- 随机方格背景:canvas -->
  228. <canvas id="wyoj-date-canvas"
  229. style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none;"></canvas>
  230. <!-- 内容层 -->
  231. <div style="position:relative;z-index:1;">
  232. <div class="d-flex justify-content-center align-items-center">
  233. <div style="writing-mode:vertical-rl;font-size:1.5rem;font-weight:bold;letter-spacing:0.3em;line-height:1;">
  234. <?= $monthText ?>
  235. </div>
  236. <div class="mx-4" style="font-size:5rem;font-weight:600;line-height:1;">
  237. <?= $dayNum ?>
  238. </div>
  239. <div style="writing-mode:vertical-rl;font-size:1.5rem;font-weight:bold;letter-spacing:0.3em;line-height:1;">
  240. <?= $weekText ?>
  241. </div>
  242. </div>
  243. <div class="text-center mt-4" style="font-size:1rem;letter-spacing:0.05em;">
  244. <?php if ($daysLeft > 0): ?>
  245. 距 <strong>NOIP2026</strong> 还有 <strong><?= $daysLeft ?></strong> 天
  246. <?php elseif ($daysLeft === 0): ?>
  247. <strong>NOIP2026</strong> 就是今天
  248. <?php else: ?>
  249. <strong>NOIP2026</strong> 已结束
  250. <?php endif; ?>
  251. <br/>
  252. <?php if ($csp1daysLeft > 0): ?>
  253. 距 <strong>CSP-J/S 2026 第一轮</strong> 还有 <strong><?= $csp1daysLeft ?></strong> 天
  254. <?php elseif ($csp1daysLeft === 0): ?>
  255. <strong>CSP-J/S 2026 第一轮</strong> 就是今天
  256. <?php else: ?>
  257. <strong>CSP-J/S 2026 第一轮</strong> 已结束
  258. <?php endif; ?>
  259. <br/>
  260. <?php if ($csp2daysLeft > 0): ?>
  261. 距 <strong>CSP-J/S 2026 第二轮</strong> 还有 <strong><?= $csp2daysLeft ?></strong> 天
  262. <?php elseif ($csp2daysLeft === 0): ?>
  263. <strong>CSP-J/S 2026 第二轮</strong> 就是今天!
  264. <?php else: ?>
  265. <strong>CSP-J/S 2026 第二轮</strong> 已结束
  266. <?php endif; ?>
  267. <br/>
  268. <?php if ($gk27daysLeft > 0): ?>
  269. 距 <strong>2027 年普通高等学校招生全国统一考试</strong> 还有 <strong><?= $gk27daysLeft ?></strong> 天
  270. <?php elseif ($gk27daysLeft === 0): ?>
  271. <strong>2027 年普通高等学校招生全国统一考试</strong> 就是今天!
  272. <?php else: ?>
  273. <strong>2027 年普通高等学校招生全国统一考试</strong> 已结束
  274. <?php endif; ?>
  275. <br/>
  276. <?php if ($gk28daysLeft > 0): ?>
  277. 距 <strong>2028 年普通高等学校招生全国统一考试</strong> 还有 <strong><?= $gk28daysLeft ?></strong> 天
  278. <?php elseif ($gk28daysLeft === 0): ?>
  279. <strong>2028 年普通高等学校招生全国统一考试</strong> 就是今天!
  280. <?php else: ?>
  281. <strong>2028 年普通高等学校招生全国统一考试</strong> 已结束
  282. <?php endif; ?>
  283. </div>
  284. </div>
  285. </div>
  286. <script>
  287. (function () {
  288. var card = document.getElementById('wyoj-date-card');
  289. var canvas = document.getElementById('wyoj-date-canvas');
  290. if (!card || !canvas) return;
  291. var ctx = canvas.getContext('2d');
  292. // ===== 可调参数 =====
  293. var COLORS = ['#000000', '#0c0c0c', '#181818', '#242424', '#303030'];
  294. var CELL = 26; // 每格边长(px)
  295. var P = 0.3; // 每格每轮变色概率(期望值)
  296. var PERIOD = 1500; // 刷新间隔(ms)
  297. var Z = 1.96; // 95% 置信区间
  298. var USE_DPR = false; // 是否按设备像素比放大 canvas(false 更省 CPU)
  299. // ====================
  300. var dpr = USE_DPR ? (window.devicePixelRatio || 1) : 1;
  301. var cols = 0, rows = 0, nCells = 0;
  302. var lastW = 0, lastH = 0;
  303. var gridColors = [];
  304. var idx = null;
  305. var xs = null, ys = null;
  306. var rafId = null;
  307. var running = false;
  308. var lastTick = 0;
  309. function randColor() {
  310. return COLORS[(Math.random() * COLORS.length) | 0];
  311. }
  312. function computeBounds() {
  313. var w = card.clientWidth;
  314. var h = card.clientHeight;
  315. xs = new Int32Array(cols + 1);
  316. ys = new Int32Array(rows + 1);
  317. for (var i = 0; i <= cols; i++) xs[i] = Math.round(i * w / cols);
  318. for (var j = 0; j <= rows; j++) ys[j] = Math.round(j * h / rows);
  319. }
  320. function build() {
  321. var w = card.clientWidth;
  322. var h = card.clientHeight;
  323. if (w === 0 || h === 0) return;
  324. if (w === lastW && h === lastH) return; // 尺寸没变就不重建
  325. lastW = w; lastH = h;
  326. cols = Math.max(1, Math.ceil(w / CELL));
  327. rows = Math.max(1, Math.ceil(h / CELL));
  328. nCells = cols * rows;
  329. canvas.width = Math.round(w * dpr);
  330. canvas.height = Math.round(h * dpr);
  331. canvas.style.width = w + 'px';
  332. canvas.style.height = h + 'px';
  333. ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  334. ctx.imageSmoothingEnabled = false;
  335. computeBounds();
  336. gridColors = [];
  337. for (var y = 0; y < rows; y++) {
  338. var row = [];
  339. for (var x = 0; x < cols; x++) row.push(randColor());
  340. gridColors.push(row);
  341. }
  342. idx = new Int32Array(nCells);
  343. for (var i = 0; i < nCells; i++) idx[i] = i;
  344. redrawAll();
  345. }
  346. function redrawAll() {
  347. for (var y = 0; y < rows; y++) {
  348. var y0 = ys[y], y1 = ys[y + 1];
  349. for (var x = 0; x < cols; x++) {
  350. ctx.fillStyle = gridColors[y][x];
  351. ctx.fillRect(xs[x], y0, xs[x + 1] - xs[x], y1 - y0);
  352. }
  353. }
  354. }
  355. function tick() {
  356. if (nCells === 0) return;
  357. var mu = nCells * P;
  358. var sigma = Math.sqrt(nCells * P * (1 - P));
  359. var lo = Math.max(0, Math.floor(mu - Z * sigma));
  360. var hi = Math.min(nCells, Math.ceil(mu + Z * sigma));
  361. var m = lo + Math.floor(Math.random() * (hi - lo + 1));
  362. for (var i = 0; i < m; i++) {
  363. var j = i + Math.floor(Math.random() * (nCells - i));
  364. var t = idx[i]; idx[i] = idx[j]; idx[j] = t;
  365. }
  366. for (var k = 0; k < m; k++) {
  367. var ci = idx[k];
  368. var x = ci % cols;
  369. var y = (ci / cols) | 0;
  370. var c = randColor();
  371. gridColors[y][x] = c;
  372. ctx.fillStyle = c;
  373. ctx.fillRect(xs[x], ys[y], xs[x + 1] - xs[x], ys[y + 1] - ys[y]);
  374. }
  375. }
  376. function loop(now) {
  377. rafId = null;
  378. if (now - lastTick >= PERIOD) {
  379. lastTick = now;
  380. tick();
  381. }
  382. if (running) rafId = requestAnimationFrame(loop);
  383. }
  384. function start() {
  385. if (running) return;
  386. running = true;
  387. lastTick = performance.now();
  388. rafId = requestAnimationFrame(loop);
  389. }
  390. function stop() {
  391. running = false;
  392. if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
  393. }
  394. build();
  395. if (window.ResizeObserver) {
  396. new ResizeObserver(function () { build(); }).observe(card);
  397. } else {
  398. window.addEventListener('resize', build);
  399. }
  400. if (window.IntersectionObserver) {
  401. new IntersectionObserver(function (entries) {
  402. if (entries[0].isIntersecting) start(); else stop();
  403. }, { threshold: 0 }).observe(card);
  404. } else {
  405. start();
  406. }
  407. document.addEventListener('visibilitychange', function () {
  408. if (document.hidden) stop(); else start();
  409. });
  410. })();
  411. </script>
  412. <!-- 博客列表 -->
  413. <div class="row">
  414. <?php foreach ($recentblogs as $blog): ?>
  415. <?= echoBlogCard(queryBlog($blog['id'])) ?>
  416. <?php endforeach ?>
  417. <?php foreach ($topblogs as $blog): ?>
  418. <?= echoBlogCard(queryBlog($blog['id'])) ?>
  419. <?php endforeach ?>
  420. </div>
  421. </div>
  422. <!-- ============ 右列:排行榜 ============ -->
  423. <div class="col-12 col-lg-3">
  424. <!-- 活跃 -->
  425. <div class="card shadow-sm border-0 mb-3">
  426. <div class="card-body p-3">
  427. <h6 class="font-weight-bold mb-3">活跃排行榜</h6>
  428. <?php foreach ($activateusers as $username): ?>
  429. <?php $user = queryUser($username['submitter']); ?>
  430. <div class="d-flex align-items-start border-bottom pb-2 mb-2">
  431. <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
  432. style="width:36px;height:36px;object-fit:cover;"
  433. src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
  434. <div class="flex-grow-1" style="min-width:0;">
  435. <div class="text-truncate font-weight-bold" style="font-size:0.9rem;">
  436. <?= getUserLink($user['username']) ?>
  437. </div>
  438. <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
  439. <?= $user['motto'] ?>
  440. </div>
  441. </div>
  442. </div>
  443. <?php endforeach ?>
  444. </div>
  445. </div>
  446. <!-- 比赛 -->
  447. <div class="card shadow-sm border-0 mb-3">
  448. <div class="card-body p-3">
  449. <h6 class="font-weight-bold mb-1">比赛排行榜</h6>
  450. <div class="mb-3" style="font-size:0.75rem;">
  451. <span class="text-muted">180 天内三场及以上</span>
  452. <a href="/ranklist" class="ml-1">全部排名</a>
  453. </div>
  454. <?php foreach ($topusers as $user): ?>
  455. <div class="d-flex align-items-start border-bottom pb-2 mb-2">
  456. <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
  457. style="width:36px;height:36px;object-fit:cover;"
  458. src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
  459. <div class="flex-grow-1" style="min-width:0;">
  460. <div class="d-flex justify-content-between align-items-baseline">
  461. <span class="text-truncate font-weight-bold" style="font-size:0.9rem;">
  462. <?= getUserLink($user['username']) ?>
  463. </span>
  464. <strong class="text-danger ml-1 flex-shrink-0" style="font-size:0.75rem;">
  465. <?= $user['rating'] ?> <small>(±<?= round($user['rating_rd']) * 2 ?>)</small>
  466. </strong>
  467. </div>
  468. <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
  469. <?= $user['motto'] ?>
  470. </div>
  471. </div>
  472. </div>
  473. <?php endforeach ?>
  474. </div>
  475. </div>
  476. <!-- 黑马 -->
  477. <div class="card shadow-sm border-0 mb-3">
  478. <div class="card-body p-3">
  479. <h6 class="font-weight-bold mb-3">黑马 / 爆冷榜</h6>
  480. <?php foreach ($hotusers as $user): ?>
  481. <div class="d-flex align-items-start border-bottom pb-2 mb-2">
  482. <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
  483. style="width:36px;height:36px;object-fit:cover;"
  484. src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
  485. <div class="flex-grow-1" style="min-width:0;">
  486. <div class="d-flex justify-content-between align-items-baseline">
  487. <span class="text-truncate font-weight-bold" style="font-size:0.9rem;">
  488. <?= getUserLink($user['username']) ?>
  489. </span>
  490. <strong class="text-danger ml-1 flex-shrink-0" style="font-size:0.75rem;">
  491. Vol <?= round(1000 * $user['rating_vol']) / 1000 ?>
  492. </strong>
  493. </div>
  494. <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
  495. <?= $user['motto'] ?>
  496. </div>
  497. </div>
  498. </div>
  499. <?php endforeach ?>
  500. </div>
  501. </div>
  502. </div>
  503. </div>
  504. <!-- 博客卡片悬停效果 -->
  505. <style>
  506. .wyoj-blog-card {
  507. transition: box-shadow .2s ease;
  508. }
  509. .wyoj-blog-card:hover {
  510. box-shadow: 0 4px 14px rgba(0,0,0,.10) !important;
  511. }
  512. .wyoj-blog-card .wyoj-blog-title {
  513. font-size: 1.05rem;
  514. line-height: 1.5;
  515. }
  516. </style>
  517. <?php echoUOJPageFooter() ?>
  518. <div class="row my-4">