From b83d6adb76e2f711005e233c350674137ac7bc2d Mon Sep 17 00:00:00 2001 From: zqlit Date: Mon, 25 May 2026 12:23:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=80=A7=E8=83=BD=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=96=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/Ying/assets/css/main.css | 232 +++++++--------- themes/Ying/assets/js/modules/artalk.js | 314 +++++++++++++++++----- themes/Ying/assets/js/modules/utils.js | 81 +++--- themes/Ying/layouts/partials/artalk.html | 63 +++++ themes/Ying/layouts/partials/barrage.html | 4 +- themes/Ying/layouts/partials/footer.html | 57 +++- 6 files changed, 506 insertions(+), 245 deletions(-) diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index de415ad8..ca938f6d 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -236,7 +236,7 @@ code::-webkit-scrollbar { } .code-expand-btn { - display: none; + display: flex; position: absolute; bottom: 10px; left: 50%; @@ -256,9 +256,11 @@ code::-webkit-scrollbar { padding: 0; } -/* Show button when collapsed */ -.code-fold-container.collapsed .code-expand-btn { - display: flex; +.code-fold-container:not(.collapsed) .code-expand-btn { + position: sticky; + bottom: 10px; + margin: 10px auto 0; + transform: none; } /* Button Pill Style */ @@ -433,12 +435,48 @@ table td { } blockquote { - margin-left: 4px; - font-weight: 300; - border-left: 3.8px solid rgb(80 80 80); - background-color: rgba(51 51 51/5%); - padding-left: 20px; - word-break: break-all; + position: relative; + margin: 1.2rem 0; + padding: 14px 17px 14px 20px; + border: 1px solid rgba(0, 0, 0, 0.08); + border-left: 4px solid rgba(0, 0, 0, 0.72); + border-radius: 12px; + background: linear-gradient(135deg, rgba(0, 0, 0, 0.02), rgba(0, 0, 0, 0.01)); + color: rgba(30, 30, 30, 0.92); + font-size: 0.95em; + font-weight: 400; + line-height: 1.68; + word-break: break-word; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06); +} + +blockquote::before { + content: "“"; + position: absolute; + top: -8px; + left: 13px; + color: rgba(0, 0, 0, 0.08); + font-size: 42px; + line-height: 1; + font-family: Georgia, serif; + pointer-events: none; +} + +blockquote p { + position: relative; + margin: 0.25rem 0 !important; +} + +[data-theme="dark"] blockquote { + border-color: rgba(148, 163, 184, 0.18); + border-left-color: rgba(168, 181, 205, 0.82); + background: linear-gradient(135deg, rgba(30, 41, 59, 0.92), rgba(15, 23, 42, 0.78)); + color: rgba(226, 232, 240, 0.96); + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.22); +} + +[data-theme="dark"] blockquote::before { + color: rgba(203, 213, 225, 0.16); } a { @@ -567,16 +605,22 @@ ul { } .header-avatar { - display: flex; + display: inline-flex; align-items: center; justify-content: center; + gap: 10px; margin-right: 16px; - padding: 0; + padding: 8px 10px 8px 8px; + border-radius: 999px; cursor: pointer; + -webkit-user-select: none; + user-select: none; + -webkit-tap-highlight-color: transparent; } .avatar-theme-tip { - margin-left: 10px; + margin-left: 0; + pointer-events: none; padding: 4px 12px; border-radius: 999px; background: #111827; @@ -1416,52 +1460,6 @@ ul { background: rgba(0,0,0,0.7); } -/* Expandable Code Blocks */ -.highlight.collapsed { - max-height: 300px; - position: relative; -} - -.highlight.collapsed::after { - content: ""; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 80px; - background: linear-gradient(to bottom, transparent, #f6f8fa); - pointer-events: none; -} - -[data-theme="dark"] .highlight.collapsed::after { - background: linear-gradient(to bottom, transparent, #282a36); -} - -.expand-btn-container { - text-align: center; - position: absolute; - bottom: 0; - width: 100%; - padding: 10px; - z-index: 5; - display: none; -} - -.highlight.collapsed .expand-btn-container { - display: block; -} - -.expand-btn { - background: var(--link-color); - color: white; - border: none; - padding: 5px 15px; - border-radius: 20px; - cursor: pointer; - font-size: 12px; - box-shadow: 0 2px 5px rgba(0,0,0,0.2); -} - [data-theme="dark"] .highlight { background: #282a36; /* Night Mode: Dark BG (Dracula) */ color: #f8f8f2; /* Night Mode: Light Text */ @@ -3018,59 +3016,6 @@ input[type=submit] { box-shadow: none; } -.post-content .sen-image-frame[data-title]::after, -.entry-content .sen-image-frame[data-title]::after { - content: attr(data-title); - position: absolute; - left: 10px; - bottom: 10px; - max-width: calc(100% - 20px); - padding: 5px 10px; - border-radius: 4px; - border: 1px solid rgba(0, 0, 0, 0.08); - background: rgba(255, 255, 255, 0.72); - color: rgba(0, 0, 0, 0.82); - -webkit-backdrop-filter: blur(16px) saturate(180%); - backdrop-filter: blur(16px) saturate(180%); - font-size: 13px; - font-weight: 500; - letter-spacing: 0.03em; - line-height: 1.2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - pointer-events: none; - opacity: 0; - transform: translateY(4px) translateZ(0); - transition: opacity 0.4s 0.2s cubic-bezier(0.22, 0.61, 0.36, 1), - transform 0.4s 0.2s cubic-bezier(0.22, 0.61, 0.36, 1); - will-change: transform, opacity; -} - -.post-content .sen-image-frame.is-visible[data-title]::after, -.entry-content .sen-image-frame.is-visible[data-title]::after { - opacity: 1; - transform: translateY(0) translateZ(0); -} - -[data-theme="dark"] .post-content .sen-image-frame[data-title]::after, -[data-theme="dark"] .entry-content .sen-image-frame[data-title]::after { - border-color: rgba(255, 255, 255, 0.12); - background: rgba(0, 0, 0, 0.62); - color: rgba(255, 255, 255, 0.92); -} - -@media screen and (max-width: 768px) { - .post-content .sen-image-frame[data-title]::after, - .entry-content .sen-image-frame[data-title]::after { - left: 8px; - bottom: 8px; - max-width: calc(100% - 16px); - padding: 4px 8px; - font-size: 12px; - } -} - /* 设置进度条的背景色 */ .bar { background: #2aae67 !important; @@ -3582,8 +3527,8 @@ input[type=submit] { .floating-tools a { width: 42px; height: 42px; - background: rgba(255, 255, 255, 0.68); - color: rgba(0, 0, 0, 0.75); + background: rgba(255, 255, 255, 0.94); + color: rgba(24, 24, 27, 0.86); border-radius: 50%; font-size: 20px; cursor: pointer; @@ -3591,30 +3536,30 @@ input[type=submit] { display: flex; justify-content: center; align-items: center; - -webkit-backdrop-filter: blur(16px) saturate(180%); - backdrop-filter: blur(16px) saturate(180%); - border: 1px solid rgba(255, 255, 255, 0.5); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07); + -webkit-backdrop-filter: none; + backdrop-filter: none; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: 0 6px 18px rgba(15, 23, 42, 0.12); transform: translateZ(0); - transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; + transition: background 0.2s ease, transform 0.2s ease; } .floating-tools a:hover { - background: rgba(255, 255, 255, 0.88); + background: #fff; transform: translateZ(0) translateY(-2px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.16); } [data-theme="dark"] .floating-tools a { - background: rgba(30, 30, 35, 0.62); - color: rgba(220, 220, 230, 0.78); - border-color: rgba(255, 255, 255, 0.1); - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.22); + background: rgba(30, 41, 59, 0.94); + color: rgba(241, 245, 249, 0.9); + border-color: rgba(148, 163, 184, 0.22); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.34); } [data-theme="dark"] .floating-tools a:hover { - background: rgba(45, 45, 50, 0.78); - box-shadow: 0 4px 18px rgba(0, 0, 0, 0.32); + background: rgba(51, 65, 85, 0.98); + box-shadow: 0 12px 26px rgba(0, 0, 0, 0.42); } /* ── ViewImage — Apple-style lightbox ── */ @@ -3710,7 +3655,7 @@ input[type=submit] { .img-grid { display: grid; gap: 4px; - margin: 0.6rem 0; + margin: 0.2rem 0; } .img-grid.cols-2 { grid-template-columns: repeat(2, 1fr); } @@ -3740,17 +3685,23 @@ input[type=submit] { border-radius: 4px; } -/* Suppress title overlay inside grids */ -.img-grid .sen-image-frame[data-title]::after { - display: none; -} - -/* Mobile: reduce to 2 columns */ -@media screen and (max-width: 480px) { +/* Mobile: reduce columns for smaller screens */ +@media screen and (max-width: 640px) { .img-grid.cols-3, .img-grid.cols-4 { grid-template-columns: repeat(2, 1fr); } + .img-grid .sen-image-frame { + aspect-ratio: auto; + } +} + +@media screen and (max-width: 400px) { + .img-grid.cols-2, + .img-grid.cols-3, + .img-grid.cols-4 { + grid-template-columns: 1fr; + } } /* ── Toast ── */ @@ -3777,8 +3728,8 @@ input[type=submit] { transform: translateY(-10px); transition: opacity 0.28s ease, transform 0.28s ease; pointer-events: auto; - -webkit-backdrop-filter: blur(20px) saturate(180%); - backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); will-change: transform, opacity; } @@ -3814,8 +3765,8 @@ input[type=submit] { [data-theme="dark"] .ying-toast--info { background: rgba(159, 196, 244, 0.22); color: #f3f7fd; - -webkit-backdrop-filter: blur(24px) saturate(180%); - backdrop-filter: blur(24px) saturate(180%); + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); } @media screen and (max-width: 480px) { @@ -4545,8 +4496,7 @@ hr, color 0.5s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.5s cubic-bezier(0.4, 0, 0.2, 1), fill 0.5s cubic-bezier(0.4, 0, 0.2, 1), - opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.5s cubic-bezier(0.4, 0, 0.2, 1) !important; + opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1) !important; } /* --- Transition Effects (Deprecated old block) --- */ diff --git a/themes/Ying/assets/js/modules/artalk.js b/themes/Ying/assets/js/modules/artalk.js index 2ea5a0ed..4cf0d0f5 100644 --- a/themes/Ying/assets/js/modules/artalk.js +++ b/themes/Ying/assets/js/modules/artalk.js @@ -154,75 +154,257 @@ window.initArtalk = function() { setTimeout(saveConf, 2000); setTimeout(saveConf, 5000); - // Handle list-loaded event for lazy loading avatars and animations - artalk.on('list-loaded', function() { - if (window.artalkConfig.aiComment) { - var ai = window.artalkConfig.aiComment; - var listBody = document.querySelector('.atk-list .atk-list-body'); - if (listBody && !listBody.querySelector('.atk-ai-comment')) { - var aiCommentHtml = ` -
-
- ${ai.name} -
-
-
- ${ai.name} - ${window.artalkConfig.aiBadge || 'AI'} - ${ai.date} -
-
-
${ai.content}
-
-
-
- `; - listBody.insertAdjacentHTML('afterbegin', aiCommentHtml); - - // Hide "No Comment" placeholder if it exists - var noComment = document.querySelector('.atk-list-no-comment'); - if (noComment) { - noComment.style.display = 'none'; - } - } - } + // ── Badge system: 友链 / 友圈 / 评论等级 ── +var badgesInjected = false; - // Add staggered animation to the comment list - var list = document.querySelector('.atk-list'); - if (list) { - list.setAttribute('uk-scrollspy', 'target: > .atk-comment-wrap; cls: uk-animation-slide-bottom-small; delay: 50'); - } +var normalizeHost = function(h) { + return (h || '').replace(/^www\./, ''); +}; - // Apply granular animations to inner elements - document.querySelectorAll('.atk-comment-wrap:not(.atk-ai-comment)').forEach(function(wrap) { - var avatar = wrap.querySelector('.atk-avatar'); - if (avatar) avatar.setAttribute('uk-scrollspy', 'cls: uk-animation-scale-up; delay: 200'); - - var main = wrap.querySelector('.atk-main'); - if (main) main.setAttribute('uk-scrollspy', 'cls: uk-animation-slide-right-small; delay: 300'); - - var nick = wrap.querySelector('.atk-nick'); - if (nick) nick.setAttribute('uk-scrollspy', 'cls: uk-animation-slide-top-small; delay: 350'); - }); - - // Find all avatars in the comment list - // We let lazyload.js handle the avatars automatically by calling lazyLoadAgain() - // which will find these new images, set the placeholder, and observe them. - - // Trigger lazy load update - if (window.lazyLoadAgain) { - window.lazyLoadAgain(); - } - - // Force hide "No Comment" if AI comment exists - if (window.artalkConfig.aiComment) { - var noComment = document.querySelector('.atk-list-no-comment'); - var aiComment = document.querySelector('.atk-ai-comment'); - if (aiComment && noComment) { - noComment.style.display = 'none'; - } - } +var buildMap = function(data) { + var hosts = new Map(); + var names = new Map(); + if (data && data.hosts) { + data.hosts.forEach(function(item) { + if (item.host) hosts.set(item.host, item.title || item.host); }); + } + if (data && data.names) { + data.names.forEach(function(n) { + if (n.lower) names.set(n.lower, n.title); + }); + } + return { hosts: hosts, names: names }; +}; + +var linkMap = null; +var feedMap = null; + +// Comment rank table — Song dynasty 九品十八阶 +var RANK_TABLE = [ + { min: 2000, title: '正一品·太师', short: '太师', color: '#b8860b', bg: 'rgba(184,134,11,0.22)' }, + { min: 1400, title: '从一品·太尉', short: '太尉', color: '#b8860b', bg: 'rgba(184,134,11,0.18)' }, + { min: 1050, title: '正二品·参知政事', short: '参知政事', color: '#7c3aed', bg: 'rgba(124,58,237,0.18)' }, + { min: 800, title: '从二品·节度使', short: '节度使', color: '#7c3aed', bg: 'rgba(124,58,237,0.15)' }, + { min: 600, title: '正三品·御史中丞', short: '御史中丞', color: '#a855f7', bg: 'rgba(168,85,247,0.15)' }, + { min: 480, title: '从三品·秘书监', short: '秘书监', color: '#a855f7', bg: 'rgba(168,85,247,0.12)' }, + { min: 380, title: '正四品·谏议大夫', short: '谏议大夫', color: '#c084fc', bg: 'rgba(192,132,252,0.12)' }, + { min: 300, title: '从四品·侍读学士', short: '侍读学士', color: '#c084fc', bg: 'rgba(192,132,252,0.10)' }, + { min: 240, title: '正五品·给事中', short: '给事中', color: '#dc2626', bg: 'rgba(220,38,38,0.15)' }, + { min: 190, title: '从五品·知州', short: '知州', color: '#dc2626', bg: 'rgba(220,38,38,0.12)' }, + { min: 150, title: '正六品·侍御史', short: '侍御史', color: '#ef4444', bg: 'rgba(239,68,68,0.12)' }, + { min: 115, title: '从六品·通判', short: '通判', color: '#ef4444', bg: 'rgba(239,68,68,0.10)' }, + { min: 85, title: '正七品·知县', short: '知县', color: '#059669', bg: 'rgba(5,150,105,0.15)' }, + { min: 60, title: '从七品·殿中侍御史', short: '殿中侍御史', color: '#059669', bg: 'rgba(5,150,105,0.12)' }, + { min: 40, title: '正八品·大理评事', short: '大理评事', color: '#10b981', bg: 'rgba(16,185,129,0.12)' }, + { min: 25, title: '从八品·录事参军', short: '录事参军', color: '#10b981', bg: 'rgba(16,185,129,0.10)' }, + { min: 15, title: '正九品·主簿', short: '主簿', color: '#a16207', bg: 'rgba(161,98,7,0.15)' }, + { min: 5, title: '从九品·司户参军', short: '司户参军', color: '#a16207', bg: 'rgba(161,98,7,0.12)' }, + { min: 0, title: '庶民', short: '庶民', color: '#9ca3af', bg: 'rgba(156,163,175,0.12)' } +]; + +var getRank = function(count) { + for (var i = 0; i < RANK_TABLE.length; i++) { + if (count >= RANK_TABLE[i].min) return RANK_TABLE[i]; + } + return RANK_TABLE[RANK_TABLE.length - 1]; +}; + +// localStorage-based cross-page user comment counter with page dedup +var USER_COUNT_KEY = 'atk_user_counts_v2'; +var loadUserCounts = function() { + try { + var raw = localStorage.getItem(USER_COUNT_KEY); + return raw ? JSON.parse(raw) : { _pages: {} }; + } catch(e) { return { _pages: {} }; } +}; +var saveUserCounts = function(counts) { + try { localStorage.setItem(USER_COUNT_KEY, JSON.stringify(counts)); } catch(e) {} +}; +var getPageKey = function() { + return window.artalkConfig ? (window.artalkConfig.pageKey || window.location.pathname) : window.location.pathname; +}; + +var processBadges = function() { + if (!linkMap && !feedMap) return; + + var data = loadUserCounts(); + var pageKey = getPageKey(); + var pages = data._pages || {}; + var isNewPage = !pages[pageKey]; + + // Count per-user comments on current page + var pageCounts = {}; + document.querySelectorAll('.atk-comment-wrap:not(.atk-ai-comment)').forEach(function(wrap) { + var nickEl = wrap.querySelector('.atk-nick'); + if (!nickEl) return; + var name = (nickEl.textContent || '').trim(); + if (name) pageCounts[name] = (pageCounts[name] || 0) + 1; + }); + + // Accumulate: only add counts if this page hasn't been processed before + var userCounts = {}; + for (var name in pageCounts) { + var prev = data[name] || 0; + if (isNewPage) { + data[name] = prev + pageCounts[name]; + } + userCounts[name] = data[name]; + } + if (isNewPage) { + pages[pageKey] = true; + data._pages = pages; + } + saveUserCounts(data); + + document.querySelectorAll('.atk-comment-wrap:not(.atk-ai-comment)').forEach(function(wrap) { + var nickEl = wrap.querySelector('.atk-nick'); + var avatarEl = wrap.querySelector('.atk-avatar'); + if (!nickEl) return; + + var nickText = (nickEl.textContent || '').trim(); + var nickLower = nickText.toLowerCase(); + var nickLink = nickEl.querySelector('a[href]'); + var commentHost = null; + if (nickLink) { + try { commentHost = normalizeHost(new URL(nickLink.getAttribute('href')).hostname); } catch(e) {} + } + + // 1. 友链 badge (green gradient) — from /api/links?all=1 + if (linkMap && !nickEl.querySelector('.atk-link-badge')) { + var linkMatch = null; + if (commentHost && linkMap.hosts.has(commentHost)) { + linkMatch = linkMap.hosts.get(commentHost); + } else if (linkMap.names.has(nickLower)) { + linkMatch = linkMap.names.get(nickLower); + } + if (linkMatch) { + var lb = document.createElement('span'); + lb.className = 'atk-link-badge'; + lb.textContent = '友链'; + lb.title = '友链: ' + linkMatch; + nickEl.appendChild(lb); + } + } + + // 2. 友圈 badge (gold gradient) — from /api/feeds + if (feedMap && !nickEl.querySelector('.atk-feed-badge')) { + var feedMatch = null; + if (commentHost && feedMap.hosts.has(commentHost)) { + feedMatch = feedMap.hosts.get(commentHost); + } else if (feedMap.names.has(nickLower)) { + feedMatch = feedMap.names.get(nickLower); + } + if (feedMatch) { + var fb = document.createElement('span'); + fb.className = 'atk-feed-badge'; + fb.textContent = '友圈'; + fb.title = '友圈: ' + feedMatch; + nickEl.appendChild(fb); + } + } + + // 3. Comment rank badge — inline next to name + if (!nickEl.querySelector('.atk-rank-badge')) { + var count = userCounts[nickText] || 0; + var rank = getRank(count); + var rb = document.createElement('span'); + rb.className = 'atk-rank-badge'; + rb.textContent = rank.short; + rb.title = rank.title + ' (' + count + '条评论)'; + var idx = RANK_TABLE.indexOf(rank); + if (idx > 0) { + var next = RANK_TABLE[idx - 1]; + var diff = next.min - count; + if (diff > 0) { + var progress = Math.round((count - rank.min) / (next.min - rank.min) * 100); + rb.title += ' — 距' + next.short + '还差' + diff + '条 (' + progress + '%)'; + } + } + nickEl.appendChild(rb); + } + }); +}; + +var injectAllBadges = function() { + if (badgesInjected) return; + badgesInjected = true; + + if (window.friendLinks) linkMap = buildMap(window.friendLinks); + if (window.friendFeeds) feedMap = buildMap(window.friendFeeds); + + if (linkMap || feedMap) { + processBadges(); + } +}; + +artalk.on('list-loaded', function() { + // Badge injection + badgesInjected = false; + setTimeout(injectAllBadges, 300); + + if (window.artalkConfig.aiComment) { + var ai = window.artalkConfig.aiComment; + var listBody = document.querySelector('.atk-list .atk-list-body'); + if (listBody && !listBody.querySelector('.atk-ai-comment')) { + var aiCommentHtml = ` +
+
+ ${ai.name} +
+
+
+ ${ai.name} + ${window.artalkConfig.aiBadge || 'AI'} + ${ai.date} +
+
+
${ai.content}
+
+
+
+ `; + listBody.insertAdjacentHTML('afterbegin', aiCommentHtml); + + var noComment = document.querySelector('.atk-list-no-comment'); + if (noComment) { + noComment.style.display = 'none'; + } + } + } + + // Add staggered animation to the comment list + var list = document.querySelector('.atk-list'); + if (list) { + list.setAttribute('uk-scrollspy', 'target: > .atk-comment-wrap; cls: uk-animation-slide-bottom-small; delay: 50'); + } + + // Apply granular animations to inner elements + document.querySelectorAll('.atk-comment-wrap:not(.atk-ai-comment)').forEach(function(wrap) { + var avatar = wrap.querySelector('.atk-avatar'); + if (avatar) avatar.setAttribute('uk-scrollspy', 'cls: uk-animation-scale-up; delay: 200'); + + var main = wrap.querySelector('.atk-main'); + if (main) main.setAttribute('uk-scrollspy', 'cls: uk-animation-slide-right-small; delay: 300'); + + var nick = wrap.querySelector('.atk-nick'); + if (nick) nick.setAttribute('uk-scrollspy', 'cls: uk-animation-slide-top-small; delay: 350'); + }); + + // Trigger lazy load update + if (window.lazyLoadAgain) { + window.lazyLoadAgain(); + } + + // Force hide "No Comment" if AI comment exists + if (window.artalkConfig.aiComment) { + var noComment = document.querySelector('.atk-list-no-comment'); + var aiComment = document.querySelector('.atk-ai-comment'); + if (aiComment && noComment) { + noComment.style.display = 'none'; + } + } +}); } catch (e) { console.error('Artalk init failed:', e); diff --git a/themes/Ying/assets/js/modules/utils.js b/themes/Ying/assets/js/modules/utils.js index 0f77b14b..850b37b3 100644 --- a/themes/Ying/assets/js/modules/utils.js +++ b/themes/Ying/assets/js/modules/utils.js @@ -1,43 +1,41 @@ function themeSweep(newTheme, callback) { - var veil = document.createElement('div'); - var gradient = newTheme === 'dark' - ? 'linear-gradient(135deg,rgba(2,6,23,0.18),rgba(15,23,42,0.55) 45%,rgba(30,41,59,0.36))' - : 'linear-gradient(135deg,rgba(255,247,237,0.28),rgba(252,250,245,0.62) 45%,rgba(254,243,199,0.32))'; + // View Transition API — browser-native crossfade, zero JS animation overhead. + // Same principle as image lightbox transitions. Chrome/Edge 111+, Opera 97+. + if (document.startViewTransition) { + document.startViewTransition(function() { callback(); }); + return; + } - veil.style.cssText = + // Fallback: lightweight overlay for Safari / Firefox + var oldTheme = document.documentElement.getAttribute('data-theme'); + var tint = oldTheme === 'dark' + ? 'rgba(15,23,42,0.95)' + : 'rgba(255,255,255,0.95)'; + + var overlay = document.createElement('div'); + overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;pointer-events:none;' + - 'background:' + gradient + ';' + - 'opacity:0;transition:opacity 0.42s cubic-bezier(0.22,0.61,0.36,1);' + + 'background:' + tint + ';opacity:1;' + + 'transition:opacity 0.22s ease;' + 'will-change:opacity;contain:paint;'; - document.body.appendChild(veil); + document.body.appendChild(overlay); var cleaned = false; function cleanup() { if (cleaned) return; cleaned = true; - if (veil.parentNode) veil.parentNode.removeChild(veil); + if (overlay.parentNode) overlay.parentNode.removeChild(overlay); } - var applied = false; - function applyTheme() { - if (applied) return; - applied = true; - callback(); - veil.style.opacity = '0'; - } + callback(); + overlay.offsetWidth; + overlay.style.opacity = '0'; - veil.offsetWidth; - veil.style.opacity = '1'; - - setTimeout(applyTheme, 300); - veil.addEventListener('transitionend', function(e) { - if (!applied || e.propertyName !== 'opacity') return; + overlay.addEventListener('transitionend', function(e) { + if (e.propertyName !== 'opacity') return; cleanup(); }); - setTimeout(function() { - applyTheme(); - cleanup(); - }, 950); + setTimeout(cleanup, 380); } // Toast Message Helper @@ -85,23 +83,36 @@ window.initThemeToggle = function() { window.initCodeFold = function() { var codeBlocks = document.querySelectorAll('.highlight'); codeBlocks.forEach(function(block) { - if (block.offsetHeight > 360) { - if (block.parentNode.classList.contains('code-fold-container')) return; + var wrapper = block.parentNode && block.parentNode.classList.contains('code-fold-container') ? block.parentNode : null; + if (!wrapper && block.scrollHeight <= 360 && block.offsetHeight <= 360) return; - var wrapper = document.createElement('div'); + if (!wrapper) { + wrapper = document.createElement('div'); wrapper.className = 'code-fold-container collapsed'; block.parentNode.insertBefore(wrapper, block); wrapper.appendChild(block); + } - var btn = document.createElement('button'); + var btn = wrapper.querySelector(':scope > .code-expand-btn'); + if (!btn) { + btn = document.createElement('button'); + btn.type = 'button'; btn.className = 'code-expand-btn'; - btn.innerHTML = ' 展开代码 '; - btn.onclick = function() { - wrapper.classList.remove('collapsed'); - btn.style.display = 'none'; - }; wrapper.appendChild(btn); } + + var syncButton = function() { + var collapsed = wrapper.classList.contains('collapsed'); + btn.innerHTML = collapsed + ? ' 展开代码 ' + : ' 收起代码 '; + }; + + btn.onclick = function() { + wrapper.classList.toggle('collapsed'); + syncButton(); + }; + syncButton(); }); }; diff --git a/themes/Ying/layouts/partials/artalk.html b/themes/Ying/layouts/partials/artalk.html index 445309ad..bda89eb6 100644 --- a/themes/Ying/layouts/partials/artalk.html +++ b/themes/Ying/layouts/partials/artalk.html @@ -26,6 +26,69 @@ color: #ffffff !important; background: #ff6262 !important; } + .atk-link-badge, + .atk-feed-badge, + .atk-rank-badge { + display: inline-block; + margin-left: 4px; + padding: 0 6px; + font-size: 12px; + font-weight: 500; + border-radius: 3px; + line-height: 18px; + vertical-align: middle; + pointer-events: none; + white-space: nowrap; + } + .atk-link-badge { + color: #059669; + background: rgba(5,150,105,0.08); + border: 1px solid rgba(5,150,105,0.15); + } + .atk-feed-badge { + color: #d97706; + background: rgba(217,119,6,0.08); + border: 1px solid rgba(217,119,6,0.15); + } + .atk-rank-badge { + color: #fff; + background: #3b82f6; + border: none; + font-weight: 600; + } + [data-theme="dark"] .atk-link-badge { + color: #34d399; + background: rgba(52,211,153,0.12); + border-color: rgba(52,211,153,0.2); + } + [data-theme="dark"] .atk-feed-badge { + color: #fbbf24; + background: rgba(251,191,36,0.12); + border-color: rgba(251,191,36,0.2); + } + [data-theme="dark"] .atk-rank-badge { + background: #2563eb; + } + + /* 覆盖 Artalk 原版 .atk-badge(博主)/ .atk-pinned-badge(置顶)尺寸 */ + .atk-comment>.atk-main>.atk-header .atk-badge { + font-size: 12px; + padding: 0 6px; + font-weight: 500; + line-height: 18px; + border-radius: 3px; + vertical-align: middle; + color: #fff !important; + background: #1a1a1a !important; + } + .atk-comment>.atk-main>.atk-header .atk-pinned-badge { + font-size: 12px; + padding: 0 6px; + font-weight: 500; + line-height: 18px; + border-radius: 3px; + vertical-align: middle; + }
diff --git a/themes/Ying/layouts/partials/barrage.html b/themes/Ying/layouts/partials/barrage.html index 907273c2..a62050af 100644 --- a/themes/Ying/layouts/partials/barrage.html +++ b/themes/Ying/layouts/partials/barrage.html @@ -1,4 +1,5 @@ {{ if .Site.Params.barrage.enable }} +{{ $barrage := resources.Get "js/modules/barrage.js" | resources.Minify | resources.Fingerprint }}
-{{ end }} \ No newline at end of file + +{{ end }} diff --git a/themes/Ying/layouts/partials/footer.html b/themes/Ying/layouts/partials/footer.html index 9c26bbd5..d2512a81 100644 --- a/themes/Ying/layouts/partials/footer.html +++ b/themes/Ying/layouts/partials/footer.html @@ -51,7 +51,6 @@ {{ $uikit := resources.Get "js/libs/uikit.min.js" }} {{ $easylazyload := resources.Get "js/modules/lazyload.js" }} {{ $viewimage := resources.Get "js/libs/view-image.min.js" }} -{{ $barrage := resources.Get "js/modules/barrage.js" }} {{ $main := resources.Get "js/main.js" }} {{ $mypjax := resources.Get "js/modules/mypjax.js" }} {{ $nprogress := resources.Get "js/libs/nprogress.js" }} @@ -69,7 +68,7 @@ {{ $artalkModule := resources.Get "js/modules/artalk.js" }} {{ $pjaxLib := resources.Get "js/libs/pjax.js" }} -{{ $scripts := slice $iconfont $uikit $easylazyload $viewimage $barrage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $main $mypjax $toast $tiaozhuan $reward | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }} +{{ $scripts := slice $iconfont $uikit $easylazyload $viewimage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $main $mypjax $toast $tiaozhuan $reward | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }} {{ $aiGreetings := "static/js/ai_greetings.js" }} {{ if os.FileExists $aiGreetings }} @@ -78,6 +77,60 @@ + {{/* Fetch friend data at build time — no runtime network requests */}} + {{ $linkHosts := slice }} + {{ $linkNames := slice }} + {{ with resources.GetRemote "https://api.usj.cc/api/links?all=1" }} + {{ with .Err }} + {{ warnf "Links API fetch failed: %s" . }} + {{ else }} + {{ with .Content | transform.Unmarshal }} + {{ range .links }} + {{ $host := "" }} + {{ with .url | urls.Parse }} + {{ $host = .Host | strings.TrimPrefix "www." }} + {{ end }} + {{ $title := .name | default "" }} + {{ if $host }} + {{ $linkHosts = $linkHosts | append (dict "host" $host "title" $title) }} + {{ end }} + {{ if $title }} + {{ $linkNames = $linkNames | append (dict "lower" (lower $title) "title" $title) }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + + + {{ $feedHosts := slice }} + {{ $feedNames := slice }} + {{ with resources.GetRemote "https://api.usj.cc/api/feeds" }} + {{ with .Err }} + {{ warnf "Feeds API fetch failed: %s" . }} + {{ else }} + {{ with .Content | transform.Unmarshal }} + {{ range .feeds }} + {{ $host := "" }} + {{ with .url | urls.Parse }} + {{ $host = .Host | strings.TrimPrefix "www." }} + {{ end }} + {{ $title := .feedTitle | default "" }} + {{ if $host }} + {{ $feedHosts = $feedHosts | append (dict "host" $host "title" $title) }} + {{ end }} + {{ if $title }} + {{ $feedNames = $feedNames | append (dict "lower" (lower $title) "title" $title) }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +