|
@@ -1,573 +1,892 @@
|
|
|
-<?php
|
|
|
|
|
-$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");
|
|
|
|
|
-$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;");
|
|
|
|
|
-$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;");
|
|
|
|
|
-$hotusers = DB::selectAll("select username, rating, rating_rd, rating_vol, qq, motto from user_info order by rating_vol desc, username asc limit 12;");
|
|
|
|
|
-$topblogs = DB::selectAll("select id from blogs where is_hidden = false and zan >= 10 order by zan desc limit 10;");
|
|
|
|
|
-$recentblogs = DB::selectAll("select id from blogs where is_hidden = false and zan < 10 order by post_time desc limit 10;");
|
|
|
|
|
-$songs_data = htmlspecialchars(json_encode(DB::selectAll("select * from songs;")), ENT_QUOTES, 'UTF-8');
|
|
|
|
|
-
|
|
|
|
|
-// 最近比赛:未结束的按开始时间升序取,不足 5 场时补最近结束的
|
|
|
|
|
-$recent_contests = array();
|
|
|
|
|
-$result = DB::selectAll("select * from contests where status != 'finished' order by start_time asc, id asc limit 5");
|
|
|
|
|
-foreach ($result as $contest) {
|
|
|
|
|
- genMoreContestInfo($contest);
|
|
|
|
|
- $recent_contests[] = $contest;
|
|
|
|
|
-}
|
|
|
|
|
-if (count($recent_contests) < 5) {
|
|
|
|
|
- $need = 5 - count($recent_contests);
|
|
|
|
|
- $result = DB::selectAll("select * from contests where status = 'finished' order by start_time desc, id desc limit $need");
|
|
|
|
|
- foreach ($result as $contest) {
|
|
|
|
|
- genMoreContestInfo($contest);
|
|
|
|
|
- $recent_contests[] = $contest;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-?>
|
|
|
|
|
-<?php echoUOJPageHeader(UOJConfig::$data['profile']['oj-name-short']) ?>
|
|
|
|
|
-<div id="songs-data" data-songs='<?php echo $songs_data; ?>'></div>
|
|
|
|
|
-
|
|
|
|
|
-<div class="row my-4 wyoj-topcard">
|
|
|
|
|
- <div class="card-body">
|
|
|
|
|
- <div class="row">
|
|
|
|
|
- <div class="col-sm-12 col-md-9">
|
|
|
|
|
- <table class="table table-sm">
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <?php $now_cnt = 0; ?>
|
|
|
|
|
- <?php foreach ($blogs as $blog): ?>
|
|
|
|
|
- <?php
|
|
|
|
|
- $now_cnt++;
|
|
|
|
|
- $new_tag = '';
|
|
|
|
|
- if ((time() - strtotime($blog['post_time'])) / 3600 / 24 <= 7) {
|
|
|
|
|
- $new_tag = '<sup style="color:red"> new</sup>';
|
|
|
|
|
- }
|
|
|
|
|
- ?>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <td><a href="/blogs/<?= $blog['id'] ?>"><?= $blog['title'] ?></a><?= $new_tag ?></td>
|
|
|
|
|
- <td>by <?= getUserLink($blog['poster']) ?></td>
|
|
|
|
|
- <td><small><?= $blog['post_time'] ?></small></td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- <?php endforeach ?>
|
|
|
|
|
- <?php for ($i = $now_cnt + 1; $i <= 8; $i++): ?>
|
|
|
|
|
- <tr><td colspan="233"> </td></tr>
|
|
|
|
|
- <?php endfor ?>
|
|
|
|
|
- <tr><td class="text-right" colspan="233">
|
|
|
|
|
- <span>本歌曲由 </span><span id="recommender"></span><span> 推荐</span>
|
|
|
|
|
- </td></tr>
|
|
|
|
|
- <tr><td class="text-right" colspan="233">
|
|
|
|
|
- <a href="https://git.ryp.org.cn/wyoj/" class="text-info font-weight-bold">WyOJ 以及所有相关项目均为自由软件。</a>
|
|
|
|
|
- <a href="/announcements"><?= UOJLocale::get('all the announcements') ?></a>
|
|
|
|
|
- </td></tr>
|
|
|
|
|
- <tr>
|
|
|
|
|
- <td class="text-right" colspan="233">
|
|
|
|
|
- <span class="text-danger font-weight-bold">声明:本网站属个人非经营性网站,仅用于学习交流,无任何盈利目的,与任何组织无关</span>
|
|
|
|
|
- <a href="/certs-manage/songs">我要推歌</a>
|
|
|
|
|
- </td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- </tbody>
|
|
|
|
|
- </table>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="col-xs-6 col-sm-4 col-md-3">
|
|
|
|
|
- <div class="card" style="max-width:300px; width:100%; border-radius:0; ">
|
|
|
|
|
- <div class="image-frame" style="aspect-ratio:1/1; position:relative;">
|
|
|
|
|
- <img id="photo" src="" alt="" style="width:100%; height:100%; object-fit:cover; display:block; cursor:pointer; -webkit-tap-highlight-color:transparent;">
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="intro" style="padding:1.6rem 1.8rem 1.2rem; text-align:center; background:#ffffff;">
|
|
|
|
|
- <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>
|
|
|
|
|
- <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>
|
|
|
|
|
- <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>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="button-wrapper" style="padding:0.2rem 1.8rem 2rem 1.8rem; background:#ffffff;">
|
|
|
|
|
- <style>
|
|
|
|
|
- #randomBtn{
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- gap: 0.6rem;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- background: #1f2f3b;
|
|
|
|
|
- color: white;
|
|
|
|
|
- padding: 0.7rem 0;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: all 0.2s ease;
|
|
|
|
|
- letter-spacing: 0.5px;
|
|
|
|
|
- border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
|
- backdrop-filter: blur(4px);
|
|
|
|
|
- font-family: inherit;
|
|
|
|
|
- line-height: 1;
|
|
|
|
|
- }
|
|
|
|
|
- </style>
|
|
|
|
|
- <button id="randomBtn">换一首</button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <script>
|
|
|
|
|
- (function() {
|
|
|
|
|
- let dataDiv = document.getElementById('songs-data');
|
|
|
|
|
- let library = JSON.parse(dataDiv.dataset.songs);
|
|
|
|
|
-
|
|
|
|
|
- const photoImg = document.getElementById('photo');
|
|
|
|
|
- const nameEl = document.getElementById('name');
|
|
|
|
|
- const ambEl = document.getElementById('amb');
|
|
|
|
|
- const writerEl = document.getElementById('writer');
|
|
|
|
|
- const randomBtn = document.getElementById('randomBtn');
|
|
|
|
|
- const recommenderEl = document.getElementById('recommender');
|
|
|
|
|
-
|
|
|
|
|
- let lastIndex = -1;
|
|
|
|
|
- let currentMusicLink = library[0].musiclink;
|
|
|
|
|
-
|
|
|
|
|
- photoImg.addEventListener('mousedown', function(e) {
|
|
|
|
|
- this.style.opacity = '0.8';
|
|
|
|
|
- });
|
|
|
|
|
- photoImg.addEventListener('mouseup', function(e) {
|
|
|
|
|
- this.style.opacity = '';
|
|
|
|
|
- });
|
|
|
|
|
- photoImg.addEventListener('mouseleave', function(e) {
|
|
|
|
|
- this.style.opacity = '';
|
|
|
|
|
- });
|
|
|
|
|
- photoImg.addEventListener('touchstart', function(e) {
|
|
|
|
|
- this.style.opacity = '0.8';
|
|
|
|
|
- });
|
|
|
|
|
- photoImg.addEventListener('touchend', function(e) {
|
|
|
|
|
- this.style.opacity = '';
|
|
|
|
|
- });
|
|
|
|
|
- photoImg.addEventListener('touchcancel', function(e) {
|
|
|
|
|
- this.style.opacity = '';
|
|
|
|
|
- });
|
|
|
|
|
- function setBtnActive() {
|
|
|
|
|
- randomBtn.style.background = '#17242e';
|
|
|
|
|
- randomBtn.style.transform = 'scale(0.98)';
|
|
|
|
|
- randomBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
|
|
|
|
|
- }
|
|
|
|
|
- function setBtnNormal() {
|
|
|
|
|
- randomBtn.style.background = '#1f2f3b';
|
|
|
|
|
- randomBtn.style.transform = 'scale(1)';
|
|
|
|
|
- randomBtn.style.boxShadow = 'none';
|
|
|
|
|
- }
|
|
|
|
|
- randomBtn.addEventListener('mousedown', setBtnActive);
|
|
|
|
|
- randomBtn.addEventListener('mouseup', setBtnNormal);
|
|
|
|
|
- randomBtn.addEventListener('mouseleave', setBtnNormal);
|
|
|
|
|
- randomBtn.addEventListener('touchstart', setBtnActive);
|
|
|
|
|
- randomBtn.addEventListener('touchend', setBtnNormal);
|
|
|
|
|
- randomBtn.addEventListener('touchcancel', setBtnNormal);
|
|
|
|
|
-
|
|
|
|
|
- photoImg.addEventListener('click', function() {
|
|
|
|
|
- if (currentMusicLink) {
|
|
|
|
|
- window.open(currentMusicLink, '_blank');
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- function randmusic() {
|
|
|
|
|
- let randid = lastIndex;
|
|
|
|
|
- while (randid === lastIndex) {
|
|
|
|
|
- randid = Math.floor(Math.random() * library.length);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (lastIndex == -1) randid = 3;
|
|
|
|
|
-
|
|
|
|
|
- lastIndex = randid;
|
|
|
|
|
- const ys = library[randid];
|
|
|
|
|
- photoImg.src = ys.cover;
|
|
|
|
|
- photoImg.alt = ys.name;
|
|
|
|
|
- nameEl.textContent = ys.name;
|
|
|
|
|
- ambEl.textContent = ys.album;
|
|
|
|
|
- writerEl.textContent = ys.writer;
|
|
|
|
|
- recommenderEl.textContent = ys.user;
|
|
|
|
|
- currentMusicLink = ys.click;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- randmusic();
|
|
|
|
|
-
|
|
|
|
|
- randomBtn.addEventListener('click', function() {
|
|
|
|
|
- randmusic();
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- })();
|
|
|
|
|
- </script>
|
|
|
|
|
-</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-</div>
|
|
|
|
|
-<?php
|
|
|
|
|
-// ---- 日期 ----
|
|
|
|
|
-$todayTs = time();
|
|
|
|
|
-$dayNum = (int)date('j', $todayTs);
|
|
|
|
|
-$monthNum = (int)date('n', $todayTs);
|
|
|
|
|
-$wday = (int)date('w', $todayTs);
|
|
|
|
|
-$monthCN = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
|
|
|
|
|
-$weekCN = ['日','一','二','三','四','五','六'];
|
|
|
|
|
-$monthText = $monthCN[$monthNum - 1] . '月';
|
|
|
|
|
-$weekText = '星期' . $weekCN[$wday];
|
|
|
|
|
-
|
|
|
|
|
-// ---- NOIP 倒计时 ----
|
|
|
|
|
-$noipTs = strtotime('2026-11-28');
|
|
|
|
|
-$todayMidnight = strtotime(date('Y-m-d', $todayTs));
|
|
|
|
|
-$daysLeft = (int)floor(($noipTs - $todayMidnight) / 86400);
|
|
|
|
|
-?>
|
|
|
|
|
-
|
|
|
|
|
-<?php
|
|
|
|
|
-$todayTs = time();
|
|
|
|
|
-$dayNum = (int)date('j', $todayTs);
|
|
|
|
|
-$monthNum = (int)date('n', $todayTs);
|
|
|
|
|
-$wday = (int)date('w', $todayTs);
|
|
|
|
|
-$monthCN = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
|
|
|
|
|
-$weekCN = ['日','一','二','三','四','五','六'];
|
|
|
|
|
-
|
|
|
|
|
-$bigMonths = [1,3,5,7,8,10,12];
|
|
|
|
|
-if ($monthNum == 2) {
|
|
|
|
|
- $sizeChar = '平'; // 闰年可改成 '闰'
|
|
|
|
|
-} elseif (in_array($monthNum, $bigMonths)) {
|
|
|
|
|
- $sizeChar = '大';
|
|
|
|
|
-} else {
|
|
|
|
|
- $sizeChar = '小';
|
|
|
|
|
-}
|
|
|
|
|
-$monthText = $monthCN[$monthNum - 1] . '月' . $sizeChar;
|
|
|
|
|
-$weekText = '星期' . $weekCN[$wday];
|
|
|
|
|
-
|
|
|
|
|
-$todayMidnight = strtotime(date('Y-m-d', $todayTs));
|
|
|
|
|
-$noipTs = strtotime('2026-11-28');
|
|
|
|
|
-$daysLeft = max(0, (int)floor(($noipTs - $todayMidnight) / 86400));
|
|
|
|
|
-$csj1Ts = strtotime('2026-09-19');
|
|
|
|
|
-$csp1daysLeft = max(0, (int)floor(($csp1Ts - $todayMidnight) / 86400));
|
|
|
|
|
-$csp2Ts = strtotime('2026-10-31');
|
|
|
|
|
-$csp2daysLeft = max(0, (int)floor(($csp2Ts - $todayMidnight) / 86400));
|
|
|
|
|
-$gk27Ts = strtotime('2027-06-07');
|
|
|
|
|
-$gk27daysLeft = max(0, (int)floor(($gk27Ts - $todayMidnight) / 86400));
|
|
|
|
|
-$gk28Ts = strtotime('2028-06-07');
|
|
|
|
|
-$gk28daysLeft = max(0, (int)floor(($gk28Ts - $todayMidnight) / 86400));
|
|
|
|
|
-?>
|
|
|
|
|
-
|
|
|
|
|
-<div class="row my-4">
|
|
|
|
|
- <!-- ============ 左列:日程 + 博客 ============ -->
|
|
|
|
|
- <div class="col-12 col-lg-9">
|
|
|
|
|
- <!-- 黑色日期卡片 -->
|
|
|
|
|
- <div class="mb-4 wyoj-date-card" id="wyoj-date-card"
|
|
|
|
|
- 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);">
|
|
|
|
|
-
|
|
|
|
|
- <!-- 随机方格背景:canvas -->
|
|
|
|
|
- <canvas id="wyoj-date-canvas"
|
|
|
|
|
- style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none;"></canvas>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 内容层 -->
|
|
|
|
|
- <div style="position:relative;z-index:1;">
|
|
|
|
|
- <div class="d-flex justify-content-center align-items-center">
|
|
|
|
|
- <div style="writing-mode:vertical-rl;font-size:1.5rem;font-weight:bold;letter-spacing:0.3em;line-height:1;">
|
|
|
|
|
- <?= $monthText ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="mx-4" style="font-size:5rem;font-weight:600;line-height:1;">
|
|
|
|
|
- <?= $dayNum ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div style="writing-mode:vertical-rl;font-size:1.5rem;font-weight:bold;letter-spacing:0.3em;line-height:1;">
|
|
|
|
|
- <?= $weekText ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="text-center mt-4" style="font-size:1rem;letter-spacing:0.05em;">
|
|
|
|
|
- <?php if ($daysLeft > 0): ?>
|
|
|
|
|
- 距 <strong>NOIP2026</strong> 还有 <strong><?= $daysLeft ?></strong> 天
|
|
|
|
|
- <?php elseif ($daysLeft === 0): ?>
|
|
|
|
|
- <strong>NOIP2026</strong> 就是今天
|
|
|
|
|
- <?php else: ?>
|
|
|
|
|
- <strong>NOIP2026</strong> 已结束
|
|
|
|
|
- <?php endif; ?>
|
|
|
|
|
- <br/>
|
|
|
|
|
- <?php if ($csp1daysLeft > 0): ?>
|
|
|
|
|
- 距 <strong>CSP-J/S 2026 第一轮</strong> 还有 <strong><?= $csp1daysLeft ?></strong> 天
|
|
|
|
|
- <?php elseif ($csp1daysLeft === 0): ?>
|
|
|
|
|
- <strong>CSP-J/S 2026 第一轮</strong> 就是今天
|
|
|
|
|
- <?php else: ?>
|
|
|
|
|
- <strong>CSP-J/S 2026 第一轮</strong> 已结束
|
|
|
|
|
- <?php endif; ?>
|
|
|
|
|
- <br/>
|
|
|
|
|
- <?php if ($csp2daysLeft > 0): ?>
|
|
|
|
|
- 距 <strong>CSP-J/S 2026 第二轮</strong> 还有 <strong><?= $csp2daysLeft ?></strong> 天
|
|
|
|
|
- <?php elseif ($csp2daysLeft === 0): ?>
|
|
|
|
|
- <strong>CSP-J/S 2026 第二轮</strong> 就是今天!
|
|
|
|
|
- <?php else: ?>
|
|
|
|
|
- <strong>CSP-J/S 2026 第二轮</strong> 已结束
|
|
|
|
|
- <?php endif; ?>
|
|
|
|
|
- <br/>
|
|
|
|
|
- <?php if ($gk27daysLeft > 0): ?>
|
|
|
|
|
- 距 <strong>2027 年普通高等学校招生全国统一考试</strong> 还有 <strong><?= $gk27daysLeft ?></strong> 天
|
|
|
|
|
- <?php elseif ($gk27daysLeft === 0): ?>
|
|
|
|
|
- <strong>2027 年普通高等学校招生全国统一考试</strong> 就是今天!
|
|
|
|
|
- <?php else: ?>
|
|
|
|
|
- <strong>2027 年普通高等学校招生全国统一考试</strong> 已结束
|
|
|
|
|
- <?php endif; ?>
|
|
|
|
|
- <br/>
|
|
|
|
|
- <?php if ($gk28daysLeft > 0): ?>
|
|
|
|
|
- 距 <strong>2028 年普通高等学校招生全国统一考试</strong> 还有 <strong><?= $gk28daysLeft ?></strong> 天
|
|
|
|
|
- <?php elseif ($gk28daysLeft === 0): ?>
|
|
|
|
|
- <strong>2028 年普通高等学校招生全国统一考试</strong> 就是今天!
|
|
|
|
|
- <?php else: ?>
|
|
|
|
|
- <strong>2028 年普通高等学校招生全国统一考试</strong> 已结束
|
|
|
|
|
- <?php endif; ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <script>
|
|
|
|
|
- (function () {
|
|
|
|
|
- var card = document.getElementById('wyoj-date-card');
|
|
|
|
|
- var canvas = document.getElementById('wyoj-date-canvas');
|
|
|
|
|
- if (!card || !canvas) return;
|
|
|
|
|
-
|
|
|
|
|
- var ctx = canvas.getContext('2d');
|
|
|
|
|
-
|
|
|
|
|
- // ===== 可调参数 =====
|
|
|
|
|
- var COLORS = ['#000000', '#0c0c0c', '#181818', '#242424', '#303030'];
|
|
|
|
|
- var CELL = 26; // 每格边长(px)
|
|
|
|
|
- var P = 0.3; // 每格每轮变色概率(期望值)
|
|
|
|
|
- var PERIOD = 1500; // 刷新间隔(ms)
|
|
|
|
|
- var Z = 1.96; // 95% 置信区间
|
|
|
|
|
- var USE_DPR = false; // 是否按设备像素比放大 canvas(false 更省 CPU)
|
|
|
|
|
- // ====================
|
|
|
|
|
-
|
|
|
|
|
- var dpr = USE_DPR ? (window.devicePixelRatio || 1) : 1;
|
|
|
|
|
- var cols = 0, rows = 0, nCells = 0;
|
|
|
|
|
- var lastW = 0, lastH = 0;
|
|
|
|
|
- var gridColors = [];
|
|
|
|
|
- var idx = null;
|
|
|
|
|
- var xs = null, ys = null;
|
|
|
|
|
-
|
|
|
|
|
- var rafId = null;
|
|
|
|
|
- var running = false;
|
|
|
|
|
- var lastTick = 0;
|
|
|
|
|
-
|
|
|
|
|
- function randColor() {
|
|
|
|
|
- return COLORS[(Math.random() * COLORS.length) | 0];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function computeBounds() {
|
|
|
|
|
- var w = card.clientWidth;
|
|
|
|
|
- var h = card.clientHeight;
|
|
|
|
|
- xs = new Int32Array(cols + 1);
|
|
|
|
|
- ys = new Int32Array(rows + 1);
|
|
|
|
|
- for (var i = 0; i <= cols; i++) xs[i] = Math.round(i * w / cols);
|
|
|
|
|
- for (var j = 0; j <= rows; j++) ys[j] = Math.round(j * h / rows);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function build() {
|
|
|
|
|
- var w = card.clientWidth;
|
|
|
|
|
- var h = card.clientHeight;
|
|
|
|
|
- if (w === 0 || h === 0) return;
|
|
|
|
|
- if (w === lastW && h === lastH) return; // 尺寸没变就不重建
|
|
|
|
|
- lastW = w; lastH = h;
|
|
|
|
|
-
|
|
|
|
|
- cols = Math.max(1, Math.ceil(w / CELL));
|
|
|
|
|
- rows = Math.max(1, Math.ceil(h / CELL));
|
|
|
|
|
- nCells = cols * rows;
|
|
|
|
|
-
|
|
|
|
|
- canvas.width = Math.round(w * dpr);
|
|
|
|
|
- canvas.height = Math.round(h * dpr);
|
|
|
|
|
- canvas.style.width = w + 'px';
|
|
|
|
|
- canvas.style.height = h + 'px';
|
|
|
|
|
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
|
- ctx.imageSmoothingEnabled = false;
|
|
|
|
|
-
|
|
|
|
|
- computeBounds();
|
|
|
|
|
-
|
|
|
|
|
- gridColors = [];
|
|
|
|
|
- for (var y = 0; y < rows; y++) {
|
|
|
|
|
- var row = [];
|
|
|
|
|
- for (var x = 0; x < cols; x++) row.push(randColor());
|
|
|
|
|
- gridColors.push(row);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- idx = new Int32Array(nCells);
|
|
|
|
|
- for (var i = 0; i < nCells; i++) idx[i] = i;
|
|
|
|
|
-
|
|
|
|
|
- redrawAll();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function redrawAll() {
|
|
|
|
|
- for (var y = 0; y < rows; y++) {
|
|
|
|
|
- var y0 = ys[y], y1 = ys[y + 1];
|
|
|
|
|
- for (var x = 0; x < cols; x++) {
|
|
|
|
|
- ctx.fillStyle = gridColors[y][x];
|
|
|
|
|
- ctx.fillRect(xs[x], y0, xs[x + 1] - xs[x], y1 - y0);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function tick() {
|
|
|
|
|
- if (nCells === 0) return;
|
|
|
|
|
-
|
|
|
|
|
- var mu = nCells * P;
|
|
|
|
|
- var sigma = Math.sqrt(nCells * P * (1 - P));
|
|
|
|
|
- var lo = Math.max(0, Math.floor(mu - Z * sigma));
|
|
|
|
|
- var hi = Math.min(nCells, Math.ceil(mu + Z * sigma));
|
|
|
|
|
- var m = lo + Math.floor(Math.random() * (hi - lo + 1));
|
|
|
|
|
-
|
|
|
|
|
- for (var i = 0; i < m; i++) {
|
|
|
|
|
- var j = i + Math.floor(Math.random() * (nCells - i));
|
|
|
|
|
- var t = idx[i]; idx[i] = idx[j]; idx[j] = t;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- for (var k = 0; k < m; k++) {
|
|
|
|
|
- var ci = idx[k];
|
|
|
|
|
- var x = ci % cols;
|
|
|
|
|
- var y = (ci / cols) | 0;
|
|
|
|
|
- var c = randColor();
|
|
|
|
|
- gridColors[y][x] = c;
|
|
|
|
|
- ctx.fillStyle = c;
|
|
|
|
|
- ctx.fillRect(xs[x], ys[y], xs[x + 1] - xs[x], ys[y + 1] - ys[y]);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function loop(now) {
|
|
|
|
|
- rafId = null;
|
|
|
|
|
- if (now - lastTick >= PERIOD) {
|
|
|
|
|
- lastTick = now;
|
|
|
|
|
- tick();
|
|
|
|
|
- }
|
|
|
|
|
- if (running) rafId = requestAnimationFrame(loop);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function start() {
|
|
|
|
|
- if (running) return;
|
|
|
|
|
- running = true;
|
|
|
|
|
- lastTick = performance.now();
|
|
|
|
|
- rafId = requestAnimationFrame(loop);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function stop() {
|
|
|
|
|
- running = false;
|
|
|
|
|
- if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- build();
|
|
|
|
|
-
|
|
|
|
|
- if (window.ResizeObserver) {
|
|
|
|
|
- new ResizeObserver(function () { build(); }).observe(card);
|
|
|
|
|
- } else {
|
|
|
|
|
- window.addEventListener('resize', build);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (window.IntersectionObserver) {
|
|
|
|
|
- new IntersectionObserver(function (entries) {
|
|
|
|
|
- if (entries[0].isIntersecting) start(); else stop();
|
|
|
|
|
- }, { threshold: 0 }).observe(card);
|
|
|
|
|
- } else {
|
|
|
|
|
- start();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- document.addEventListener('visibilitychange', function () {
|
|
|
|
|
- if (document.hidden) stop(); else start();
|
|
|
|
|
- });
|
|
|
|
|
- })();
|
|
|
|
|
- </script>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 博客列表 -->
|
|
|
|
|
- <div class="row">
|
|
|
|
|
- <?php foreach ($recentblogs as $blog): ?>
|
|
|
|
|
- <?= echoBlogCard(queryBlog($blog['id'])) ?>
|
|
|
|
|
- <?php endforeach ?>
|
|
|
|
|
- <?php foreach ($topblogs as $blog): ?>
|
|
|
|
|
- <?= echoBlogCard(queryBlog($blog['id'])) ?>
|
|
|
|
|
- <?php endforeach ?>
|
|
|
|
|
-
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- ============ 右列:排行榜 ============ -->
|
|
|
|
|
- <div class="col-12 col-lg-3">
|
|
|
|
|
-
|
|
|
|
|
- <!-- 活跃 -->
|
|
|
|
|
- <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
- <div class="card-body p-3">
|
|
|
|
|
- <h6 class="font-weight-bold mb-3">活跃排行榜</h6>
|
|
|
|
|
- <?php foreach ($activateusers as $username): ?>
|
|
|
|
|
- <?php $user = queryUser($username['submitter']); ?>
|
|
|
|
|
- <div class="d-flex align-items-start border-bottom pb-2 mb-2">
|
|
|
|
|
- <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
|
|
|
|
|
- style="width:36px;height:36px;object-fit:cover;"
|
|
|
|
|
- src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
|
|
|
|
|
- <div class="flex-grow-1" style="min-width:0;">
|
|
|
|
|
- <div class="text-truncate font-weight-bold" style="font-size:0.9rem;">
|
|
|
|
|
- <?= getUserLink($user['username']) ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
|
|
|
|
|
- <?= $user['motto'] ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <?php endforeach ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 比赛 -->
|
|
|
|
|
- <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
- <div class="card-body p-3">
|
|
|
|
|
- <h6 class="font-weight-bold mb-1">比赛排行榜</h6>
|
|
|
|
|
- <div class="mb-3" style="font-size:0.75rem;">
|
|
|
|
|
- <span class="text-muted">180 天内三场及以上</span>
|
|
|
|
|
- <a href="/ranklist" class="ml-1">全部排名</a>
|
|
|
|
|
- </div>
|
|
|
|
|
- <?php foreach ($topusers as $user): ?>
|
|
|
|
|
- <div class="d-flex align-items-start border-bottom pb-2 mb-2">
|
|
|
|
|
- <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
|
|
|
|
|
- style="width:36px;height:36px;object-fit:cover;"
|
|
|
|
|
- src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
|
|
|
|
|
- <div class="flex-grow-1" style="min-width:0;">
|
|
|
|
|
- <div class="d-flex justify-content-between align-items-baseline">
|
|
|
|
|
- <span class="text-truncate font-weight-bold" style="font-size:0.9rem;">
|
|
|
|
|
- <?= getUserLink($user['username']) ?>
|
|
|
|
|
- </span>
|
|
|
|
|
- <strong class="text-danger ml-1 flex-shrink-0" style="font-size:0.75rem;">
|
|
|
|
|
- <?= $user['rating'] ?> <small>(±<?= round($user['rating_rd']) * 2 ?>)</small>
|
|
|
|
|
- </strong>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
|
|
|
|
|
- <?= $user['motto'] ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <?php endforeach ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 黑马 -->
|
|
|
|
|
- <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
- <div class="card-body p-3">
|
|
|
|
|
- <h6 class="font-weight-bold mb-3">黑马 / 爆冷榜</h6>
|
|
|
|
|
- <?php foreach ($hotusers as $user): ?>
|
|
|
|
|
- <div class="d-flex align-items-start border-bottom pb-2 mb-2">
|
|
|
|
|
- <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
|
|
|
|
|
- style="width:36px;height:36px;object-fit:cover;"
|
|
|
|
|
- src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
|
|
|
|
|
- <div class="flex-grow-1" style="min-width:0;">
|
|
|
|
|
- <div class="d-flex justify-content-between align-items-baseline">
|
|
|
|
|
- <span class="text-truncate font-weight-bold" style="font-size:0.9rem;">
|
|
|
|
|
- <?= getUserLink($user['username']) ?>
|
|
|
|
|
- </span>
|
|
|
|
|
- <strong class="text-danger ml-1 flex-shrink-0" style="font-size:0.75rem;">
|
|
|
|
|
- Vol <?= round(1000 * $user['rating_vol']) / 1000 ?>
|
|
|
|
|
- </strong>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
|
|
|
|
|
- <?= $user['motto'] ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <?php endforeach ?>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- </div>
|
|
|
|
|
-</div>
|
|
|
|
|
-
|
|
|
|
|
-<!-- 博客卡片悬停效果 -->
|
|
|
|
|
-<style>
|
|
|
|
|
-.wyoj-blog-card {
|
|
|
|
|
- transition: box-shadow .2s ease;
|
|
|
|
|
-}
|
|
|
|
|
-.wyoj-blog-card:hover {
|
|
|
|
|
- box-shadow: 0 4px 14px rgba(0,0,0,.10) !important;
|
|
|
|
|
-}
|
|
|
|
|
-.wyoj-blog-card .wyoj-blog-title {
|
|
|
|
|
- font-size: 1.05rem;
|
|
|
|
|
- line-height: 1.5;
|
|
|
|
|
-}
|
|
|
|
|
-</style>
|
|
|
|
|
-
|
|
|
|
|
-<?php echoUOJPageFooter() ?>
|
|
|
|
|
-
|
|
|
|
|
-<div class="row my-4">
|
|
|
|
|
-
|
|
|
|
|
|
|
+<?php
|
|
|
|
|
+$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");
|
|
|
|
|
+$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;");
|
|
|
|
|
+$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;");
|
|
|
|
|
+$hotusers = DB::selectAll("select username, rating, rating_rd, rating_vol, qq, motto from user_info order by rating_vol desc, username asc limit 12;");
|
|
|
|
|
+$topblogs = DB::selectAll("select id from blogs where is_hidden = false and zan >= 10 order by zan desc limit 10;");
|
|
|
|
|
+$recentblogs = DB::selectAll("select id from blogs where is_hidden = false and zan < 10 order by post_time desc limit 10;");
|
|
|
|
|
+$songs_data = htmlspecialchars(json_encode(DB::selectAll("select * from songs;")), ENT_QUOTES, 'UTF-8');
|
|
|
|
|
+
|
|
|
|
|
+// 最近比赛:未结束的按开始时间升序取,不足 5 场时补最近结束的
|
|
|
|
|
+$recent_contests = array();
|
|
|
|
|
+$result = DB::selectAll("select * from contests where status != 'finished' order by start_time asc, id asc limit 5");
|
|
|
|
|
+foreach ($result as $contest) {
|
|
|
|
|
+ genMoreContestInfo($contest);
|
|
|
|
|
+ $recent_contests[] = $contest;
|
|
|
|
|
+}
|
|
|
|
|
+if (count($recent_contests) < 5) {
|
|
|
|
|
+ $need = 5 - count($recent_contests);
|
|
|
|
|
+ $result = DB::selectAll("select * from contests where status = 'finished' order by start_time desc, id desc limit $need");
|
|
|
|
|
+ foreach ($result as $contest) {
|
|
|
|
|
+ genMoreContestInfo($contest);
|
|
|
|
|
+ $recent_contests[] = $contest;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 随机用户背景图展示用
|
|
|
|
|
+$rand_users = [];
|
|
|
|
|
+$rand_users_raw = DB::selectAll("select username, bgurl, motto from user_info where bgurl is not null and bgurl != ''");
|
|
|
|
|
+
|
|
|
|
|
+foreach ($rand_users_raw as $u) {
|
|
|
|
|
+ if (!preg_match('#^https?://#i', $u['bgurl'])) continue;
|
|
|
|
|
+ $u_obj = queryUser($u['username']);
|
|
|
|
|
+ if (!$u_obj) continue;
|
|
|
|
|
+ $rand_users[] = [
|
|
|
|
|
+ 'username' => $u['username'],
|
|
|
|
|
+ 'href' => '/user/profile/' . urlencode($u['username']),
|
|
|
|
|
+ 'bgurl' => $u['bgurl'],
|
|
|
|
|
+ 'motto' => $u['motto'],
|
|
|
|
|
+ 'userlink' => getUserLink($u['username']),
|
|
|
|
|
+ 'avatar' => HTML::avatar_addr($u_obj, 128),
|
|
|
|
|
+ ];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+$rand_users_json = htmlspecialchars(json_encode($rand_users), ENT_QUOTES, 'UTF-8');
|
|
|
|
|
+?>
|
|
|
|
|
+<?php echoUOJPageHeader(UOJConfig::$data['profile']['oj-name-short']) ?>
|
|
|
|
|
+<div id="songs-data" data-songs='<?php echo $songs_data; ?>'></div>
|
|
|
|
|
+
|
|
|
|
|
+<div class="row my-4 wyoj-topcard">
|
|
|
|
|
+ <div class="card-body">
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <div class="col-sm-12 col-md-9">
|
|
|
|
|
+ <table class="table table-sm">
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <?php $now_cnt = 0; ?>
|
|
|
|
|
+ <?php foreach ($blogs as $blog): ?>
|
|
|
|
|
+ <?php
|
|
|
|
|
+ $now_cnt++;
|
|
|
|
|
+ $new_tag = '';
|
|
|
|
|
+ if ((time() - strtotime($blog['post_time'])) / 3600 / 24 <= 7) {
|
|
|
|
|
+ $new_tag = '<sup style="color:red"> new</sup>';
|
|
|
|
|
+ }
|
|
|
|
|
+ ?>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td><a href="/blogs/<?= $blog['id'] ?>"><?= $blog['title'] ?></a><?= $new_tag ?></td>
|
|
|
|
|
+ <td>by <?= getUserLink($blog['poster']) ?></td>
|
|
|
|
|
+ <td><small><?= $blog['post_time'] ?></small></td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <?php endforeach ?>
|
|
|
|
|
+ <?php for ($i = $now_cnt + 1; $i <= 8; $i++): ?>
|
|
|
|
|
+ <tr><td colspan="233"> </td></tr>
|
|
|
|
|
+ <?php endfor ?>
|
|
|
|
|
+ <tr><td class="text-right" colspan="233">
|
|
|
|
|
+ <span>本歌曲由 </span><span id="recommender"></span><span> 推荐</span>
|
|
|
|
|
+ </td></tr>
|
|
|
|
|
+ <tr><td class="text-right" colspan="233">
|
|
|
|
|
+ <a href="https://git.ryp.org.cn/wyoj/" class="text-info font-weight-bold">WyOJ 以及所有相关项目均为自由软件。</a>
|
|
|
|
|
+ <a href="/announcements"><?= UOJLocale::get('all the announcements') ?></a>
|
|
|
|
|
+ </td></tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td class="text-right" colspan="233">
|
|
|
|
|
+ <span class="text-danger font-weight-bold">声明:本网站属个人非经营性网站,仅用于学习交流,无任何盈利目的,与任何组织无关</span>
|
|
|
|
|
+ <a href="/certs-manage/songs">我要推歌</a>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="col-xs-6 col-sm-4 col-md-3">
|
|
|
|
|
+ <div class="card" style="max-width:300px; width:100%; border-radius:0; ">
|
|
|
|
|
+ <div class="image-frame" style="aspect-ratio:1/1; position:relative;">
|
|
|
|
|
+ <img id="photo" src="" alt="" style="width:100%; height:100%; object-fit:cover; display:block; cursor:pointer; -webkit-tap-highlight-color:transparent;">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="intro" style="padding:1.6rem 1.8rem 1.2rem; text-align:center; background:#ffffff;">
|
|
|
|
|
+ <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>
|
|
|
|
|
+ <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>
|
|
|
|
|
+ <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>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="button-wrapper" style="padding:0.2rem 1.8rem 2rem 1.8rem; background:#ffffff;">
|
|
|
|
|
+ <style>
|
|
|
|
|
+ #randomBtn{
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 0.6rem;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: #1f2f3b;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ padding: 0.7rem 0;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.2s ease;
|
|
|
|
|
+ letter-spacing: 0.5px;
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
|
+ backdrop-filter: blur(4px);
|
|
|
|
|
+ font-family: inherit;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+ <button id="randomBtn">换一首</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <script>
|
|
|
|
|
+ (function() {
|
|
|
|
|
+ let dataDiv = document.getElementById('songs-data');
|
|
|
|
|
+ let library = JSON.parse(dataDiv.dataset.songs);
|
|
|
|
|
+
|
|
|
|
|
+ const photoImg = document.getElementById('photo');
|
|
|
|
|
+ const nameEl = document.getElementById('name');
|
|
|
|
|
+ const ambEl = document.getElementById('amb');
|
|
|
|
|
+ const writerEl = document.getElementById('writer');
|
|
|
|
|
+ const randomBtn = document.getElementById('randomBtn');
|
|
|
|
|
+ const recommenderEl = document.getElementById('recommender');
|
|
|
|
|
+
|
|
|
|
|
+ let lastIndex = -1;
|
|
|
|
|
+ let currentMusicLink = library[0].musiclink;
|
|
|
|
|
+
|
|
|
|
|
+ photoImg.addEventListener('mousedown', function(e) {
|
|
|
|
|
+ this.style.opacity = '0.8';
|
|
|
|
|
+ });
|
|
|
|
|
+ photoImg.addEventListener('mouseup', function(e) {
|
|
|
|
|
+ this.style.opacity = '';
|
|
|
|
|
+ });
|
|
|
|
|
+ photoImg.addEventListener('mouseleave', function(e) {
|
|
|
|
|
+ this.style.opacity = '';
|
|
|
|
|
+ });
|
|
|
|
|
+ photoImg.addEventListener('touchstart', function(e) {
|
|
|
|
|
+ this.style.opacity = '0.8';
|
|
|
|
|
+ });
|
|
|
|
|
+ photoImg.addEventListener('touchend', function(e) {
|
|
|
|
|
+ this.style.opacity = '';
|
|
|
|
|
+ });
|
|
|
|
|
+ photoImg.addEventListener('touchcancel', function(e) {
|
|
|
|
|
+ this.style.opacity = '';
|
|
|
|
|
+ });
|
|
|
|
|
+ function setBtnActive() {
|
|
|
|
|
+ randomBtn.style.background = '#17242e';
|
|
|
|
|
+ randomBtn.style.transform = 'scale(0.98)';
|
|
|
|
|
+ randomBtn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
|
|
|
|
|
+ }
|
|
|
|
|
+ function setBtnNormal() {
|
|
|
|
|
+ randomBtn.style.background = '#1f2f3b';
|
|
|
|
|
+ randomBtn.style.transform = 'scale(1)';
|
|
|
|
|
+ randomBtn.style.boxShadow = 'none';
|
|
|
|
|
+ }
|
|
|
|
|
+ randomBtn.addEventListener('mousedown', setBtnActive);
|
|
|
|
|
+ randomBtn.addEventListener('mouseup', setBtnNormal);
|
|
|
|
|
+ randomBtn.addEventListener('mouseleave', setBtnNormal);
|
|
|
|
|
+ randomBtn.addEventListener('touchstart', setBtnActive);
|
|
|
|
|
+ randomBtn.addEventListener('touchend', setBtnNormal);
|
|
|
|
|
+ randomBtn.addEventListener('touchcancel', setBtnNormal);
|
|
|
|
|
+
|
|
|
|
|
+ photoImg.addEventListener('click', function() {
|
|
|
|
|
+ if (currentMusicLink) {
|
|
|
|
|
+ window.open(currentMusicLink, '_blank');
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ function randmusic() {
|
|
|
|
|
+ let randid = lastIndex;
|
|
|
|
|
+ while (randid === lastIndex) {
|
|
|
|
|
+ randid = Math.floor(Math.random() * library.length);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (lastIndex == -1) randid = 3;
|
|
|
|
|
+
|
|
|
|
|
+ lastIndex = randid;
|
|
|
|
|
+ const ys = library[randid];
|
|
|
|
|
+ photoImg.src = ys.cover;
|
|
|
|
|
+ photoImg.alt = ys.name;
|
|
|
|
|
+ nameEl.textContent = ys.name;
|
|
|
|
|
+ ambEl.textContent = ys.album;
|
|
|
|
|
+ writerEl.textContent = ys.writer;
|
|
|
|
|
+ recommenderEl.textContent = ys.user;
|
|
|
|
|
+ currentMusicLink = ys.click;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ randmusic();
|
|
|
|
|
+
|
|
|
|
|
+ randomBtn.addEventListener('click', function() {
|
|
|
|
|
+ randmusic();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ })();
|
|
|
|
|
+ </script>
|
|
|
|
|
+</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<?php
|
|
|
|
|
+// ---- 日期 ----
|
|
|
|
|
+$todayTs = time();
|
|
|
|
|
+$dayNum = (int)date('j', $todayTs);
|
|
|
|
|
+$monthNum = (int)date('n', $todayTs);
|
|
|
|
|
+$wday = (int)date('w', $todayTs);
|
|
|
|
|
+$monthCN = ['一','二','三','四','五','六','七','八','九','十','十一','十二'];
|
|
|
|
|
+$weekCN = ['日','一','二','三','四','五','六'];
|
|
|
|
|
+
|
|
|
|
|
+$bigMonths = [1,3,5,7,8,10,12];
|
|
|
|
|
+if ($monthNum == 2) {
|
|
|
|
|
+ $sizeChar = '平'; // 闰年可改成 '闰'
|
|
|
|
|
+} elseif (in_array($monthNum, $bigMonths)) {
|
|
|
|
|
+ $sizeChar = '大';
|
|
|
|
|
+} else {
|
|
|
|
|
+ $sizeChar = '小';
|
|
|
|
|
+}
|
|
|
|
|
+$monthText = $monthCN[$monthNum - 1] . '月' . $sizeChar;
|
|
|
|
|
+$weekText = '星期' . $weekCN[$wday];
|
|
|
|
|
+
|
|
|
|
|
+$todayMidnight = strtotime(date('Y-m-d', $todayTs));
|
|
|
|
|
+$noipTs = strtotime('2026-11-28');
|
|
|
|
|
+$daysLeft = max(0, (int)floor(($noipTs - $todayMidnight) / 86400));
|
|
|
|
|
+$csp1Ts = strtotime('2026-09-19');
|
|
|
|
|
+$csp1daysLeft = max(0, (int)floor(($csp1Ts - $todayMidnight) / 86400));
|
|
|
|
|
+$csp2Ts = strtotime('2026-10-31');
|
|
|
|
|
+$csp2daysLeft = max(0, (int)floor(($csp2Ts - $todayMidnight) / 86400));
|
|
|
|
|
+$gk27Ts = strtotime('2027-06-07');
|
|
|
|
|
+$gk27daysLeft = max(0, (int)floor(($gk27Ts - $todayMidnight) / 86400));
|
|
|
|
|
+$gk28Ts = strtotime('2028-06-07');
|
|
|
|
|
+$gk28daysLeft = max(0, (int)floor(($gk28Ts - $todayMidnight) / 86400));
|
|
|
|
|
+?>
|
|
|
|
|
+
|
|
|
|
|
+<div class="row my-4">
|
|
|
|
|
+ <!-- ============ 左列:日程 + 博客 ============ -->
|
|
|
|
|
+ <div class="col-12 col-lg-9">
|
|
|
|
|
+ <!-- 黑色日期卡片 -->
|
|
|
|
|
+ <div class="mb-4 wyoj-date-card" id="wyoj-date-card"
|
|
|
|
|
+ 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);">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 随机方格背景:canvas -->
|
|
|
|
|
+ <canvas id="wyoj-date-canvas"
|
|
|
|
|
+ style="position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none;"></canvas>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 内容层 -->
|
|
|
|
|
+ <div style="position:relative;z-index:1;">
|
|
|
|
|
+ <div class="d-flex justify-content-center align-items-center">
|
|
|
|
|
+ <div style="writing-mode:vertical-rl;font-size:1.5rem;font-weight:bold;letter-spacing:0.3em;line-height:1;">
|
|
|
|
|
+ <?= $monthText ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="mx-4" style="font-size:5rem;font-weight:600;line-height:1;">
|
|
|
|
|
+ <?= $dayNum ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="writing-mode:vertical-rl;font-size:1.5rem;font-weight:bold;letter-spacing:0.3em;line-height:1;">
|
|
|
|
|
+ <?= $weekText ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-center mt-4" style="font-size:1rem;letter-spacing:0.05em;">
|
|
|
|
|
+ <?php if ($daysLeft > 0): ?>
|
|
|
|
|
+ 距 <strong>NOIP2026</strong> 还有 <strong><?= $daysLeft ?></strong> 天
|
|
|
|
|
+ <?php elseif ($daysLeft === 0): ?>
|
|
|
|
|
+ <strong>NOIP2026</strong> 就是今天
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <strong>NOIP2026</strong> 已结束
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ <br/>
|
|
|
|
|
+ <?php if ($csp1daysLeft > 0): ?>
|
|
|
|
|
+ 距 <strong>CSP-J/S 2026 第一轮</strong> 还有 <strong><?= $csp1daysLeft ?></strong> 天
|
|
|
|
|
+ <?php elseif ($csp1daysLeft === 0): ?>
|
|
|
|
|
+ <strong>CSP-J/S 2026 第一轮</strong> 就是今天
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <strong>CSP-J/S 2026 第一轮</strong> 已结束
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ <br/>
|
|
|
|
|
+ <?php if ($csp2daysLeft > 0): ?>
|
|
|
|
|
+ 距 <strong>CSP-J/S 2026 第二轮</strong> 还有 <strong><?= $csp2daysLeft ?></strong> 天
|
|
|
|
|
+ <?php elseif ($csp2daysLeft === 0): ?>
|
|
|
|
|
+ <strong>CSP-J/S 2026 第二轮</strong> 就是今天!
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <strong>CSP-J/S 2026 第二轮</strong> 已结束
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ <br/>
|
|
|
|
|
+ <?php if ($gk27daysLeft > 0): ?>
|
|
|
|
|
+ 距 <strong>2027 年普通高等学校招生全国统一考试</strong> 还有 <strong><?= $gk27daysLeft ?></strong> 天
|
|
|
|
|
+ <?php elseif ($gk27daysLeft === 0): ?>
|
|
|
|
|
+ <strong>2027 年普通高等学校招生全国统一考试</strong> 就是今天!
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <strong>2027 年普通高等学校招生全国统一考试</strong> 已结束
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ <br/>
|
|
|
|
|
+ <?php if ($gk28daysLeft > 0): ?>
|
|
|
|
|
+ 距 <strong>2028 年普通高等学校招生全国统一考试</strong> 还有 <strong><?= $gk28daysLeft ?></strong> 天
|
|
|
|
|
+ <?php elseif ($gk28daysLeft === 0): ?>
|
|
|
|
|
+ <strong>2028 年普通高等学校招生全国统一考试</strong> 就是今天!
|
|
|
|
|
+ <?php else: ?>
|
|
|
|
|
+ <strong>2028 年普通高等学校招生全国统一考试</strong> 已结束
|
|
|
|
|
+ <?php endif; ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <script>
|
|
|
|
|
+ (function () {
|
|
|
|
|
+ var card = document.getElementById('wyoj-date-card');
|
|
|
|
|
+ var canvas = document.getElementById('wyoj-date-canvas');
|
|
|
|
|
+ if (!card || !canvas) return;
|
|
|
|
|
+
|
|
|
|
|
+ var ctx = canvas.getContext('2d');
|
|
|
|
|
+
|
|
|
|
|
+ // ===== 可调参数 =====
|
|
|
|
|
+ var COLORS = ['#000000', '#0c0c0c', '#181818', '#242424', '#303030'];
|
|
|
|
|
+ var CELL = 26; // 每格边长(px)
|
|
|
|
|
+ var P = 0.3; // 每格每轮变色概率(期望值)
|
|
|
|
|
+ var PERIOD = 3000; // 刷新间隔(ms)
|
|
|
|
|
+ var Z = 1.96; // 95% 置信区间
|
|
|
|
|
+ var USE_DPR = false; // 是否按设备像素比放大 canvas(false 更省 CPU)
|
|
|
|
|
+ // ====================
|
|
|
|
|
+
|
|
|
|
|
+ var dpr = USE_DPR ? (window.devicePixelRatio || 1) : 1;
|
|
|
|
|
+ var cols = 0, rows = 0, nCells = 0;
|
|
|
|
|
+ var lastW = 0, lastH = 0;
|
|
|
|
|
+ var gridColors = [];
|
|
|
|
|
+ var idx = null;
|
|
|
|
|
+ var xs = null, ys = null;
|
|
|
|
|
+
|
|
|
|
|
+ var rafId = null;
|
|
|
|
|
+ var running = false;
|
|
|
|
|
+ var lastTick = 0;
|
|
|
|
|
+
|
|
|
|
|
+ function randColor() {
|
|
|
|
|
+ return COLORS[(Math.random() * COLORS.length) | 0];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function computeBounds() {
|
|
|
|
|
+ var w = card.clientWidth;
|
|
|
|
|
+ var h = card.clientHeight;
|
|
|
|
|
+ xs = new Int32Array(cols + 1);
|
|
|
|
|
+ ys = new Int32Array(rows + 1);
|
|
|
|
|
+ for (var i = 0; i <= cols; i++) xs[i] = Math.round(i * w / cols);
|
|
|
|
|
+ for (var j = 0; j <= rows; j++) ys[j] = Math.round(j * h / rows);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function build() {
|
|
|
|
|
+ var w = card.clientWidth;
|
|
|
|
|
+ var h = card.clientHeight;
|
|
|
|
|
+ if (w === 0 || h === 0) return;
|
|
|
|
|
+ if (w === lastW && h === lastH) return; // 尺寸没变就不重建
|
|
|
|
|
+ lastW = w; lastH = h;
|
|
|
|
|
+
|
|
|
|
|
+ cols = Math.max(1, Math.ceil(w / CELL));
|
|
|
|
|
+ rows = Math.max(1, Math.ceil(h / CELL));
|
|
|
|
|
+ nCells = cols * rows;
|
|
|
|
|
+
|
|
|
|
|
+ canvas.width = Math.round(w * dpr);
|
|
|
|
|
+ canvas.height = Math.round(h * dpr);
|
|
|
|
|
+ canvas.style.width = w + 'px';
|
|
|
|
|
+ canvas.style.height = h + 'px';
|
|
|
|
|
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
|
+ ctx.imageSmoothingEnabled = false;
|
|
|
|
|
+
|
|
|
|
|
+ computeBounds();
|
|
|
|
|
+
|
|
|
|
|
+ gridColors = [];
|
|
|
|
|
+ for (var y = 0; y < rows; y++) {
|
|
|
|
|
+ var row = [];
|
|
|
|
|
+ for (var x = 0; x < cols; x++) row.push(randColor());
|
|
|
|
|
+ gridColors.push(row);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ idx = new Int32Array(nCells);
|
|
|
|
|
+ for (var i = 0; i < nCells; i++) idx[i] = i;
|
|
|
|
|
+
|
|
|
|
|
+ redrawAll();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function redrawAll() {
|
|
|
|
|
+ for (var y = 0; y < rows; y++) {
|
|
|
|
|
+ var y0 = ys[y], y1 = ys[y + 1];
|
|
|
|
|
+ for (var x = 0; x < cols; x++) {
|
|
|
|
|
+ ctx.fillStyle = gridColors[y][x];
|
|
|
|
|
+ ctx.fillRect(xs[x], y0, xs[x + 1] - xs[x], y1 - y0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function tick() {
|
|
|
|
|
+ if (nCells === 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ var mu = nCells * P;
|
|
|
|
|
+ var sigma = Math.sqrt(nCells * P * (1 - P));
|
|
|
|
|
+ var lo = Math.max(0, Math.floor(mu - Z * sigma));
|
|
|
|
|
+ var hi = Math.min(nCells, Math.ceil(mu + Z * sigma));
|
|
|
|
|
+ var m = lo + Math.floor(Math.random() * (hi - lo + 1));
|
|
|
|
|
+
|
|
|
|
|
+ for (var i = 0; i < m; i++) {
|
|
|
|
|
+ var j = i + Math.floor(Math.random() * (nCells - i));
|
|
|
|
|
+ var t = idx[i]; idx[i] = idx[j]; idx[j] = t;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (var k = 0; k < m; k++) {
|
|
|
|
|
+ var ci = idx[k];
|
|
|
|
|
+ var x = ci % cols;
|
|
|
|
|
+ var y = (ci / cols) | 0;
|
|
|
|
|
+ var c = randColor();
|
|
|
|
|
+ gridColors[y][x] = c;
|
|
|
|
|
+ ctx.fillStyle = c;
|
|
|
|
|
+ ctx.fillRect(xs[x], ys[y], xs[x + 1] - xs[x], ys[y + 1] - ys[y]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function loop(now) {
|
|
|
|
|
+ rafId = null;
|
|
|
|
|
+ if (now - lastTick >= PERIOD) {
|
|
|
|
|
+ lastTick = now;
|
|
|
|
|
+ tick();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (running) rafId = requestAnimationFrame(loop);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function start() {
|
|
|
|
|
+ if (running) return;
|
|
|
|
|
+ running = true;
|
|
|
|
|
+ lastTick = performance.now();
|
|
|
|
|
+ rafId = requestAnimationFrame(loop);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function stop() {
|
|
|
|
|
+ running = false;
|
|
|
|
|
+ if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ build();
|
|
|
|
|
+
|
|
|
|
|
+ if (window.ResizeObserver) {
|
|
|
|
|
+ new ResizeObserver(function () { build(); }).observe(card);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ window.addEventListener('resize', build);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (window.IntersectionObserver) {
|
|
|
|
|
+ new IntersectionObserver(function (entries) {
|
|
|
|
|
+ if (entries[0].isIntersecting) start(); else stop();
|
|
|
|
|
+ }, { threshold: 0 }).observe(card);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ start();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('visibilitychange', function () {
|
|
|
|
|
+ if (document.hidden) stop(); else start();
|
|
|
|
|
+ });
|
|
|
|
|
+ })();
|
|
|
|
|
+ </script>
|
|
|
|
|
+
|
|
|
|
|
+ <?php if (!empty($rand_users)): ?>
|
|
|
|
|
+ <div class="mb-4" id="wyoj-random-user-wrap">
|
|
|
|
|
+ <div class="wyoj-random-user" id="wyoj-random-user-link">
|
|
|
|
|
+ <div class="wyoj-random-img-wrap">
|
|
|
|
|
+ <img class="wyoj-rand-img" id="wyoj-rand-img-a" src="" alt="">
|
|
|
|
|
+ <img class="wyoj-rand-img" id="wyoj-rand-img-b" src="" alt="">
|
|
|
|
|
+ <button type="button" class="wyoj-rand-arrow wyoj-rand-arrow-left"
|
|
|
|
|
+ id="wyoj-rand-prev" aria-label="上一个">‹</button>
|
|
|
|
|
+ <button type="button" class="wyoj-rand-arrow wyoj-rand-arrow-right"
|
|
|
|
|
+ id="wyoj-rand-next" aria-label="下一个">›</button>
|
|
|
|
|
+ <button type="button" class="wyoj-rand-pin pinned"
|
|
|
|
|
+ id="wyoj-rand-pin" aria-label="固定" title="已固定,点击恢复自动切换">📌</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="wyoj-random-info">
|
|
|
|
|
+ <span class="wyoj-rand-motto" id="wyoj-rand-motto"></span>
|
|
|
|
|
+ <span class="wyoj-rand-userlink" id="wyoj-rand-userlink"></span>
|
|
|
|
|
+ <img class="wyoj-rand-avatar" id="wyoj-rand-avatar" src="" alt="">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div id="wyoj-random-user-data" data-users='<?= $rand_users_json ?>' style="display:none;"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <script>
|
|
|
|
|
+ (function () {
|
|
|
|
|
+ var wrap = document.getElementById('wyoj-random-user-wrap');
|
|
|
|
|
+ if (!wrap) return;
|
|
|
|
|
+ var dataEl = document.getElementById('wyoj-random-user-data');
|
|
|
|
|
+ if (!dataEl) return;
|
|
|
|
|
+
|
|
|
|
|
+ var users;
|
|
|
|
|
+ try { users = JSON.parse(dataEl.dataset.users); } catch (e) { wrap.style.display = 'none'; return; }
|
|
|
|
|
+ if (!users || users.length === 0) { wrap.style.display = 'none'; return; }
|
|
|
|
|
+
|
|
|
|
|
+ var link = document.getElementById('wyoj-random-user-link');
|
|
|
|
|
+ var imgA = document.getElementById('wyoj-rand-img-a');
|
|
|
|
|
+ var imgB = document.getElementById('wyoj-rand-img-b');
|
|
|
|
|
+ var mottoEl = document.getElementById('wyoj-rand-motto');
|
|
|
|
|
+ var userEl = document.getElementById('wyoj-rand-userlink');
|
|
|
|
|
+ var avatarEl = document.getElementById('wyoj-rand-avatar');
|
|
|
|
|
+ var prevBtn = document.getElementById('wyoj-rand-prev');
|
|
|
|
|
+ var nextBtn = document.getElementById('wyoj-rand-next');
|
|
|
|
|
+ var pinBtn = document.getElementById('wyoj-rand-pin');
|
|
|
|
|
+
|
|
|
|
|
+ var frontImg = imgA;
|
|
|
|
|
+ var backImg = imgB;
|
|
|
|
|
+ var currentIdx = -1;
|
|
|
|
|
+ var currentUrl = '';
|
|
|
|
|
+ var timer = null;
|
|
|
|
|
+ var pinned = true;
|
|
|
|
|
+ var SWAP_MS = 5000;
|
|
|
|
|
+
|
|
|
|
|
+ function preload(url) {
|
|
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
|
|
+ var im = new Image();
|
|
|
|
|
+ im.onload = function () { resolve(); };
|
|
|
|
|
+ im.onerror = function () { reject(); };
|
|
|
|
|
+ im.src = url;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function applyInfo(u) {
|
|
|
|
|
+ mottoEl.textContent = u.motto || '';
|
|
|
|
|
+ userEl.innerHTML = u.userlink;
|
|
|
|
|
+ avatarEl.src = u.avatar;
|
|
|
|
|
+ currentUrl = u.href;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function showImage(u) {
|
|
|
|
|
+ backImg.src = u.bgurl;
|
|
|
|
|
+ requestAnimationFrame(function () {
|
|
|
|
|
+ backImg.classList.add('active');
|
|
|
|
|
+ frontImg.classList.remove('active');
|
|
|
|
|
+ var t = frontImg; frontImg = backImg; backImg = t;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 展示指定索引;如果图挂了就往同方向继续找
|
|
|
|
|
+ function goTo(idx, dir, tries) {
|
|
|
|
|
+ dir = dir || 1;
|
|
|
|
|
+ tries = tries || 0;
|
|
|
|
|
+ if (tries >= users.length) return;
|
|
|
|
|
+ var realIdx = ((idx % users.length) + users.length) % users.length;
|
|
|
|
|
+ var u = users[realIdx];
|
|
|
|
|
+ preload(u.bgurl).then(function () {
|
|
|
|
|
+ currentIdx = realIdx;
|
|
|
|
|
+ applyInfo(u);
|
|
|
|
|
+ showImage(u);
|
|
|
|
|
+ }, function () {
|
|
|
|
|
+ goTo(realIdx + dir, dir, tries + 1);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function schedule() {
|
|
|
|
|
+ clearTimeout(timer);
|
|
|
|
|
+ timer = null;
|
|
|
|
|
+ if (pinned) return;
|
|
|
|
|
+ if (users.length <= 1) return;
|
|
|
|
|
+ timer = setTimeout(function () {
|
|
|
|
|
+ goTo(currentIdx + 1, 1, 0);
|
|
|
|
|
+ schedule();
|
|
|
|
|
+ }, SWAP_MS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function pickRandom() {
|
|
|
|
|
+ if (users.length === 1) return 0;
|
|
|
|
|
+ var i = currentIdx;
|
|
|
|
|
+ while (i === currentIdx) i = Math.floor(Math.random() * users.length);
|
|
|
|
|
+ return i;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化
|
|
|
|
|
+ (function init() {
|
|
|
|
|
+ var tries = 0;
|
|
|
|
|
+ var idx = -1;
|
|
|
|
|
+ for (var i = 0; i < users.length; i++)
|
|
|
|
|
+ if (users[i].username == 'ryp') { idx = i; break; }
|
|
|
|
|
+ // 夹带私货了
|
|
|
|
|
+ if (idx < 0) idx = Math.floor(Math.random() * users.length);
|
|
|
|
|
+ function attempt() {
|
|
|
|
|
+ if (tries >= users.length) { wrap.style.display = 'none'; return; }
|
|
|
|
|
+ tries++;
|
|
|
|
|
+ var u = users[idx];
|
|
|
|
|
+ preload(u.bgurl).then(function () {
|
|
|
|
|
+ currentIdx = idx;
|
|
|
|
|
+ frontImg.src = u.bgurl;
|
|
|
|
|
+ frontImg.classList.add('active');
|
|
|
|
|
+ applyInfo(u);
|
|
|
|
|
+ schedule();
|
|
|
|
|
+ }, function () {
|
|
|
|
|
+ idx = (idx + 1) % users.length;
|
|
|
|
|
+ attempt();
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ attempt();
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ // 点击卡片主体(非按钮、非内部链接)跳转
|
|
|
|
|
+ link.addEventListener('click', function (e) {
|
|
|
|
|
+ if (e.target.closest('button')) return;
|
|
|
|
|
+ if (e.target.closest('a')) return;
|
|
|
|
|
+ if (currentUrl) window.location.href = currentUrl;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 上一个
|
|
|
|
|
+ prevBtn.addEventListener('click', function (e) {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ goTo(currentIdx - 1, -1, 0);
|
|
|
|
|
+ schedule();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 下一个
|
|
|
|
|
+ nextBtn.addEventListener('click', function (e) {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ goTo(currentIdx + 1, 1, 0);
|
|
|
|
|
+ schedule();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 固定 / 取消固定
|
|
|
|
|
+ pinBtn.addEventListener('click', function (e) {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ pinned = !pinned;
|
|
|
|
|
+ pinBtn.classList.toggle('pinned', pinned);
|
|
|
|
|
+ pinBtn.title = pinned ? '已固定,点击恢复自动切换' : '固定当前';
|
|
|
|
|
+ if (pinned) {
|
|
|
|
|
+ clearTimeout(timer);
|
|
|
|
|
+ timer = null;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ schedule();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('visibilitychange', function () {
|
|
|
|
|
+ if (document.hidden) {
|
|
|
|
|
+ clearTimeout(timer);
|
|
|
|
|
+ timer = null;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ schedule();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ })();
|
|
|
|
|
+ </script>
|
|
|
|
|
+ <?php endif ?>
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 博客列表 -->
|
|
|
|
|
+ <div class="row">
|
|
|
|
|
+ <?php foreach ($recentblogs as $blog): ?>
|
|
|
|
|
+ <?= echoBlogCard(queryBlog($blog['id'])) ?>
|
|
|
|
|
+ <?php endforeach ?>
|
|
|
|
|
+ <?php foreach ($topblogs as $blog): ?>
|
|
|
|
|
+ <?= echoBlogCard(queryBlog($blog['id'])) ?>
|
|
|
|
|
+ <?php endforeach ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- ============ 右列:排行榜 ============ -->
|
|
|
|
|
+ <div class="col-12 col-lg-3">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 活跃 -->
|
|
|
|
|
+ <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
+ <div class="card-body p-3">
|
|
|
|
|
+ <h6 class="font-weight-bold mb-3">活跃排行榜</h6>
|
|
|
|
|
+ <?php foreach ($activateusers as $username): ?>
|
|
|
|
|
+ <?php $user = queryUser($username['submitter']); ?>
|
|
|
|
|
+ <div class="d-flex align-items-start border-bottom pb-2 mb-2">
|
|
|
|
|
+ <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
|
|
|
|
|
+ style="width:36px;height:36px;object-fit:cover;"
|
|
|
|
|
+ src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
|
|
|
|
|
+ <div class="flex-grow-1" style="min-width:0;">
|
|
|
|
|
+ <div class="text-truncate font-weight-bold" style="font-size:0.9rem;">
|
|
|
|
|
+ <?= getUserLink($user['username']) ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
|
|
|
|
|
+ <?= $user['motto'] ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php endforeach ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 比赛 -->
|
|
|
|
|
+ <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
+ <div class="card-body p-3">
|
|
|
|
|
+ <h6 class="font-weight-bold mb-1">比赛排行榜</h6>
|
|
|
|
|
+ <div class="mb-3" style="font-size:0.75rem;">
|
|
|
|
|
+ <span class="text-muted">180 天内三场及以上</span>
|
|
|
|
|
+ <a href="/ranklist" class="ml-1">全部排名</a>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php foreach ($topusers as $user): ?>
|
|
|
|
|
+ <div class="d-flex align-items-start border-bottom pb-2 mb-2">
|
|
|
|
|
+ <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
|
|
|
|
|
+ style="width:36px;height:36px;object-fit:cover;"
|
|
|
|
|
+ src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
|
|
|
|
|
+ <div class="flex-grow-1" style="min-width:0;">
|
|
|
|
|
+ <div class="d-flex justify-content-between align-items-baseline">
|
|
|
|
|
+ <span class="text-truncate font-weight-bold" style="font-size:0.9rem;">
|
|
|
|
|
+ <?= getUserLink($user['username']) ?>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <strong class="text-danger ml-1 flex-shrink-0" style="font-size:0.75rem;">
|
|
|
|
|
+ <?= $user['rating'] ?> <small>(±<?= round($user['rating_rd']) * 2 ?>)</small>
|
|
|
|
|
+ </strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
|
|
|
|
|
+ <?= $user['motto'] ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php endforeach ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 黑马 -->
|
|
|
|
|
+ <div class="card shadow-sm border-0 mb-3">
|
|
|
|
|
+ <div class="card-body p-3">
|
|
|
|
|
+ <h6 class="font-weight-bold mb-3">黑马 / 爆冷榜</h6>
|
|
|
|
|
+ <?php foreach ($hotusers as $user): ?>
|
|
|
|
|
+ <div class="d-flex align-items-start border-bottom pb-2 mb-2">
|
|
|
|
|
+ <img class="uoj-avatar rounded-circle flex-shrink-0 mr-2"
|
|
|
|
|
+ style="width:36px;height:36px;object-fit:cover;"
|
|
|
|
|
+ src="https://q.qlogo.cn/g?b=qq&nk=<?= $user['qq'] ?>&s=100" alt="...">
|
|
|
|
|
+ <div class="flex-grow-1" style="min-width:0;">
|
|
|
|
|
+ <div class="d-flex justify-content-between align-items-baseline">
|
|
|
|
|
+ <span class="text-truncate font-weight-bold" style="font-size:0.9rem;">
|
|
|
|
|
+ <?= getUserLink($user['username']) ?>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <strong class="text-danger ml-1 flex-shrink-0" style="font-size:0.75rem;">
|
|
|
|
|
+ Vol <?= round(1000 * $user['rating_vol']) / 1000 ?>
|
|
|
|
|
+ </strong>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="text-truncate" style="font-size:0.8rem;font-family:'SimSun','宋体','Songti SC','STSong',serif;">
|
|
|
|
|
+ <?= $user['motto'] ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <?php endforeach ?>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<!-- 博客卡片悬停效果 -->
|
|
|
|
|
+<style>
|
|
|
|
|
+.wyoj-blog-card {
|
|
|
|
|
+ transition: box-shadow .2s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-blog-card:hover {
|
|
|
|
|
+ box-shadow: 0 4px 14px rgba(0,0,0,.10) !important;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-blog-card .wyoj-blog-title {
|
|
|
|
|
+ font-size: 1.05rem;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/* ---------- 随机用户背景图卡片 ---------- */
|
|
|
|
|
+.wyoj-random-user {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ color: inherit;
|
|
|
|
|
+ box-shadow: 0 .125rem .25rem rgba(0,0,0,.075);
|
|
|
|
|
+ transition: box-shadow 0.2s ease;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-random-user:hover {
|
|
|
|
|
+ box-shadow: 0 .25rem .75rem rgba(0,0,0,.10);
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-random-img-wrap {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ aspect-ratio: 16 / 7;
|
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-img {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transition: opacity 0.6s ease;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-img.active { opacity: 1; }
|
|
|
|
|
+
|
|
|
|
|
+/* 左右箭头 */
|
|
|
|
|
+.wyoj-rand-arrow {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ transform: translateY(-50%);
|
|
|
|
|
+ width: 32px;
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: rgba(255,255,255,0.85);
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ padding: 0 0 2px 0;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ z-index: 3;
|
|
|
|
|
+ opacity: 0.4;
|
|
|
|
|
+ transition: opacity 0.2s ease, background 0.2s ease, transform 0.1s ease;
|
|
|
|
|
+ -webkit-tap-highlight-color: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-random-user:hover .wyoj-rand-arrow { opacity: 1; }
|
|
|
|
|
+.wyoj-rand-arrow:hover {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ transform: translateY(-50%) scale(1.08);
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-arrow:active { transform: translateY(-50%) scale(0.96); }
|
|
|
|
|
+.wyoj-rand-arrow-left { left: 10px; }
|
|
|
|
|
+.wyoj-rand-arrow-right { right: 10px; }
|
|
|
|
|
+
|
|
|
|
|
+/* 固定按钮 */
|
|
|
|
|
+.wyoj-rand-pin {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 10px;
|
|
|
|
|
+ right: 10px;
|
|
|
|
|
+ width: 28px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ border: 0;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: rgba(255,255,255,0.85);
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ z-index: 3;
|
|
|
|
|
+ opacity: 0.4;
|
|
|
|
|
+ transition: opacity 0.2s ease, background 0.2s ease, transform 0.1s ease;
|
|
|
|
|
+ -webkit-tap-highlight-color: transparent;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-random-user:hover .wyoj-rand-pin { opacity: 1; }
|
|
|
|
|
+.wyoj-rand-pin:hover { background: #fff; }
|
|
|
|
|
+.wyoj-rand-pin:active { transform: scale(0.94); }
|
|
|
|
|
+.wyoj-rand-pin.pinned {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ background: #ffc107;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-pin.pinned:hover { background: #ffb300; }
|
|
|
|
|
+
|
|
|
|
|
+/* 信息行 */
|
|
|
|
|
+.wyoj-random-info {
|
|
|
|
|
+ padding: 12px 16px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-motto {
|
|
|
|
|
+ color: #6c757d;
|
|
|
|
|
+ font-size: 0.85rem;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ flex-shrink: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-userlink {
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ line-height: 1.3;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+.wyoj-rand-avatar {
|
|
|
|
|
+ width: 24px;
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ border: 1px solid #dee2e6;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+
|
|
|
|
|
+<?php echoUOJPageFooter() ?>
|
|
|
|
|
+
|