瀏覽代碼

Animation optimized

ryp 3 周之前
父節點
當前提交
3713113c20
共有 1 個文件被更改,包括 223 次插入 和 113 次删除
  1. 223 113
      app/controllers/index.php

+ 223 - 113
app/controllers/index.php

@@ -228,124 +228,232 @@ $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);">
-
-	    <!-- 随机方格背景层 -->
-	    <div id="wyoj-date-grid"
-	         style="position:absolute;top:0;left:0;right:0;bottom:0;display:grid;z-index:0;pointer-events:none;"></div>
-
-	    <!-- 内容层 -->
-	    <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; ?>
-		    </div>
-		</div>
-	</div>
-	<script>
-	(function () {
-	    var card = document.getElementById('wyoj-date-card');
-	    var grid = document.getElementById('wyoj-date-grid');
-	    if (!card || !grid) return;
-
-	    // ===== 可调参数 =====
-	    var COLORS = ['#000000', '#0c0c0c', '#181818', '#242424', '#303030']; // 5 档灰→黑
-	    var CELL   = 22;    // 每个小方格的边长(px)
-	    var P      = 0.3;  // 每格每秒变色的概率(0~1),越大越跳
-	    // ====================
-
-	    var cells = [];
-	    var lastCols = 0, lastRows = 0;
-
-	    function pickColor() {
-	        return COLORS[Math.floor(Math.random() * COLORS.length)];
-	    }
-
-	    function build(force) {
-	        var w = card.clientWidth;
-	        var h = card.clientHeight;
-	        var cols = Math.max(1, Math.ceil(w / CELL));
-	        var rows = Math.max(1, Math.ceil(h / CELL));
-	        if (!force && cols === lastCols && rows === lastRows) return;
-	        lastCols = cols;
-	        lastRows = rows;
-
-	        grid.style.gridTemplateColumns = 'repeat(' + cols + ', 1fr)';
-	        grid.style.gridTemplateRows    = 'repeat(' + rows + ', 1fr)';
-	        grid.innerHTML = '';
-	        cells = [];
-
-	        for (var i = 0; i < cols * rows; i++) {
-	            var d = document.createElement('div');
-	            d.style.backgroundColor = pickColor();
-	            // 每格单独的过渡时长,让变化错开,不会整片一起闪
-	            d.style.transition = 'background-color ' + (0.5 + Math.random() * 0.5).toFixed(2) + 's ease';
-	            grid.appendChild(d);
-	            cells.push(d);
-	        }
-	    }
-
-	    build(true);
-
-	    // 尺寸变化时重建(只在行列数变化时真的重建,避免无谓重置)
-	    if (window.ResizeObserver) {
-	        new ResizeObserver(function () { build(false); }).observe(card);
-	    } else {
-	        window.addEventListener('resize', function () { build(false); });
-	    }
-
-	    // 每秒给每格一个 p 的变色机会
-	    setInterval(function () {
-	        for (var i = 0; i < cells.length; i++) {
-	            if (Math.random() < P) {
-	                cells[i].style.backgroundColor = pickColor();
-	            }
-	        }
-	    }, 1500);
-	})();
-	</script>
-
-
-        <!-- 博客列表 -->
+        <!-- 黑色日期卡片 -->
+        <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'])) ?>
@@ -461,3 +569,5 @@ $csp2daysLeft  = max(0, (int)floor(($csp2Ts - $todayMidnight) / 86400));
 
 <?php echoUOJPageFooter() ?>
 
+<div class="row my-4">
+