hexo-theme-stellar 1.43.0 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/THIRD-PARTY-NOTICES.md +2 -1
- package/_config.yml +7 -1
- package/layout/_partial/components/collection-item.ejs +1 -1
- package/layout/_partial/cover/wiki_cover.ejs +13 -9
- package/layout/_partial/dropdown.ejs +1 -1
- package/layout/_partial/main/notebook/note_tags.ejs +3 -3
- package/layout/_partial/main/pin_slider.ejs +1 -1
- package/layout/_partial/main/post_list/latest_post_card.ejs +1 -1
- package/layout/_partial/sidebar/search.ejs +12 -12
- package/layout/_plugins/card_hover.ejs +32 -0
- package/layout/_plugins/index.ejs +1 -0
- package/layout/index.ejs +2 -2
- package/layout/index_wiki.ejs +2 -2
- package/layout/notebooks.ejs +1 -1
- package/layout/notes.ejs +1 -1
- package/package.json +1 -1
- package/scripts/tags/lib/dropdown.js +1 -1
- package/scripts/tags/lib/grid.js +4 -1
- package/scripts/tags/lib/link.js +1 -1
- package/source/css/_components/pages/notebook.styl +0 -28
- package/source/css/_components/partial/cover.styl +7 -3
- package/source/css/_components/sidebar/footer.styl +3 -2
- package/source/css/_components/sidebar/search.styl +8 -5
- package/source/css/_plugins/card-hover.styl +63 -0
- package/source/css/_plugins/index.styl +3 -0
- package/source/js/plugins/card-hover.js +254 -0
- package/source/js/plugins/galaxy.js +65 -1
- package/source/js/search/algolia-search.js +37 -1
- package/source/js/search/local-search.js +22 -0
- package/source/js/search/shortcut.js +39 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.44.0
|
|
4
|
+
|
|
5
|
+
> 发布日期:2026-08-21
|
|
6
|
+
|
|
7
|
+
### 新功能
|
|
8
|
+
- 新增可选的卡片 Spotlight 光斑与 3D Tilt 倾斜效果,可通过 `plugins.card_hover` 配置启用、光斑颜色和最大倾斜角度,并按组件独立接入与降级
|
|
9
|
+
- Wiki Hero 操作按钮接入 Spotlight 光效,背景图片与动画改为独立配置,Galaxy 支持逐实例参数覆盖、图片叠加和 WebGL 失败降级
|
|
10
|
+
- 桌面端新增 `Command+K` / `Ctrl+K` 聚焦搜索框快捷键,在窄屏、编辑区域和输入法组合状态下保留原生行为
|
|
11
|
+
|
|
12
|
+
### 样式与重构
|
|
13
|
+
- 搜索控件改用原生搜索输入框、搜索地标和标签关联,完善无障碍语义;本地搜索与 Algolia 结果统一可点击区域、表面样式和 Spotlight 生命周期
|
|
14
|
+
- 笔记标签行复用文章标签胶囊的 hashtag 图标、间距与交互样式
|
|
15
|
+
|
|
16
|
+
### 升级注意(配置变更与破坏性改动)
|
|
17
|
+
- Wiki Hero 的 Galaxy 配置由 `background: galaxy` 迁移为 `animation.type: galaxy`,参数放在 `animation.params`;`background` 现在仅用于背景图片,并可与动画同时显示
|
|
18
|
+
- `plugins.card_hover.enable` 默认为 `false`,升级后不会自动启用卡片动态效果
|
|
19
|
+
|
|
20
|
+
Full Changelog: [1.43.1...1.44.0](https://github.com/xaoxuu/hexo-theme-stellar/compare/1.43.1...1.44.0)
|
|
21
|
+
|
|
22
|
+
## 1.43.1
|
|
23
|
+
|
|
24
|
+
> 发布日期:2026-08-20
|
|
25
|
+
|
|
26
|
+
### 修复
|
|
27
|
+
- 左栏 Footer Social 的外部图片图标统一为 24×24px,非方形图片保持原始比例,避免固有尺寸撑宽按钮或被裁切
|
|
28
|
+
- 发版脚本自动同步主题配置、包信息与安装知识库的版本号,并在最终待提交状态执行完整质量检查
|
|
29
|
+
|
|
30
|
+
Full Changelog: [1.43.0...1.43.1](https://github.com/xaoxuu/hexo-theme-stellar/compare/1.43.0...1.43.1)
|
|
31
|
+
|
|
3
32
|
## 1.43.0
|
|
4
33
|
|
|
5
34
|
> 发布日期:2026-08-20
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## React Bits
|
|
4
4
|
|
|
5
|
-
The Wiki Galaxy WebGL background
|
|
5
|
+
The Wiki Galaxy WebGL background and Card Hover spotlight layer are adapted
|
|
6
|
+
from the Galaxy and Spotlight Card components in
|
|
6
7
|
[React Bits](https://github.com/DavidHDev/react-bits).
|
|
7
8
|
|
|
8
9
|
MIT License
|
package/_config.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
######## Stellar info ########
|
|
2
2
|
stellar:
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.44.0'
|
|
4
4
|
homepage: 'https://xaoxuu.com/wiki/stellar/'
|
|
5
5
|
repo: 'https://github.com/xaoxuu/hexo-theme-stellar'
|
|
6
6
|
main_css: /css/main.css
|
|
@@ -686,6 +686,12 @@ plugins:
|
|
|
686
686
|
# 页面存在 [data-text-adaptive] 元素时才按需加载(theme:背景图平均色 lighten/darken;contrast:黑白对比)
|
|
687
687
|
adaptive_text:
|
|
688
688
|
enable: true
|
|
689
|
+
|
|
690
|
+
# 卡片鼠标跟随光斑与 3D 倾斜:通过 card-hover 组合类按组件接入
|
|
691
|
+
card_hover:
|
|
692
|
+
enable: false
|
|
693
|
+
spotlight_color: 'rgba(255, 255, 255, 0.25)'
|
|
694
|
+
max_tilt: 3 # 最大倾斜角度(deg),运行时限制在 0~8
|
|
689
695
|
|
|
690
696
|
# 赫蹏 (Heti) - 专为中文网页内容设计的排版样式增强
|
|
691
697
|
# https://github.com/sivan/heti
|
|
@@ -5,7 +5,7 @@ var entryPrefix = entry.prefix == null ? '' : String(entry.prefix)
|
|
|
5
5
|
var entryDescription = entry.description == null ? '' : String(entry.description)
|
|
6
6
|
var entryMeta = entry.meta == null ? '' : String(entry.meta)
|
|
7
7
|
var entryClassName = typeof entry.className === 'string' ? entry.className.trim() : ''
|
|
8
|
-
var entryClasses = 'ui-collection__item'
|
|
8
|
+
var entryClasses = 'ui-collection__item card-hover card-hover--spotlight'
|
|
9
9
|
if (entry.active) {
|
|
10
10
|
entryClasses += ' is-active'
|
|
11
11
|
}
|
|
@@ -9,7 +9,8 @@ function layoutWikiCover() {
|
|
|
9
9
|
return el;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
var background = proj.background;
|
|
12
|
+
var background = typeof proj.background === 'string' && proj.background.length > 0 ? proj.background : '';
|
|
13
|
+
var animation = proj.animation && typeof proj.animation === 'object' ? proj.animation : {};
|
|
13
14
|
var preview = proj.preview || {};
|
|
14
15
|
var commands = Array.isArray(preview.commands) ? preview.commands.filter(function (item) {
|
|
15
16
|
return item && item.codes;
|
|
@@ -21,8 +22,10 @@ function layoutWikiCover() {
|
|
|
21
22
|
var actions = Array.isArray(proj.actions) ? proj.actions.filter(function (item) {
|
|
22
23
|
return item && item.title && item.url;
|
|
23
24
|
}) : [];
|
|
24
|
-
var isGalaxy =
|
|
25
|
-
var isImageBackground = background
|
|
25
|
+
var isGalaxy = animation.type === 'galaxy';
|
|
26
|
+
var isImageBackground = background.length > 0;
|
|
27
|
+
var galaxyParams = animation.params && typeof animation.params === 'object' && !Array.isArray(animation.params) ? animation.params : {};
|
|
28
|
+
var galaxyParamsJson = escape_html(JSON.stringify(galaxyParams));
|
|
26
29
|
var projectName = proj.name || proj.title || page.wiki;
|
|
27
30
|
var adaptiveTextAttr = '';
|
|
28
31
|
if (isImageBackground) {
|
|
@@ -33,12 +36,13 @@ function layoutWikiCover() {
|
|
|
33
36
|
|
|
34
37
|
el += '<div class="l_cover wiki wiki-hero' + (hasPreview ? '' : ' without-preview') + '">';
|
|
35
38
|
el += '<div class="wiki-cover-background' + (isGalaxy ? ' galaxy' : '') + (isImageBackground ? ' image' : '') + '" aria-hidden="true">';
|
|
36
|
-
if (
|
|
37
|
-
el += '<canvas></canvas>';
|
|
38
|
-
} else if (isImageBackground) {
|
|
39
|
+
if (isImageBackground) {
|
|
39
40
|
el += '<img class="wiki-cover-background-image" src="' + escape_html(background) + '" alt="" />';
|
|
40
41
|
el += '<img class="wiki-cover-background-blur" src="' + escape_html(background) + '" alt="" />';
|
|
41
42
|
}
|
|
43
|
+
if (isGalaxy) {
|
|
44
|
+
el += '<canvas data-galaxy-params="' + galaxyParamsJson + '"></canvas>';
|
|
45
|
+
}
|
|
42
46
|
el += '<div class="wiki-cover-background-overlay"></div></div>';
|
|
43
47
|
el += '<div class="wiki-cover-layout"' + adaptiveTextAttr + '>';
|
|
44
48
|
el += '<nav class="wiki-navbar" aria-label="' + escape_html(config.title) + '">';
|
|
@@ -58,11 +62,11 @@ function layoutWikiCover() {
|
|
|
58
62
|
}
|
|
59
63
|
el += '<div class="wiki-cover-actions">';
|
|
60
64
|
if (repo) {
|
|
61
|
-
el += '<a class="button wiki-cover-source" href="https://github.com/' + escape_html(repo) + '" target="_blank" rel="external nofollow noopener noreferrer">' + icon('default:github') + '<span>' + escape_html(__('btn.source')) + '</span></a>';
|
|
65
|
+
el += '<a class="button wiki-cover-source card-hover card-hover--spotlight" href="https://github.com/' + escape_html(repo) + '" target="_blank" rel="external nofollow noopener noreferrer">' + icon('default:github') + '<span>' + escape_html(__('btn.source')) + '</span></a>';
|
|
62
66
|
}
|
|
63
|
-
el += '<a class="button" href="#start">' + icon('default:documents') + '<span>' + escape_html(__('btn.docs')) + '</span></a>';
|
|
67
|
+
el += '<a class="button card-hover card-hover--spotlight" href="#start">' + icon('default:documents') + '<span>' + escape_html(__('btn.docs')) + '</span></a>';
|
|
64
68
|
actions.forEach(function (item) {
|
|
65
|
-
el += '<a class="button" href="' + escape_html(item.url) + '" target="_blank" rel="external nofollow noopener noreferrer">';
|
|
69
|
+
el += '<a class="button card-hover card-hover--spotlight" href="' + escape_html(item.url) + '" target="_blank" rel="external nofollow noopener noreferrer">';
|
|
66
70
|
if (item.icon) {
|
|
67
71
|
el += icon(item.icon);
|
|
68
72
|
}
|
|
@@ -48,7 +48,7 @@ function layoutDiv() {
|
|
|
48
48
|
el += '</summary>'
|
|
49
49
|
el += '<div class="dropdown-menu ui-collection" data-ui-surface="glass" data-layout="list" data-variant="nav" data-density="compact">'
|
|
50
50
|
for (let child of items) {
|
|
51
|
-
el += '<a class="dropdown-item ui-collection__item"' + linkAttrs(child) + '>'
|
|
51
|
+
el += '<a class="dropdown-item ui-collection__item card-hover card-hover--spotlight"' + linkAttrs(child) + '>'
|
|
52
52
|
if (child.icon) {
|
|
53
53
|
el += '<span class="ui-collection__leading">' + icon(child.icon, 'no-lazy', true) + '</span>'
|
|
54
54
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
<%#
|
|
1
|
+
<%# 笔记的所属标签(容器与胶囊样式复用 article/article_tags.ejs 的 article-tags) %>
|
|
2
2
|
<% if (page.tags) { %>
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="article-tags<%= scrollreveal(' ') %>">
|
|
4
4
|
<% for (const t of page.tags) { %>
|
|
5
5
|
<% const tag = notebook.tagTree.get(t.toLowerCase()) %>
|
|
6
|
-
<a class="tag" href="<%= pretty_url(tag.path) %>"
|
|
6
|
+
<a class="tag" href="<%= pretty_url(tag.path) %>"><%- icon('default:hashtag') %><%- escape_html(tag.name) %></a>
|
|
7
7
|
<% } %>
|
|
8
8
|
</div>
|
|
9
9
|
<% } %>
|
|
@@ -119,7 +119,7 @@ if (pinItems.length > 0) {
|
|
|
119
119
|
var groupKey = pinType == 'wiki' ? 'wiki' : 'post'
|
|
120
120
|
var sliderInterval = 5000 // 自动轮播间隔固定 5000ms
|
|
121
121
|
var el = ''
|
|
122
|
-
el += '<div class="pin-slider" id="pin-slider" data-interval="' + sliderInterval + '" data-key="' + pinType + '" data-group="' + groupKey + '">'
|
|
122
|
+
el += '<div class="pin-slider card-hover card-hover--spotlight card-hover--tilt" id="pin-slider" data-interval="' + sliderInterval + '" data-key="' + pinType + '" data-group="' + groupKey + '">'
|
|
123
123
|
el += '<div class="pin-slider-track">'
|
|
124
124
|
el += slidesHtml
|
|
125
125
|
el += '</div>'
|
|
@@ -7,7 +7,7 @@ function layoutDiv() {
|
|
|
7
7
|
var bg = background || theme.default.topic;
|
|
8
8
|
// 注意:不能用 `var href` 承接,var 提升会遮蔽 partial 传入的 href 参数
|
|
9
9
|
var link = pretty_url(href || '/');
|
|
10
|
-
el += '<a class="cover" position="bottom" style="--cover-url:url(\'' + escape_html(bg.replace(/'/g, '%27')) + '\')" href="' + link + '">';
|
|
10
|
+
el += '<a class="cover card-hover card-hover--spotlight card-hover--tilt" position="bottom" style="--cover-url:url(\'' + escape_html(bg.replace(/'/g, '%27')) + '\')" href="' + link + '">';
|
|
11
11
|
el += '<img src="' + escape_html(bg) + '" alt=""/>';
|
|
12
12
|
el += '<div class="cover-info" position="bottom" data-text-adaptive="split">';
|
|
13
13
|
if (typeof label !== 'undefined' && label) {
|
|
@@ -17,7 +17,7 @@ function layoutDiv() {
|
|
|
17
17
|
return el
|
|
18
18
|
}
|
|
19
19
|
el += `<div class="search-wrapper" id="search-wrapper">`
|
|
20
|
-
el += `<
|
|
20
|
+
el += `<div class="search-form" role="search">`
|
|
21
21
|
var filter = ''
|
|
22
22
|
if (item.filter == 'auto') {
|
|
23
23
|
if (page.wiki) {
|
|
@@ -31,20 +31,20 @@ function layoutDiv() {
|
|
|
31
31
|
filter = item.filter
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
if (filter.length > 0 && !filter.startsWith('/')) {
|
|
35
|
+
filter = '/' + filter
|
|
36
|
+
}
|
|
37
|
+
var placeholder = item.placeholder || (filter.length > 0 ? __('search.search_in', filter) : __('search.search'))
|
|
38
|
+
var escapedPlaceholder = escape_html(placeholder)
|
|
39
|
+
el += `<label class="search-button" for="search-input" aria-hidden="true">`
|
|
35
40
|
el += icon('default:search', '', true)
|
|
36
|
-
el += `</
|
|
37
|
-
el += `<input type="
|
|
41
|
+
el += `</label>`
|
|
42
|
+
el += `<input type="search" class="search-input" id="search-input" aria-label="${escapedPlaceholder}"`
|
|
38
43
|
if (filter.length > 0) {
|
|
39
|
-
|
|
40
|
-
filter = '/' + filter
|
|
41
|
-
}
|
|
42
|
-
el += ` data-filter="${filter}"`
|
|
43
|
-
el += ` placeholder="${item.placeholder || __('search.search_in', filter)}">`
|
|
44
|
-
} else {
|
|
45
|
-
el += ` placeholder="${item.placeholder || __('search.search')}">`
|
|
44
|
+
el += ` data-filter="${escape_html(filter)}"`
|
|
46
45
|
}
|
|
47
|
-
el +=
|
|
46
|
+
el += ` placeholder="${escapedPlaceholder}">`
|
|
47
|
+
el += `</div>`
|
|
48
48
|
el += `<div id="search-result"></div>`
|
|
49
49
|
el += `<div class="search-no-result">${__('search.no_results')}</div>`
|
|
50
50
|
el += `</div>`
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<%
|
|
2
|
+
var spotlightColor = typeof conf.spotlight_color === 'string' && conf.spotlight_color.trim().length > 0
|
|
3
|
+
? conf.spotlight_color.trim()
|
|
4
|
+
: 'rgba(255, 255, 255, 0.25)';
|
|
5
|
+
var maxTilt = Number(conf.max_tilt);
|
|
6
|
+
if (conf.max_tilt == null || conf.max_tilt === '' || !Number.isFinite(maxTilt)) {
|
|
7
|
+
maxTilt = 3;
|
|
8
|
+
}
|
|
9
|
+
maxTilt = Math.max(0, Math.min(8, maxTilt));
|
|
10
|
+
var cardHoverConfig = {
|
|
11
|
+
spotlightColor: spotlightColor,
|
|
12
|
+
maxTilt: maxTilt
|
|
13
|
+
};
|
|
14
|
+
var cardHoverConfigJson = JSON.stringify(cardHoverConfig).replace(/</g, '\\u003c');
|
|
15
|
+
%>
|
|
16
|
+
<script>
|
|
17
|
+
stellar.initPlugin(() => {
|
|
18
|
+
let disposed = false;
|
|
19
|
+
ctx.card_hover = <%- cardHoverConfigJson %>;
|
|
20
|
+
utils.js('/js/plugins/card-hover.js', { defer: true }).then(() => {
|
|
21
|
+
if (!disposed && stellar.cardHover && typeof stellar.cardHover.mountAll === 'function') {
|
|
22
|
+
stellar.cardHover.mountAll(document);
|
|
23
|
+
}
|
|
24
|
+
}).catch(() => {});
|
|
25
|
+
return () => {
|
|
26
|
+
disposed = true;
|
|
27
|
+
if (stellar.cardHover && typeof stellar.cardHover.destroy === 'function') {
|
|
28
|
+
stellar.cardHover.destroy();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}, 'card-hover');
|
|
32
|
+
</script>
|
|
@@ -29,6 +29,7 @@ function loadPlugins() {
|
|
|
29
29
|
|
|
30
30
|
// search
|
|
31
31
|
if (theme.search[theme.search.service]) {
|
|
32
|
+
el += `<script>window.addEventListener('DOMContentLoaded', function () { utils.js('/js/search/shortcut.js', { defer: true }); });</script>`;
|
|
32
33
|
el += partial(`search/${theme.search.service}`, {conf: theme.search[theme.search.service]});
|
|
33
34
|
}
|
|
34
35
|
|
package/layout/index.ejs
CHANGED
|
@@ -85,9 +85,9 @@ function layout_post_card(layout, post, content) {
|
|
|
85
85
|
if (hero) {
|
|
86
86
|
// hero 封面卡片:背景图由 .post-card 自身背景承载(跟随 corner-shape 圆角裁剪),
|
|
87
87
|
// 子图保留用于 hover 变暗/放大滤镜
|
|
88
|
-
el += `<a class="post-card ${layout}" style="--cover-url:url('${escape_html(post.cover.replace(/'/g, '%27'))}')" href="${pretty_url(post.link || post.path)}">`
|
|
88
|
+
el += `<a class="post-card ${layout} card-hover card-hover--spotlight card-hover--tilt" style="--cover-url:url('${escape_html(post.cover.replace(/'/g, '%27'))}')" href="${pretty_url(post.link || post.path)}">`
|
|
89
89
|
} else {
|
|
90
|
-
el += `<a class="post-card ${layout}" href="${pretty_url(post.link || post.path)}">`
|
|
90
|
+
el += `<a class="post-card ${layout} card-hover card-hover--spotlight card-hover--tilt" href="${pretty_url(post.link || post.path)}">`
|
|
91
91
|
}
|
|
92
92
|
el += content;
|
|
93
93
|
el += '</a>';
|
package/layout/index_wiki.ejs
CHANGED
|
@@ -17,7 +17,7 @@ function layout_wiki_list(partial) {
|
|
|
17
17
|
if (proj.cover) {
|
|
18
18
|
coverStyle = ` style="--cover-url:url('${escape_html(proj.cover.replace(/'/g, '%27'))}')"`
|
|
19
19
|
}
|
|
20
|
-
el += `<a class="wiki-card${proj.cover ? '' : ' no-cover'}"${coverStyle} href="${pretty_url(proj.homepage?.path || '/')}">`
|
|
20
|
+
el += `<a class="wiki-card card-hover card-hover--spotlight card-hover--tilt${proj.cover ? '' : ' no-cover'}"${coverStyle} href="${pretty_url(proj.homepage?.path || '/')}">`
|
|
21
21
|
el += partial(proj)
|
|
22
22
|
el += `</a>`
|
|
23
23
|
el += `</div>`
|
|
@@ -28,7 +28,7 @@ function layout_wiki_list(partial) {
|
|
|
28
28
|
if (proj.cover) {
|
|
29
29
|
coverStyle = ` style="--cover-url:url('${escape_html(proj.cover.replace(/'/g, '%27'))}')"`
|
|
30
30
|
}
|
|
31
|
-
el += `<a class="wiki-card${proj.cover ? '' : ' no-cover'}"${coverStyle} href="${pretty_url(proj.homepage?.path || '/')}">`
|
|
31
|
+
el += `<a class="wiki-card card-hover card-hover--spotlight card-hover--tilt${proj.cover ? '' : ' no-cover'}"${coverStyle} href="${pretty_url(proj.homepage?.path || '/')}">`
|
|
32
32
|
el += partial(proj)
|
|
33
33
|
el += `</a>`
|
|
34
34
|
el += `</div>`
|
package/layout/notebooks.ejs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<% for (const notebook of Object.values(theme.notebooks.tree)) { %>
|
|
3
3
|
<div class="post-list wiki">
|
|
4
4
|
<div class="post-card-wrap<%= scrollreveal(' ') %>">
|
|
5
|
-
<a class="post-card wiki" href="<%= pretty_url(notebook.base_dir) %>">
|
|
5
|
+
<a class="post-card wiki card-hover card-hover--spotlight card-hover--tilt" href="<%= pretty_url(notebook.base_dir) %>">
|
|
6
6
|
<%- include('_partial/main/notebook/notebook_card', { notebook: notebook }) %>
|
|
7
7
|
</a>
|
|
8
8
|
</div>
|
package/layout/notes.ejs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<% page.posts.each(post => { %>
|
|
4
4
|
<div class="post-card-wrap<%= scrollreveal(' ') %>">
|
|
5
5
|
<a
|
|
6
|
-
class="post-card post"
|
|
6
|
+
class="post-card post card-hover card-hover--spotlight card-hover--tilt"
|
|
7
7
|
href="<%= pretty_url(post.link || post.path) %><%= page.activeTag ? `?tag=${page.activeTag}` : '' %>"
|
|
8
8
|
>
|
|
9
9
|
<%- include('_partial/main/notebook/note_card', { note: post }) %>
|
package/package.json
CHANGED
|
@@ -48,7 +48,7 @@ module.exports = ctx => function(args, content = '') {
|
|
|
48
48
|
el += '<div class="dropdown-menu ui-collection" data-ui-surface="glass" data-layout="list" data-variant="nav" data-density="compact">'
|
|
49
49
|
for (const item of items) {
|
|
50
50
|
const url = String(item.url)
|
|
51
|
-
el += `<a class="dropdown-item ui-collection__item" href="${escapeHTML(toUrl(url))}`
|
|
51
|
+
el += `<a class="dropdown-item ui-collection__item card-hover card-hover--spotlight" href="${escapeHTML(toUrl(url))}`
|
|
52
52
|
if (url.includes('://')) {
|
|
53
53
|
el += '" target="_blank" rel="external nofollow noopener noreferrer">'
|
|
54
54
|
} else {
|
package/scripts/tags/lib/grid.js
CHANGED
|
@@ -18,6 +18,9 @@ module.exports = ctx => function(args, content) {
|
|
|
18
18
|
args.w = '240px'
|
|
19
19
|
}
|
|
20
20
|
var el = ''
|
|
21
|
+
var cellClass = args.bg === 'card'
|
|
22
|
+
? 'cell card-hover card-hover--spotlight card-hover--tilt'
|
|
23
|
+
: 'cell'
|
|
21
24
|
el += '<div class="tag-plugin grid"'
|
|
22
25
|
el += ' ' + ctx.args.joinTags(args, ['bg', 'columns']).join(' ')
|
|
23
26
|
el += ' style="'
|
|
@@ -34,7 +37,7 @@ module.exports = ctx => function(args, content) {
|
|
|
34
37
|
// 分组
|
|
35
38
|
var cells = content.split(/<!--\s*cell(.*?)-->/g).filter(item => item.trim().length > 0)
|
|
36
39
|
for (let cell of cells) {
|
|
37
|
-
el += `<div class="
|
|
40
|
+
el += `<div class="${cellClass}" style="`
|
|
38
41
|
if (args.br) {
|
|
39
42
|
el += `border-radius:${args.br};`
|
|
40
43
|
}
|
package/scripts/tags/lib/link.js
CHANGED
|
@@ -32,7 +32,7 @@ module.exports = ctx => function(args) {
|
|
|
32
32
|
}
|
|
33
33
|
var el = ''
|
|
34
34
|
el += '<div class="tag-plugin link dis-select">'
|
|
35
|
-
el += '<a class="link-card' + (args.desc ? ' rich' : ' plain') + '" title="' + safeTitle + '" href="' + args.url + '"'
|
|
35
|
+
el += '<a class="link-card' + (args.desc ? ' rich' : ' plain') + ' card-hover card-hover--spotlight card-hover--tilt" title="' + safeTitle + '" href="' + args.url + '"'
|
|
36
36
|
if (args.url.includes('://')) {
|
|
37
37
|
el += ' target="_blank" rel="external nofollow noopener noreferrer"'
|
|
38
38
|
}
|
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
.md-text .tag-list
|
|
2
|
-
display: flex
|
|
3
|
-
flex-wrap: wrap
|
|
4
|
-
padding: 0
|
|
5
|
-
margin-top: 2rem
|
|
6
|
-
a.tag
|
|
7
|
-
display: inline-flex
|
|
8
|
-
align-items: center
|
|
9
|
-
position: relative
|
|
10
|
-
color: var(--text-p2)
|
|
11
|
-
margin: 4px
|
|
12
|
-
padding: .5em .75rem
|
|
13
|
-
border-radius: 4px
|
|
14
|
-
background: var(--block)
|
|
15
|
-
font-size: $fs-13
|
|
16
|
-
font-weight: 500
|
|
17
|
-
&:before
|
|
18
|
-
content: "#"
|
|
19
|
-
margin-left: -2px
|
|
20
|
-
margin-right: 2px
|
|
21
|
-
opacity: .4
|
|
22
|
-
&:hover
|
|
23
|
-
&:before
|
|
24
|
-
color var(--theme)
|
|
25
|
-
opacity: 1
|
|
26
|
-
color: var(--text)
|
|
27
|
-
background: var(--block-border)
|
|
28
|
-
|
|
29
1
|
.post-list .post-card .meta.cap .tag
|
|
30
2
|
&:before
|
|
31
3
|
content: "#"
|
|
@@ -97,14 +97,18 @@
|
|
|
97
97
|
transform: scale(1.03)
|
|
98
98
|
.wiki-cover-background-overlay
|
|
99
99
|
position: absolute
|
|
100
|
-
z-index:
|
|
100
|
+
z-index: 3
|
|
101
101
|
inset: 0
|
|
102
102
|
pointer-events: none
|
|
103
103
|
background: linear-gradient(to bottom, transparent 0%, transparent 80%, var(--background) 100%)
|
|
104
104
|
&.galaxy
|
|
105
|
-
|
|
105
|
+
&:not(.image)
|
|
106
|
+
background: #000000
|
|
106
107
|
canvas
|
|
107
108
|
display: block
|
|
109
|
+
position: absolute
|
|
110
|
+
z-index: 2
|
|
111
|
+
inset: 0
|
|
108
112
|
width: 100%
|
|
109
113
|
height: 100%
|
|
110
114
|
pointer-events: none
|
|
@@ -224,7 +228,7 @@
|
|
|
224
228
|
border-color: var(--text-banner, var(--text))
|
|
225
229
|
color: var(--text-banner, var(--text))
|
|
226
230
|
background: var(--text-banner, var(--text))
|
|
227
|
-
svg, span
|
|
231
|
+
> svg, > span:not(.card-hover__spotlight)
|
|
228
232
|
filter: invert(1)
|
|
229
233
|
&:hover
|
|
230
234
|
color: var(--text-banner, var(--text))
|
|
@@ -53,12 +53,17 @@
|
|
|
53
53
|
&:after
|
|
54
54
|
opacity: 1
|
|
55
55
|
.search-input
|
|
56
|
+
-webkit-appearance: none
|
|
57
|
+
appearance: none
|
|
56
58
|
width: 100%
|
|
57
59
|
box-sizing: border-box
|
|
58
60
|
font-family: $ff-body
|
|
59
61
|
font-size: $fs-14
|
|
60
62
|
padding: 12px 0
|
|
61
63
|
color: var(--text)
|
|
64
|
+
&::-webkit-search-cancel-button,
|
|
65
|
+
&::-webkit-search-decoration
|
|
66
|
+
-webkit-appearance: none
|
|
62
67
|
&::placeholder
|
|
63
68
|
color: var(--text-p3)
|
|
64
69
|
:root[data-theme="dark"] &
|
|
@@ -74,6 +79,7 @@
|
|
|
74
79
|
flex-shrink: 0
|
|
75
80
|
width: 1.5rem
|
|
76
81
|
height: 1.5rem
|
|
82
|
+
cursor: pointer
|
|
77
83
|
svg
|
|
78
84
|
height: 1rem
|
|
79
85
|
width: 1rem
|
|
@@ -118,14 +124,11 @@
|
|
|
118
124
|
list-style-type: none
|
|
119
125
|
li a
|
|
120
126
|
display: block
|
|
121
|
-
background: var(--ui-
|
|
127
|
+
background: var(--ui-adapter-bg-hover)
|
|
128
|
+
box-shadow: var(--ui-adapter-shadow-hover)
|
|
122
129
|
line-height: 1.2
|
|
123
130
|
padding: 0.75rem 1rem
|
|
124
131
|
border-radius: $border-card-s
|
|
125
|
-
trans1 background
|
|
126
|
-
&:hover
|
|
127
|
-
background: var(--ui-adapter-bg-hover)
|
|
128
|
-
box-shadow: var(--ui-adapter-shadow-hover)
|
|
129
132
|
&:focus-visible
|
|
130
133
|
outline: 2px solid var(--theme)
|
|
131
134
|
outline-offset: 2px
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// 组合类驱动的通用卡片 hover 能力。
|
|
2
|
+
// 仅在客户端完成挂载后生效,脚本失败时保留组件原有表现。
|
|
3
|
+
.card-hover.is-card-hover-ready
|
|
4
|
+
position: relative
|
|
5
|
+
--card-hover-mouse-x: 50%
|
|
6
|
+
--card-hover-mouse-y: 50%
|
|
7
|
+
--card-hover-rotate-x: 0deg
|
|
8
|
+
--card-hover-rotate-y: 0deg
|
|
9
|
+
|
|
10
|
+
.card-hover.is-card-hover-ready.card-hover--tilt
|
|
11
|
+
transform: perspective(900px) translate3d(0, 0, 0) rotateX(var(--card-hover-rotate-x)) rotateY(var(--card-hover-rotate-y))
|
|
12
|
+
transform-origin: center
|
|
13
|
+
transition: transform .25s ease-out, box-shadow .2s ease-out
|
|
14
|
+
|
|
15
|
+
.card-hover.is-card-hover-ready.card-hover--tilt.is-card-hover-active
|
|
16
|
+
transform: perspective(900px) translate3d(0, -2px, 0) rotateX(var(--card-hover-rotate-x)) rotateY(var(--card-hover-rotate-y))
|
|
17
|
+
transition-duration: .08s, .2s
|
|
18
|
+
will-change: transform
|
|
19
|
+
|
|
20
|
+
.card-hover__spotlight
|
|
21
|
+
position: absolute
|
|
22
|
+
inset: 0
|
|
23
|
+
z-index: 2
|
|
24
|
+
border-radius: inherit
|
|
25
|
+
corner-shape: inherit
|
|
26
|
+
background: radial-gradient(circle at var(--card-hover-mouse-x) var(--card-hover-mouse-y), var(--card-hover-spotlight-color, rgba(255, 255, 255, .25)), transparent 80%)
|
|
27
|
+
opacity: 0
|
|
28
|
+
pointer-events: none
|
|
29
|
+
transition: opacity .25s ease-out
|
|
30
|
+
|
|
31
|
+
.card-hover.is-card-hover-ready.card-hover--spotlight
|
|
32
|
+
&.is-card-hover-active, &:focus-within
|
|
33
|
+
> .card-hover__spotlight
|
|
34
|
+
opacity: .6
|
|
35
|
+
|
|
36
|
+
// ScrollReveal 使用外层 .post-card-wrap 的 transform;倾斜只作用于内层卡片。
|
|
37
|
+
// 启用插件时把同尺寸包装器的阴影移到卡片本体,使阴影随 3D transform 移动。
|
|
38
|
+
.post-list:not(.topic) .post-card-wrap
|
|
39
|
+
box-shadow: none
|
|
40
|
+
|
|
41
|
+
.post-list:not(.topic) .post-card.card-hover,
|
|
42
|
+
.post-list.wiki .wiki-card.card-hover
|
|
43
|
+
box-shadow: $boxshadow-card
|
|
44
|
+
|
|
45
|
+
.post-list.wiki .post-card-wrap
|
|
46
|
+
box-shadow: none
|
|
47
|
+
|
|
48
|
+
// link/grid 原本通过 hoverable-card() 写入 transform;显式组合类接管后统一合成倾斜与上浮。
|
|
49
|
+
.md-text .link-card.card-hover.card-hover--tilt.is-card-hover-ready,
|
|
50
|
+
.tag-plugin.grid[bg='card'] > .cell.card-hover.card-hover--tilt.is-card-hover-ready
|
|
51
|
+
transform: perspective(900px) translate3d(0, 0, 0) rotateX(var(--card-hover-rotate-x)) rotateY(var(--card-hover-rotate-y))
|
|
52
|
+
&.is-card-hover-active
|
|
53
|
+
transform: perspective(900px) translate3d(0, -2px, 0) rotateX(var(--card-hover-rotate-x)) rotateY(var(--card-hover-rotate-y))
|
|
54
|
+
|
|
55
|
+
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce)
|
|
56
|
+
.card-hover.is-card-hover-ready.card-hover--tilt,
|
|
57
|
+
.card-hover.is-card-hover-ready.card-hover--tilt.is-card-hover-active,
|
|
58
|
+
.md-text .link-card.card-hover.card-hover--tilt.is-card-hover-ready,
|
|
59
|
+
.tag-plugin.grid[bg='card'] > .cell.card-hover.card-hover--tilt.is-card-hover-ready
|
|
60
|
+
transform: none
|
|
61
|
+
transition: none
|
|
62
|
+
.card-hover.is-card-hover-ready.card-hover--spotlight > .card-hover__spotlight
|
|
63
|
+
opacity: 0
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Card hover spotlight adapted from React Bits Spotlight Card:
|
|
3
|
+
* https://github.com/DavidHDev/react-bits
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
(function () {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
window.stellar = window.stellar || {};
|
|
10
|
+
|
|
11
|
+
const DEFAULT_SPOTLIGHT_COLOR = 'rgba(255, 255, 255, 0.25)';
|
|
12
|
+
const DEFAULT_MAX_TILT = 3;
|
|
13
|
+
const MAX_TILT_LIMIT = 8;
|
|
14
|
+
const mounted = new Map();
|
|
15
|
+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
16
|
+
const finePointer = window.matchMedia('(hover: hover) and (pointer: fine)');
|
|
17
|
+
let lifecycleBound = false;
|
|
18
|
+
|
|
19
|
+
function normalizeConfig() {
|
|
20
|
+
const raw = typeof ctx !== 'undefined' && ctx.card_hover ? ctx.card_hover : {};
|
|
21
|
+
let maxTilt = Number(raw.maxTilt);
|
|
22
|
+
if (raw.maxTilt === null || raw.maxTilt === '' || !Number.isFinite(maxTilt)) {
|
|
23
|
+
maxTilt = DEFAULT_MAX_TILT;
|
|
24
|
+
}
|
|
25
|
+
maxTilt = Math.max(0, Math.min(MAX_TILT_LIMIT, maxTilt));
|
|
26
|
+
|
|
27
|
+
let spotlightColor = typeof raw.spotlightColor === 'string' && raw.spotlightColor.trim()
|
|
28
|
+
? raw.spotlightColor.trim()
|
|
29
|
+
: DEFAULT_SPOTLIGHT_COLOR;
|
|
30
|
+
if (window.CSS && typeof window.CSS.supports === 'function' && !window.CSS.supports('color', spotlightColor)) {
|
|
31
|
+
spotlightColor = DEFAULT_SPOTLIGHT_COLOR;
|
|
32
|
+
}
|
|
33
|
+
return { maxTilt: maxTilt, spotlightColor: spotlightColor };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function canAnimate() {
|
|
37
|
+
return !reduceMotion.matches && finePointer.matches;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requestFrame(state) {
|
|
41
|
+
if (state.frame !== null) return;
|
|
42
|
+
state.frame = window.requestAnimationFrame(function () {
|
|
43
|
+
state.frame = null;
|
|
44
|
+
if (!state.pointer || !document.documentElement.contains(state.element)) return;
|
|
45
|
+
const rect = state.element.getBoundingClientRect();
|
|
46
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
47
|
+
|
|
48
|
+
const x = Math.max(0, Math.min(rect.width, state.pointer.x - rect.left));
|
|
49
|
+
const y = Math.max(0, Math.min(rect.height, state.pointer.y - rect.top));
|
|
50
|
+
const normalizedX = x / rect.width * 2 - 1;
|
|
51
|
+
const normalizedY = y / rect.height * 2 - 1;
|
|
52
|
+
|
|
53
|
+
state.element.style.setProperty('--card-hover-mouse-x', x + 'px');
|
|
54
|
+
state.element.style.setProperty('--card-hover-mouse-y', y + 'px');
|
|
55
|
+
if (state.hasTilt) {
|
|
56
|
+
state.element.style.setProperty('--card-hover-rotate-x', (-normalizedY * state.maxTilt).toFixed(3) + 'deg');
|
|
57
|
+
state.element.style.setProperty('--card-hover-rotate-y', (normalizedX * state.maxTilt).toFixed(3) + 'deg');
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cancelFrame(state) {
|
|
63
|
+
if (state.frame !== null) {
|
|
64
|
+
window.cancelAnimationFrame(state.frame);
|
|
65
|
+
state.frame = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resetSpotlightPosition(state) {
|
|
70
|
+
state.element.style.setProperty('--card-hover-mouse-x', '50%');
|
|
71
|
+
state.element.style.setProperty('--card-hover-mouse-y', '50%');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function deactivatePointer(state) {
|
|
75
|
+
cancelFrame(state);
|
|
76
|
+
state.pointer = null;
|
|
77
|
+
state.element.classList.remove('is-card-hover-active');
|
|
78
|
+
state.element.style.setProperty('--card-hover-rotate-x', '0deg');
|
|
79
|
+
state.element.style.setProperty('--card-hover-rotate-y', '0deg');
|
|
80
|
+
if (!state.spotlight || state.element.matches(':focus-within')) {
|
|
81
|
+
resetSpotlightPosition(state);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resetState(state) {
|
|
86
|
+
deactivatePointer(state);
|
|
87
|
+
resetSpotlightPosition(state);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function unmount(state) {
|
|
91
|
+
resetState(state);
|
|
92
|
+
state.element.removeEventListener('pointerenter', state.onPointerEnter);
|
|
93
|
+
state.element.removeEventListener('pointermove', state.onPointerMove);
|
|
94
|
+
state.element.removeEventListener('pointerleave', state.onPointerLeave);
|
|
95
|
+
state.element.removeEventListener('focusin', state.onFocusIn);
|
|
96
|
+
if (state.spotlight) {
|
|
97
|
+
state.spotlight.removeEventListener('transitionend', state.onSpotlightTransitionEnd);
|
|
98
|
+
}
|
|
99
|
+
state.element.classList.remove('is-card-hover-ready');
|
|
100
|
+
if (state.spotlight && state.spotlight.parentNode === state.element) {
|
|
101
|
+
state.spotlight.remove();
|
|
102
|
+
}
|
|
103
|
+
mounted.delete(state.element);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function unmountAll(root) {
|
|
107
|
+
Array.from(mounted.values()).forEach(function (state) {
|
|
108
|
+
if (!root || state.element === root || (typeof root.contains === 'function' && root.contains(state.element))) {
|
|
109
|
+
unmount(state);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function mount(element) {
|
|
115
|
+
if (!element || mounted.has(element) || !canAnimate()) return;
|
|
116
|
+
const hasSpotlight = element.classList.contains('card-hover--spotlight');
|
|
117
|
+
const hasTilt = element.classList.contains('card-hover--tilt');
|
|
118
|
+
if (!hasSpotlight && !hasTilt) return;
|
|
119
|
+
|
|
120
|
+
const config = normalizeConfig();
|
|
121
|
+
document.documentElement.style.setProperty('--card-hover-spotlight-color', config.spotlightColor);
|
|
122
|
+
let spotlight = null;
|
|
123
|
+
if (hasSpotlight) {
|
|
124
|
+
spotlight = document.createElement('span');
|
|
125
|
+
spotlight.className = 'card-hover__spotlight';
|
|
126
|
+
spotlight.setAttribute('aria-hidden', 'true');
|
|
127
|
+
element.appendChild(spotlight);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const state = {
|
|
131
|
+
element: element,
|
|
132
|
+
spotlight: spotlight,
|
|
133
|
+
hasTilt: hasTilt,
|
|
134
|
+
maxTilt: config.maxTilt,
|
|
135
|
+
frame: null,
|
|
136
|
+
pointer: null,
|
|
137
|
+
onPointerEnter: null,
|
|
138
|
+
onPointerMove: null,
|
|
139
|
+
onPointerLeave: null,
|
|
140
|
+
onFocusIn: null,
|
|
141
|
+
onSpotlightTransitionEnd: null
|
|
142
|
+
};
|
|
143
|
+
state.onPointerEnter = function (event) {
|
|
144
|
+
state.pointer = { x: event.clientX, y: event.clientY };
|
|
145
|
+
element.classList.add('is-card-hover-active');
|
|
146
|
+
requestFrame(state);
|
|
147
|
+
};
|
|
148
|
+
state.onPointerMove = function (event) {
|
|
149
|
+
state.pointer = { x: event.clientX, y: event.clientY };
|
|
150
|
+
requestFrame(state);
|
|
151
|
+
};
|
|
152
|
+
state.onPointerLeave = function () {
|
|
153
|
+
deactivatePointer(state);
|
|
154
|
+
};
|
|
155
|
+
state.onFocusIn = function () {
|
|
156
|
+
if (!element.classList.contains('is-card-hover-active')) {
|
|
157
|
+
resetSpotlightPosition(state);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
state.onSpotlightTransitionEnd = function (event) {
|
|
161
|
+
if (event.target !== spotlight || event.propertyName !== 'opacity') return;
|
|
162
|
+
if (element.classList.contains('is-card-hover-active') || element.matches(':focus-within')) return;
|
|
163
|
+
resetSpotlightPosition(state);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
element.addEventListener('pointerenter', state.onPointerEnter, { passive: true });
|
|
167
|
+
element.addEventListener('pointermove', state.onPointerMove, { passive: true });
|
|
168
|
+
element.addEventListener('pointerleave', state.onPointerLeave, { passive: true });
|
|
169
|
+
element.addEventListener('focusin', state.onFocusIn);
|
|
170
|
+
if (spotlight) {
|
|
171
|
+
spotlight.addEventListener('transitionend', state.onSpotlightTransitionEnd);
|
|
172
|
+
}
|
|
173
|
+
element.classList.add('is-card-hover-ready');
|
|
174
|
+
mounted.set(element, state);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function mountAll(root) {
|
|
178
|
+
bindLifecycle();
|
|
179
|
+
if (!canAnimate()) {
|
|
180
|
+
unmountAll();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const scope = root && typeof root.querySelectorAll === 'function' ? root : document;
|
|
184
|
+
if (scope.nodeType === 1 && scope.matches('.card-hover')) {
|
|
185
|
+
mount(scope);
|
|
186
|
+
}
|
|
187
|
+
scope.querySelectorAll('.card-hover').forEach(mount);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function resetAll() {
|
|
191
|
+
mounted.forEach(resetState);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function handleMotionPreference() {
|
|
195
|
+
if (canAnimate()) {
|
|
196
|
+
mountAll(document);
|
|
197
|
+
} else {
|
|
198
|
+
unmountAll();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function handleRenderedMarkdown(event) {
|
|
203
|
+
const target = event.detail && event.detail.target ? event.detail.target : document;
|
|
204
|
+
mountAll(target);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function handleVisibility() {
|
|
208
|
+
if (document.hidden) resetAll();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function addMediaListener(media, listener) {
|
|
212
|
+
if (typeof media.addEventListener === 'function') {
|
|
213
|
+
media.addEventListener('change', listener);
|
|
214
|
+
} else if (typeof media.addListener === 'function') {
|
|
215
|
+
media.addListener(listener);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function removeMediaListener(media, listener) {
|
|
220
|
+
if (typeof media.removeEventListener === 'function') {
|
|
221
|
+
media.removeEventListener('change', listener);
|
|
222
|
+
} else if (typeof media.removeListener === 'function') {
|
|
223
|
+
media.removeListener(listener);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function bindLifecycle() {
|
|
228
|
+
if (lifecycleBound) return;
|
|
229
|
+
lifecycleBound = true;
|
|
230
|
+
addMediaListener(reduceMotion, handleMotionPreference);
|
|
231
|
+
addMediaListener(finePointer, handleMotionPreference);
|
|
232
|
+
document.addEventListener('stellar:mdrender', handleRenderedMarkdown);
|
|
233
|
+
document.addEventListener('visibilitychange', handleVisibility);
|
|
234
|
+
window.addEventListener('pagehide', resetAll);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function destroy() {
|
|
238
|
+
unmountAll();
|
|
239
|
+
if (!lifecycleBound) return;
|
|
240
|
+
lifecycleBound = false;
|
|
241
|
+
removeMediaListener(reduceMotion, handleMotionPreference);
|
|
242
|
+
removeMediaListener(finePointer, handleMotionPreference);
|
|
243
|
+
document.removeEventListener('stellar:mdrender', handleRenderedMarkdown);
|
|
244
|
+
document.removeEventListener('visibilitychange', handleVisibility);
|
|
245
|
+
window.removeEventListener('pagehide', resetAll);
|
|
246
|
+
document.documentElement.style.removeProperty('--card-hover-spotlight-color');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
stellar.cardHover = {
|
|
250
|
+
mountAll: mountAll,
|
|
251
|
+
unmountAll: unmountAll,
|
|
252
|
+
destroy: destroy
|
|
253
|
+
};
|
|
254
|
+
})();
|
|
@@ -168,7 +168,7 @@ void main() {
|
|
|
168
168
|
}
|
|
169
169
|
`;
|
|
170
170
|
|
|
171
|
-
const
|
|
171
|
+
const defaultParams = {
|
|
172
172
|
focal: [0.5, 0.5],
|
|
173
173
|
rotation: [1.0, 0.0],
|
|
174
174
|
starSpeed: 2.0,
|
|
@@ -185,6 +185,69 @@ void main() {
|
|
|
185
185
|
transparent: true
|
|
186
186
|
};
|
|
187
187
|
|
|
188
|
+
function isFiniteNumber(value) {
|
|
189
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function finiteOrDefault(value, fallback) {
|
|
193
|
+
return isFiniteNumber(value) ? value : fallback;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function nonNegativeOrDefault(value, fallback) {
|
|
197
|
+
return isFiniteNumber(value) && value >= 0 ? value : fallback;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function booleanOrDefault(value, fallback) {
|
|
201
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function pairOrDefault(value, fallback, clampToUnit) {
|
|
205
|
+
if (!Array.isArray(value) || value.length !== 2 || !isFiniteNumber(value[0]) || !isFiniteNumber(value[1])) {
|
|
206
|
+
return fallback.slice();
|
|
207
|
+
}
|
|
208
|
+
if (clampToUnit) {
|
|
209
|
+
return value.map(function (item) {
|
|
210
|
+
return Math.max(0, Math.min(1, item));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return value.slice();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeHue(value, fallback) {
|
|
217
|
+
if (!isFiniteNumber(value)) return fallback;
|
|
218
|
+
return ((value % 360) + 360) % 360;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeParams(value) {
|
|
222
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
223
|
+
return {
|
|
224
|
+
focal: pairOrDefault(source.focal, defaultParams.focal, true),
|
|
225
|
+
rotation: pairOrDefault(source.rotation, defaultParams.rotation, false),
|
|
226
|
+
starSpeed: nonNegativeOrDefault(source.starSpeed, defaultParams.starSpeed),
|
|
227
|
+
density: nonNegativeOrDefault(source.density, defaultParams.density),
|
|
228
|
+
hueShift: normalizeHue(source.hueShift, defaultParams.hueShift),
|
|
229
|
+
speed: nonNegativeOrDefault(source.speed, defaultParams.speed),
|
|
230
|
+
glowIntensity: nonNegativeOrDefault(source.glowIntensity, defaultParams.glowIntensity),
|
|
231
|
+
saturation: nonNegativeOrDefault(source.saturation, defaultParams.saturation),
|
|
232
|
+
mouseRepulsion: booleanOrDefault(source.mouseRepulsion, defaultParams.mouseRepulsion),
|
|
233
|
+
twinkleIntensity: nonNegativeOrDefault(source.twinkleIntensity, defaultParams.twinkleIntensity),
|
|
234
|
+
rotationSpeed: finiteOrDefault(source.rotationSpeed, defaultParams.rotationSpeed),
|
|
235
|
+
repulsionStrength: nonNegativeOrDefault(source.repulsionStrength, defaultParams.repulsionStrength),
|
|
236
|
+
autoCenterRepulsion: nonNegativeOrDefault(source.autoCenterRepulsion, defaultParams.autoCenterRepulsion),
|
|
237
|
+
transparent: booleanOrDefault(source.transparent, defaultParams.transparent)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function readParams(canvas) {
|
|
242
|
+
const raw = canvas.getAttribute('data-galaxy-params');
|
|
243
|
+
if (!raw) return normalizeParams({});
|
|
244
|
+
try {
|
|
245
|
+
return normalizeParams(JSON.parse(raw));
|
|
246
|
+
} catch (e) {
|
|
247
|
+
return normalizeParams({});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
188
251
|
function createShader(gl, type, source) {
|
|
189
252
|
const shader = gl.createShader(type);
|
|
190
253
|
gl.shaderSource(shader, source);
|
|
@@ -235,6 +298,7 @@ void main() {
|
|
|
235
298
|
const background = canvas.parentElement;
|
|
236
299
|
const interactionTarget = canvas.closest('.wiki-hero') || background;
|
|
237
300
|
if (!background || !interactionTarget) return;
|
|
301
|
+
const params = readParams(canvas);
|
|
238
302
|
|
|
239
303
|
let gl;
|
|
240
304
|
try {
|
|
@@ -9,6 +9,25 @@ utils.js(window.searchConfig.js).then(() => {
|
|
|
9
9
|
var client = algoliasearch(window.searchConfig.appId, window.searchConfig.apiKey);
|
|
10
10
|
var index = client.initIndex(window.searchConfig.indexName);
|
|
11
11
|
|
|
12
|
+
function getCardHoverApi() {
|
|
13
|
+
if (typeof stellar === 'undefined' || !stellar.cardHover) return null;
|
|
14
|
+
return stellar.cardHover;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function unmountResultCards(root) {
|
|
18
|
+
var cardHover = getCardHoverApi();
|
|
19
|
+
if (cardHover && typeof cardHover.unmountAll === 'function') {
|
|
20
|
+
cardHover.unmountAll(root);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mountResultCards(root) {
|
|
25
|
+
var cardHover = getCardHoverApi();
|
|
26
|
+
if (cardHover && typeof cardHover.mountAll === 'function') {
|
|
27
|
+
cardHover.mountAll(root);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
function filterResults(hits, filterPath) {
|
|
13
32
|
if (!filterPath || filterPath === '/') return hits;
|
|
14
33
|
var regex = new RegExp(filterPath);
|
|
@@ -26,11 +45,27 @@ utils.js(window.searchConfig.js).then(() => {
|
|
|
26
45
|
var contentSnippet = hit._snippetResult.content.value;
|
|
27
46
|
var title = hit.hierarchy.lvl1 || 'Untitled';
|
|
28
47
|
var item = document.createElement("li");
|
|
29
|
-
|
|
48
|
+
var titleSpan = document.createElement("span");
|
|
49
|
+
titleSpan.className = "search-result-title";
|
|
50
|
+
titleSpan.textContent = title;
|
|
51
|
+
|
|
52
|
+
var link = document.createElement("a");
|
|
53
|
+
link.className = "card-hover card-hover--spotlight";
|
|
54
|
+
link.href = hit.url;
|
|
55
|
+
|
|
56
|
+
var content = document.createElement("p");
|
|
57
|
+
content.className = "search-result-content";
|
|
58
|
+
content.innerHTML = contentSnippet;
|
|
59
|
+
|
|
60
|
+
link.appendChild(content);
|
|
61
|
+
item.appendChild(titleSpan);
|
|
62
|
+
item.appendChild(link);
|
|
30
63
|
resultList.appendChild(item);
|
|
31
64
|
});
|
|
32
65
|
}
|
|
66
|
+
unmountResultCards(resultArea);
|
|
33
67
|
resultArea.replaceChildren(resultList);
|
|
68
|
+
mountResultCards(resultList);
|
|
34
69
|
}
|
|
35
70
|
|
|
36
71
|
inputArea.addEventListener("input", function() {
|
|
@@ -39,6 +74,7 @@ utils.js(window.searchConfig.js).then(() => {
|
|
|
39
74
|
|
|
40
75
|
if (query.length <= 0) {
|
|
41
76
|
searchWrapper.setAttribute('searching', 'false');
|
|
77
|
+
unmountResultCards(resultArea);
|
|
42
78
|
resultArea.replaceChildren();
|
|
43
79
|
return;
|
|
44
80
|
}
|
|
@@ -129,6 +129,25 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
function getCardHoverApi() {
|
|
133
|
+
if (typeof stellar === 'undefined' || !stellar.cardHover) return null;
|
|
134
|
+
return stellar.cardHover;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function unmountResultCards(root) {
|
|
138
|
+
var cardHover = getCardHoverApi();
|
|
139
|
+
if (cardHover && typeof cardHover.unmountAll === 'function') {
|
|
140
|
+
cardHover.unmountAll(root);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function mountResultCards(root) {
|
|
145
|
+
var cardHover = getCardHoverApi();
|
|
146
|
+
if (cardHover && typeof cardHover.mountAll === 'function') {
|
|
147
|
+
cardHover.mountAll(root);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
132
151
|
// 构建单条搜索结果 DOM
|
|
133
152
|
function buildResultElement(dataTitle, sectionName, secText, secFirst, pairs, href) {
|
|
134
153
|
const li = document.createElement('li');
|
|
@@ -140,6 +159,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
140
159
|
li.appendChild(titleSpan);
|
|
141
160
|
|
|
142
161
|
const a = document.createElement('a');
|
|
162
|
+
a.className = 'card-hover card-hover--spotlight';
|
|
143
163
|
a.href = href;
|
|
144
164
|
|
|
145
165
|
if (sectionName) {
|
|
@@ -180,6 +200,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
180
200
|
|
|
181
201
|
$input.addEventListener("input", function() {
|
|
182
202
|
var rawValue = this.value.trim();
|
|
203
|
+
unmountResultCards($resultContent);
|
|
183
204
|
$resultContent.innerHTML = "";
|
|
184
205
|
if (rawValue.length <= 0) {
|
|
185
206
|
$wrapper.setAttribute('searching', 'false');
|
|
@@ -283,6 +304,7 @@ var searchFunc = function(path, filter, wrapperId, searchId, contentId) {
|
|
|
283
304
|
|
|
284
305
|
$resultContent.innerHTML = '';
|
|
285
306
|
$resultContent.appendChild(ul);
|
|
307
|
+
mountResultCards(ul);
|
|
286
308
|
}
|
|
287
309
|
});
|
|
288
310
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
function isSearchShortcut(event) {
|
|
3
|
+
if (event.defaultPrevented || event.isComposing) return false;
|
|
4
|
+
if (event.altKey || event.shiftKey) return false;
|
|
5
|
+
if (!event.metaKey && !event.ctrlKey) return false;
|
|
6
|
+
return typeof event.key === 'string' && event.key.toLowerCase() === 'k';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isEditableTarget(target) {
|
|
10
|
+
if (!target || typeof target.closest !== 'function') return false;
|
|
11
|
+
return target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])') !== null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isNarrowLayout() {
|
|
15
|
+
var leftbarToggle = document.querySelector('.mobile-only.leftbar-toggle');
|
|
16
|
+
return !!(leftbarToggle && window.getComputedStyle(leftbarToggle).display !== 'none');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleShortcut(event) {
|
|
20
|
+
if (!isSearchShortcut(event)) return;
|
|
21
|
+
|
|
22
|
+
var input = document.getElementById('search-input');
|
|
23
|
+
if (!input || isNarrowLayout()) return;
|
|
24
|
+
if (event.target !== input && isEditableTarget(event.target)) return;
|
|
25
|
+
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
input.focus();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function init() {
|
|
31
|
+
document.addEventListener('keydown', handleShortcut);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (document.readyState === 'loading') {
|
|
35
|
+
document.addEventListener('DOMContentLoaded', init, { once: true });
|
|
36
|
+
} else {
|
|
37
|
+
init();
|
|
38
|
+
}
|
|
39
|
+
})();
|