hexo-theme-stellar 1.37.0 → 1.39.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.
Files changed (87) hide show
  1. package/.claude/skills/stellar-theme-dev/SKILL.md +50 -0
  2. package/CHANGELOG.md +75 -0
  3. package/CONTRIBUTING.md +155 -0
  4. package/README.md +56 -35
  5. package/_config.yml +16 -13
  6. package/_data/icons.yml +3 -0
  7. package/languages/en.yml +37 -0
  8. package/languages/zh-CN.yml +37 -0
  9. package/languages/zh-TW.yml +37 -0
  10. package/layout/_partial/comments/layout.ejs +2 -1
  11. package/layout/_partial/main/navbar/article_banner.ejs +11 -5
  12. package/layout/_partial/main/navbar/nav_tabs_blog.ejs +2 -1
  13. package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +1 -0
  14. package/layout/_partial/main/notebook/paginator.ejs +3 -2
  15. package/layout/_partial/main/pin_slider.ejs +325 -0
  16. package/layout/_partial/main/post_list/paginator.ejs +4 -3
  17. package/layout/_partial/main/post_list/post_card.ejs +2 -4
  18. package/layout/_partial/scripts/defines.ejs +2 -1
  19. package/layout/_partial/scripts/lazyload.ejs +24 -0
  20. package/layout/_partial/scripts/utils.ejs +58 -15
  21. package/layout/_partial/sidebar/grad-def.ejs +26 -0
  22. package/layout/_partial/sidebar/index_leftbar.ejs +25 -10
  23. package/layout/_partial/sidebar/logo.ejs +22 -1
  24. package/layout/_partial/sidebar/menu.ejs +11 -5
  25. package/layout/_partial/widgets/components/linklist.ejs +2 -2
  26. package/layout/_partial/widgets/toc.ejs +12 -8
  27. package/layout/_plugins/scrollreveal.ejs +17 -2
  28. package/layout/index.ejs +36 -1
  29. package/layout/layout.ejs +1 -1
  30. package/layout/notebooks.ejs +3 -1
  31. package/layout/notes.ejs +3 -1
  32. package/package.json +5 -2
  33. package/scripts/tags/index.js +1 -0
  34. package/scripts/tags/lib/mbti.js +169 -0
  35. package/source/css/_common/canonical.styl +9 -10
  36. package/source/css/_common/device.styl +12 -12
  37. package/source/css/_common/highlight.styl +0 -2
  38. package/source/css/_common/html.styl +2 -1
  39. package/source/css/_common/svg.styl +9 -1
  40. package/source/css/_components/layout.styl +11 -8
  41. package/source/css/_components/list.styl +55 -21
  42. package/source/css/_components/main.styl +2 -2
  43. package/source/css/_components/md.styl +0 -1
  44. package/source/css/_components/pages/article-story.styl +1 -3
  45. package/source/css/_components/partial/article-banner.styl +17 -11
  46. package/source/css/_components/partial/article-footer.styl +0 -1
  47. package/source/css/_components/partial/navbar.styl +10 -27
  48. package/source/css/_components/partial/paginator.styl +9 -11
  49. package/source/css/_components/pin-slider.styl +240 -0
  50. package/source/css/_components/sidebar/footer.styl +1 -1
  51. package/source/css/_components/sidebar/logo.styl +27 -1
  52. package/source/css/_components/sidebar/menu.styl +53 -30
  53. package/source/css/_components/sidebar/nav-area.styl +1 -1
  54. package/source/css/_components/sidebar/search.styl +50 -11
  55. package/source/css/_components/sidebar/sidebar.styl +32 -5
  56. package/source/css/_components/tag-plugins/banner.styl +2 -1
  57. package/source/css/_components/tag-plugins/chat.styl +3 -1
  58. package/source/css/_components/tag-plugins/folding.styl +1 -2
  59. package/source/css/_components/tag-plugins/friends.styl +1 -0
  60. package/source/css/_components/tag-plugins/friends_posts.styl +2 -1
  61. package/source/css/_components/tag-plugins/gallery.styl +1 -2
  62. package/source/css/_components/tag-plugins/grid.styl +1 -2
  63. package/source/css/_components/tag-plugins/image.styl +0 -2
  64. package/source/css/_components/tag-plugins/link.styl +0 -1
  65. package/source/css/_components/tag-plugins/mbti.styl +80 -0
  66. package/source/css/_components/tag-plugins/note.styl +1 -2
  67. package/source/css/_components/tag-plugins/okr.styl +1 -2
  68. package/source/css/_components/tag-plugins/sites.styl +0 -1
  69. package/source/css/_components/tag-plugins/timeline.styl +8 -6
  70. package/source/css/_components/widgets/components.styl +19 -7
  71. package/source/css/_components/widgets/ghuser.styl +3 -2
  72. package/source/css/_components/widgets/list.styl +20 -8
  73. package/source/css/_components/widgets/markdown.styl +5 -2
  74. package/source/css/_components/widgets/related.styl +6 -4
  75. package/source/css/_components/widgets/timeline.styl +1 -1
  76. package/source/css/_components/widgets/toc.styl +9 -6
  77. package/source/css/_components/widgets/widgets.styl +11 -10
  78. package/source/css/_custom.styl +5 -2
  79. package/source/css/_defines/const.styl +2 -2
  80. package/source/css/_defines/func.styl +49 -3
  81. package/source/css/_defines/theme_base.styl +22 -1
  82. package/source/css/_plugins/comments/artalk.styl +2 -2
  83. package/source/css/_plugins/comments/twikoo.styl +0 -2
  84. package/source/css/_plugins/tianli_gpt.styl +3 -0
  85. package/source/js/main.js +39 -17
  86. package/source/js/services/artalk_latest_comment.js +43 -1
  87. package/source/js/services/memos.js +12 -3
@@ -78,16 +78,11 @@ function layoutTitle(main, url, sub) {
78
78
  function layoutWidgets() {
79
79
  var el = '';
80
80
  el += '<div class="widgets">';
81
+ // 左栏共用的 SVG 渐变定义(供激活书签等彩色图标使用,见 grad-def.ejs)
82
+ el += partial('grad-def', {id: 'leftbar-active-icon-grad'})
83
+ // 导航区放进组件滚动区,一起滚动
84
+ el += layoutNavArea()
81
85
  if (page.leftbar) {
82
- var search = {}
83
- if (page.search) {
84
- search = page.search
85
- } else if (wiki?.search) {
86
- search = wiki.search
87
- } else if (topic?.search) {
88
- search = topic.search
89
- }
90
- el += partial('search', {item: search})
91
86
  page.leftbar.forEach((w, i) => {
92
87
  let name = ''
93
88
  let widget = {}
@@ -110,6 +105,20 @@ function layoutWidgets() {
110
105
  el += '</div>';
111
106
  return el;
112
107
  }
108
+ function layoutSearch() {
109
+ if (!page.leftbar) {
110
+ return ''
111
+ }
112
+ var search = {}
113
+ if (page.search) {
114
+ search = page.search
115
+ } else if (wiki?.search) {
116
+ search = wiki.search
117
+ } else if (topic?.search) {
118
+ search = topic.search
119
+ }
120
+ return partial('search', {item: search})
121
+ }
113
122
  function layoutFooterDiv() {
114
123
  const { social } = theme.footer
115
124
  if (social == null) {
@@ -154,6 +163,12 @@ function layoutNavArea() {
154
163
  if (page.menu == false) {
155
164
  return el
156
165
  }
166
+ // wiki 内容页默认隐藏导航区,page.menu 或 wiki.menu 为 true 时显示
167
+ if (page.wiki?.length > 0) {
168
+ if (page.menu !== true && wiki?.menu !== true) {
169
+ return el
170
+ }
171
+ }
157
172
  el += `<div class="nav-area">`
158
173
  el += partial('menu', {where: 'sidebar'})
159
174
  el += `</div>`
@@ -161,6 +176,6 @@ function layoutNavArea() {
161
176
  }
162
177
  %>
163
178
  <%- layoutLogo() %>
164
- <%- layoutNavArea() %>
179
+ <%- layoutSearch() %>
165
180
  <%- layoutWidgets() %>
166
181
  <%- layoutFooterDiv() %>
@@ -37,6 +37,26 @@ function layoutTitle(main, url, sub) {
37
37
  return el
38
38
  }
39
39
 
40
+ function layoutBack() {
41
+ if (where != 'sidebar' || !page.wiki) {
42
+ return ''
43
+ }
44
+ // 开关:page 的 wiki_home 优先,其次 wiki 定义的 wiki_home,默认显示
45
+ const wiki = theme.wiki.tree[page.wiki]
46
+ const enabled = page.wiki_home ?? wiki?.wiki_home ?? true
47
+ if (enabled === false) {
48
+ return ''
49
+ }
50
+ var el = ''
51
+ el += `<div class="wiki-home-wrap">`
52
+ el += `<a class="wiki-home cap" href="${pretty_url(theme.site_tree.index_wiki.base_dir)}">`
53
+ el += icon('default:goback')
54
+ el += `<span>${__('btn.all_wiki')}</span>`
55
+ el += `</a>`
56
+ el += `</div>`
57
+ return el
58
+ }
59
+
40
60
  function layoutDiv() {
41
61
  if (where == 'main') {
42
62
  if (page.header == false) {
@@ -59,7 +79,7 @@ function layoutDiv() {
59
79
  } else if (md_text(logo.avatar)) {
60
80
  el += `<a class="avatar" href="${url_for(md_link(logo.avatar) || '/')}">`
61
81
  if (theme.style.animated_avatar.animate) {
62
- el += `<div class="bg" style="opacity:0;background-image:url(${theme.style.animated_avatar.background});"></div>`
82
+ el += `<div class="bg"></div>`
63
83
  }
64
84
  el += `<img no-lazy class="avatar" src="${md_text(logo.avatar)}">`
65
85
  el += `</a>`
@@ -75,4 +95,5 @@ function layoutDiv() {
75
95
  }
76
96
  %>
77
97
 
98
+ <%- layoutBack() %>
78
99
  <%- layoutDiv() %>
@@ -6,16 +6,22 @@ function layoutDiv() {
6
6
  if (item == undefined || item.id == null || item.url == null) {
7
7
  continue
8
8
  }
9
- el += `<a class="nav-item${item.id == page.menu_id ? ' active' : ''}" title="${item.title}" href="${pretty_url(item.url)}"`
9
+ var active = item.id == page.menu_id
10
+ el += `<a class="nav-item${active ? ' active' : ''}${item.theme?.length > 0 ? ' themed' : ''}"`
11
+ var style = `--item-grad:url(#menu-grad-${item.id})`
10
12
  if (item.theme?.length > 0) {
11
- el += ` style="color:${item.theme}"`
13
+ style += `;--item-theme:${item.theme}`
12
14
  }
13
- el += `>`
15
+ el += ` style="${style}"`
16
+ el += ` title="${item.title}" href="${pretty_url(item.url)}">`
14
17
  if (item.icon?.length > 0) {
15
18
  el += icon(item.icon, 'no-lazy')
16
- } else {
17
- el += `<span>${__(item.title)}</span>`
18
19
  }
20
+ el += `<span>${__(item.title)}</span>`
21
+ if (active) {
22
+ el += `<span class="active-dot"></span>`
23
+ }
24
+ el += partial('grad-def', {id: 'menu-grad-' + item.id})
19
25
  el += `</a>`
20
26
  }
21
27
  el += `</nav>`
@@ -16,7 +16,7 @@ function layoutDiv(linklist) {
16
16
  if (columns == 1 && links.length > 1) {
17
17
  align = 'left'
18
18
  }
19
- el += `<div class="linklist ${align}" ${style}>`
19
+ el += `<div class="linklist ${align}${columns > 1 ? ' multi' : ''}" ${style}>`
20
20
  for (let link of links) {
21
21
  el += partial('link', {item: link, columns: columns})
22
22
  }
@@ -24,4 +24,4 @@ function layoutDiv(linklist) {
24
24
  return el
25
25
  }
26
26
  %>
27
- <%- layoutDiv(item) %>
27
+ <%- layoutDiv(item) %>
@@ -37,9 +37,15 @@ function layoutToc(fallback) {
37
37
 
38
38
  function layoutDiv(fallback) {
39
39
  const tocBody = layoutTocBody()
40
+ const hasToc = tocBody.trim().length > 0
41
+ // 页面是否实际渲染评论区,由 _partial/comments/layout.ejs 标记
42
+ const hasComments = page.cmt_rendered === true
43
+ if (!hasToc && !hasComments) {
44
+ return ''
45
+ }
40
46
  var el = ''
41
47
  el += `<widget class="widget-wrapper${scrollreveal(' ')} toc" id="data-toc" collapse="${item.collapse}">`
42
- if (tocBody.trim().length > 0) {
48
+ if (hasToc) {
43
49
  el += layoutTocHeader()
44
50
  el += `<div class="widget-body">`
45
51
  el += tocBody
@@ -50,13 +56,11 @@ function layoutDiv(fallback) {
50
56
  el += icon('default:upup')
51
57
  el += `<span>${__('btn.top')}</span>`
52
58
  el += `</a>`
53
- if (theme.comments.service && theme.comments.service.length > 0) {
54
- if (page.comments == undefined || page.comments != false) {
55
- el += `<a class="buttom" onclick="util.scrollComment()">`
56
- el += icon('default:tocomment')
57
- el += `<span>${__('btn.comments')}</span>`
58
- el += `</a>`
59
- }
59
+ if (hasComments) {
60
+ el += `<a class="buttom" onclick="util.scrollComment()">`
61
+ el += icon('default:tocomment')
62
+ el += `<span>${__('btn.comments')}</span>`
63
+ el += `</a>`
60
64
  }
61
65
  el += `</div>`
62
66
  el += `</widget>`
@@ -18,11 +18,26 @@
18
18
  return null;
19
19
  };
20
20
 
21
+ // 最近的滚动容器(overflow-y: auto/scroll)
22
+ // 吸顶容器内可能存在内部滚动容器(如侧栏 .widgets),
23
+ // 若直接用吸顶容器做 reveal 容器,折叠区下方的元素永远不会进入视口,导致一直不显示。
24
+ const findScrollContainer = (el) => {
25
+ let node = el.parentElement;
26
+ while (node && node.nodeType === 1) {
27
+ const oy = window.getComputedStyle(node).overflowY;
28
+ if (oy === 'auto' || oy === 'scroll') return node;
29
+ node = node.parentElement;
30
+ }
31
+ return null;
32
+ };
33
+
21
34
  const pinnedGroups = new Map(); // 容器 -> 元素列表
22
35
  const targets = [];
23
36
  els.forEach((el) => {
24
- const container = findPinnedContainer(el);
25
- if (container) {
37
+ const pinned = findPinnedContainer(el);
38
+ if (pinned) {
39
+ // 优先使用最近的滚动容器,其次才用吸顶容器
40
+ const container = findScrollContainer(el) || pinned;
26
41
  const group = pinnedGroups.get(container) || [];
27
42
  group.push(el);
28
43
  pinnedGroups.set(container, group);
package/layout/index.ejs CHANGED
@@ -5,6 +5,32 @@ if (page.menu_id == undefined) {
5
5
  if (page.title && page.wiki) {
6
6
  page.robots = 'noindex,follow';
7
7
  }
8
+ // 首页评论区:仅首页第一页,默认不显示,可通过 site_tree.home.comments 开启/覆盖
9
+ var homeCmt = theme.site_tree?.home?.comments
10
+ if (homeCmt && homeCmt !== false && is_home_first_page()) {
11
+ page.comments = true;
12
+ if (typeof homeCmt === 'object') {
13
+ if (homeCmt.service) {
14
+ page.comments_service = homeCmt.service;
15
+ }
16
+ if (homeCmt.comment_title != null) {
17
+ page.comment_title = homeCmt.comment_title;
18
+ }
19
+ if (homeCmt.comment_id != null) {
20
+ page.comment_id = homeCmt.comment_id;
21
+ }
22
+ var homeCommentServices = ['beaudar', 'utterances', 'giscus', 'twikoo', 'waline', 'artalk'];
23
+ for (var i = 0; i < homeCommentServices.length; i++) {
24
+ var key = homeCommentServices[i];
25
+ if (homeCmt[key]) {
26
+ page[key] = homeCmt[key];
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ // 首页第一页渲染置顶轮播时,列表不再重复展示置顶文章
33
+ var pinSliderActive = is_home_first_page()
8
34
 
9
35
  function layout_post_card(layout, post, content) {
10
36
  var el = '';
@@ -25,6 +51,12 @@ function layout_post_list(partial) {
25
51
  el += `<div class="post-list post">`;
26
52
  page.posts.each(function(post){
27
53
  if (post.indexing != false) {
54
+ if (pinSliderActive) {
55
+ var pinVal = post.pin != null ? post.pin : post.sticky
56
+ if (pinVal !== false && pinVal != null) {
57
+ return
58
+ }
59
+ }
28
60
  el += layout_post_card('post', post, partial(post));
29
61
  }
30
62
  })
@@ -38,4 +70,7 @@ function layout_post_list(partial) {
38
70
  <%- layout_post_list(function(post){
39
71
  return partial('_partial/main/post_list/post_card', {post: post})
40
72
  }) %>
41
- <%- partial('_partial/main/post_list/paginator') %>
73
+ <%- partial('_partial/main/post_list/paginator') %>
74
+ <% if (homeCmt && homeCmt !== false && is_home_first_page()) { %>
75
+ <%- partial('_partial/comments/layout') %>
76
+ <% } %>
package/layout/layout.ejs CHANGED
@@ -49,7 +49,7 @@ if (theme.style.prefers_theme === 'auto') {
49
49
  html += site_background
50
50
  html += `<div id="l_cover">${partial('_partial/cover/index').trim()}</div>`
51
51
  html += `<div class="l_body ${page_type}" id="start" layout="${page.layout}" type="${article_type}" ${indent ? 'text-indent' : ''}>`
52
- html += `<aside class="l_left">`
52
+ html += `<aside class="l_left${theme.style.leftbar?.['ui-style'] === 'card' ? ' leftbar-card' : ''}">`
53
53
  html += `<div class="sidebg"></div>`
54
54
  html += `<div class="leftbar-container${theme.style.leftbar?.blur ? ' leftbar-blur' : ''}">`
55
55
  html += partial('_partial/sidebar/index_leftbar')
@@ -1,8 +1,10 @@
1
1
  <%# 笔记本列表页主体部分 %>
2
2
  <% for (const notebook of Object.values(theme.notebooks.tree)) { %>
3
3
  <div class="post-list wiki">
4
- <a class="post-card wiki<%= scrollreveal(' ') %>" href="<%= pretty_url(notebook.base_dir) %>">
4
+ <div class="post-card-wrap<%= scrollreveal(' ') %>">
5
+ <a class="post-card wiki" href="<%= pretty_url(notebook.base_dir) %>">
5
6
  <%- include('_partial/main/notebook/notebook_card', { notebook: notebook }) %>
6
7
  </a>
8
+ </div>
7
9
  </div>
8
10
  <% } %>
package/layout/notes.ejs CHANGED
@@ -1,12 +1,14 @@
1
1
  <%# 笔记列表页主体部分 %>
2
2
  <div class="post-list post">
3
3
  <% page.posts.each(post => { %>
4
+ <div class="post-card-wrap<%= scrollreveal(' ') %>">
4
5
  <a
5
- class="post-card<%= scrollreveal(' ') %> post"
6
+ class="post-card post"
6
7
  href="<%= pretty_url(post.link || post.path) %><%= page.activeTag ? `?tag=${page.activeTag}` : '' %>"
7
8
  >
8
9
  <%- include('_partial/main/notebook/note_card', { note: post }) %>
9
10
  </a>
11
+ </div>
10
12
  <% }) %>
11
13
  </div>
12
14
  <%- include('_partial/main/notebook/paginator') %>
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "hexo-theme-stellar",
3
- "version": "1.37.0",
3
+ "version": "1.39.0",
4
4
  "description": "Elegant and powerful theme for Hexo.",
5
5
  "main": "package.json",
6
6
  "scripts": {
7
- "test": "echo test",
7
+ "lint": "eslint .",
8
+ "test": "node --test",
9
+ "check": "npm run lint && npm test && python3 docs/knowledge/tools/verify.py",
8
10
  "release": "node release.js",
9
11
  "release:dry": "node release.js --dry-run"
10
12
  },
@@ -31,5 +33,6 @@
31
33
  },
32
34
  "homepage": "https://xaoxuu.com/wiki/stellar/",
33
35
  "devDependencies": {
36
+ "eslint": "^9.39.5"
34
37
  }
35
38
  }
@@ -42,6 +42,7 @@ hexo.extend.tag.register('poetry', require('./lib/poetry')(hexo), true)
42
42
  hexo.extend.tag.register('quot', require('./lib/quot')(hexo))
43
43
  hexo.extend.tag.register('blockquote', require('./lib/blockquote')(hexo), true)
44
44
  hexo.extend.tag.register('hashtag', require('./lib/hashtag')(hexo))
45
+ hexo.extend.tag.register('mbti', require('./lib/mbti')(hexo))
45
46
  hexo.extend.tag.register('okr', require('./lib/okr')(hexo), {ends: true})
46
47
  hexo.extend.tag.register('audio', require('./lib/audio')(hexo))
47
48
  hexo.extend.tag.register('video', require('./lib/video')(hexo))
@@ -0,0 +1,169 @@
1
+ /**
2
+ * mbti.js v1 | https://github.com/xaoxuu/hexo-theme-stellar/
3
+ *
4
+ * {% mbti INTJ-T 93-72-78-83-51 [url:https://...] [image:src] %}
5
+ *
6
+ * - type: 人格类型代码(如 INTJ-T / INTJ,大小写均可),决定标题与配色
7
+ * - scores: 五个百分比,用 - 分隔,顺序固定为 energy-mind-nature-tactics-identity
8
+ * - url: 档案链接(可选,url:https://...),支持完整 URL 或纯档案 ID
9
+ * - image: 标题上方横幅图片地址(可选)
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ var TYPE_ROLES = {
15
+ INTJ: 'analyst',
16
+ INTP: 'analyst',
17
+ ENTJ: 'analyst',
18
+ ENTP: 'analyst',
19
+ INFJ: 'diplomat',
20
+ INFP: 'diplomat',
21
+ ENFJ: 'diplomat',
22
+ ENFP: 'diplomat',
23
+ ISTJ: 'sentinel',
24
+ ISFJ: 'sentinel',
25
+ ESTJ: 'sentinel',
26
+ ESFJ: 'sentinel',
27
+ ISTP: 'explorer',
28
+ ISFP: 'explorer',
29
+ ESTP: 'explorer',
30
+ ESFP: 'explorer'
31
+ };
32
+
33
+ var ROLE_COLORS = {
34
+ analyst: 'purple',
35
+ diplomat: 'green',
36
+ sentinel: 'blue',
37
+ explorer: 'yellow'
38
+ };
39
+
40
+ var TRAIT_KEYS = ['energy', 'mind', 'nature', 'tactics', 'identity'];
41
+
42
+ var TRAIT_COLORS = {
43
+ energy: '#2196f3',
44
+ mind: '#FFBD2B',
45
+ nature: '#3DC550',
46
+ tactics: '#9c27b0',
47
+ identity: '#F44336'
48
+ };
49
+
50
+ // 根据类型代码推导每个维度的偏向侧(trait 侧):E/I、N/S、T/F、J/P、A/T
51
+ function getTraitSides(typeCode) {
52
+ var code = typeCode.replace('-', '');
53
+ var sides = {};
54
+ sides.energy = code.charAt(0) === 'E' ? 'left' : 'right';
55
+ sides.mind = code.charAt(1) === 'N' ? 'left' : 'right';
56
+ sides.nature = code.charAt(2) === 'T' ? 'left' : 'right';
57
+ sides.tactics = code.charAt(3) === 'J' ? 'left' : 'right';
58
+ if (code.charAt(4) === 'A') {
59
+ sides.identity = 'left';
60
+ } else if (code.charAt(4) === 'T') {
61
+ sides.identity = 'right';
62
+ } else {
63
+ sides.identity = 'left';
64
+ console.warn('[mbti] identity variant missing, default to Assertive (-A): ' + typeCode);
65
+ }
66
+ return sides;
67
+ }
68
+
69
+ function clamp(value) {
70
+ value = parseInt(value, 10);
71
+ if (isNaN(value)) {
72
+ value = 0;
73
+ }
74
+ return Math.max(0, Math.min(100, value));
75
+ }
76
+
77
+ function alphaHex(hex, alpha) {
78
+ var r = parseInt(hex.slice(1, 3), 16);
79
+ var g = parseInt(hex.slice(3, 5), 16);
80
+ var b = parseInt(hex.slice(5, 7), 16);
81
+ return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
82
+ }
83
+
84
+ module.exports = ctx => function(args) {
85
+ args = ctx.args.map(args, ['image', 'url'], ['type', 'scores']);
86
+
87
+ var typeCode = String(args.type || '').trim().toUpperCase();
88
+ if (!typeCode) {
89
+ console.error('[mbti] missing personality type');
90
+ return '';
91
+ }
92
+ var typeMatch = typeCode.match(/^([A-Z]{4})/);
93
+ var typeKey = typeMatch ? typeMatch[1] : null;
94
+ var role = typeKey ? TYPE_ROLES[typeKey] : null;
95
+ if (!role) {
96
+ console.warn('[mbti] unknown personality type: ' + typeCode);
97
+ }
98
+ var themeColor = role ? ROLE_COLORS[role] : '';
99
+
100
+ var __ = (this && typeof this.__ === 'function') ? this.__ : ctx.theme.i18n.__(ctx.config.language);
101
+ var typeName = typeKey ? __('tag_plugins.mbti.types.' + typeKey) : '';
102
+ if (!typeName || typeName.indexOf('tag_plugins.mbti.types.') === 0) {
103
+ typeName = typeCode;
104
+ }
105
+
106
+ var scoreParts = String(args.scores || '').split('-');
107
+ var pcts = [];
108
+ for (var i = 0; i < TRAIT_KEYS.length; i++) {
109
+ pcts.push(clamp(scoreParts[i]));
110
+ }
111
+
112
+ var url = args.url ? String(args.url).trim() : '';
113
+ if (url && !/^https?:\/\//i.test(url)) {
114
+ url = 'https://www.16personalities.com/ch/' + encodeURIComponent('档案') + '/' + url;
115
+ }
116
+ if (url) {
117
+ url = url.replace(/"/g, '&quot;');
118
+ }
119
+
120
+ var image = args.image ? String(args.image).trim() : '';
121
+ if (image) {
122
+ image = image.replace(/"/g, '&quot;');
123
+ }
124
+
125
+ var traitSides = getTraitSides(typeCode);
126
+
127
+ var el = '<div class="tag-plugin colorful note mbti"';
128
+ if (themeColor) {
129
+ el += ' color="' + themeColor + '"';
130
+ }
131
+ el += '>';
132
+ if (image) {
133
+ el += '<img class="mbti-image" src="' + image + '" alt="" />';
134
+ }
135
+ var title = __('tag_plugins.mbti.title', typeCode, typeName);
136
+ el += '<div class="mbti-title">';
137
+ if (url) {
138
+ el += '<a class="mbti-title-link" href="' + url + '" target="_blank" rel="external nofollow noopener noreferrer">' + title + '</a>';
139
+ } else {
140
+ el += '<span class="mbti-title-text">' + title + '</span>';
141
+ }
142
+ el += '</div>';
143
+ el += '<div class="mbti-body">';
144
+ el += '<div class="mbti-traits">';
145
+ for (var j = 0; j < TRAIT_KEYS.length; j++) {
146
+ var key = TRAIT_KEYS[j];
147
+ var left = __('tag_plugins.mbti.traits.' + key + '.left');
148
+ var right = __('tag_plugins.mbti.traits.' + key + '.right');
149
+ var pct = pcts[j];
150
+ // 官网规则:trait 在左端时 reverse=true,圆点位置 = reverse ? 100-pct : pct,高亮侧由圆点位置决定
151
+ var reverse = traitSides[key] === 'left';
152
+ var position = reverse ? 100 - pct : pct;
153
+ var activeSide = position <= 50 ? 'left' : 'right';
154
+ // 填充从主导侧(trait 侧)开始:trait 在右时 fill 右对齐
155
+ var reversed = traitSides[key] === 'right' ? ' reversed' : '';
156
+ el += '<div class="mbti-trait' + reversed + '" style="--theme:' + TRAIT_COLORS[key] + '; --theme-a30:' + alphaHex(TRAIT_COLORS[key], 0.3) + '">';
157
+ el += '<div class="mbti-labels">';
158
+ el += '<span class="mbti-label left' + (activeSide === 'left' ? ' active' : '') + '">' + left + (activeSide === 'left' ? ' <span class="pct">' + pct + '%</span>' : '') + '</span>';
159
+ el += '<span class="mbti-label right' + (activeSide === 'right' ? ' active' : '') + '">' + (activeSide === 'right' ? '<span class="pct">' + pct + '%</span> ' : '') + right + '</span>';
160
+ el += '</div>';
161
+ // fill 从主导侧端点延伸到圆点位置(宽度与圆点互补),保证填充末端与圆点对齐
162
+ el += '<div class="mbti-progress"><div class="fill" style="width:' + (100 - pct) + '%"></div><span class="mbti-dot" style="left:' + position + '%"></span></div>';
163
+ el += '</div>';
164
+ }
165
+ el += '</div>';
166
+ el += '</div>';
167
+ el += '</div>';
168
+ return el;
169
+ };
@@ -2,6 +2,7 @@
2
2
  position: fixed
3
3
  display: flex
4
4
  text-align: center
5
+ border-radius: 8px
5
6
  bottom: 4rem
6
7
  left: 50%
7
8
  transform: translateX(-50%)
@@ -12,17 +13,15 @@
12
13
  box-shadow: 0 2px 8px rgba(black, .1), 0 4px 16px rgba(black, .1), 0 8px 32px rgba(black, .1)
13
14
  .headline
14
15
  font-size: $fsh2
15
- a, button
16
- border-radius: 6px
17
- background: var(--bg-a100)
16
+ margin-bottom: .5rem
17
+ a
18
18
  display: block
19
19
  color: inherit
20
- padding: 1rem
21
- font-size: 1rem
22
- button
23
- padding: 0
24
- width: 3rem
25
- margin-left: 1px
20
+ padding: .75rem
21
+ font-size: $fs-14
22
+
23
+ .canonical-tip.official
24
+ background-color: var(--bg-a100)
26
25
 
27
26
  .canonical-tip.unofficial
28
27
  background-color: hsl(5, 100%, 60%)
@@ -34,4 +33,4 @@
34
33
  50%
35
34
  background-color: hsl(5, 100%, 30%)
36
35
  100%
37
- background-color: hsl(5, 100%, 60%)
36
+ background-color: hsl(5, 100%, 60%)
@@ -20,28 +20,31 @@
20
20
  display: flex
21
21
  justify-content: center
22
22
  flex-direction: column
23
- border-radius: 64px
23
+ gap: $bar-item-gap
24
+ padding: $bar-item-gap
24
25
  @media screen and (min-width: $device-mobile-max)
25
26
  margin-right: 3rem
26
27
  overflow: hidden
27
28
  trans1: all
28
- &:before,&:after
29
- border-radius: 64px
30
29
 
31
- newblur()
30
+ bar-glass()
32
31
 
33
32
 
34
33
  // 侧边栏弹出按钮
35
34
  .float-panel button
35
+ bar-item()
36
36
  cursor: pointer
37
37
  color: var(--text)
38
38
  background: none
39
- width: 48px
40
- height: 48px
39
+ box-sizing: border-box
40
+ width: 36px
41
+ height: 36px
42
+ padding: 4px
41
43
  line-height: 0
42
44
  font-size: 28px
43
- margin: 0
44
45
  display: flex
46
+ justify-content: center
47
+ align-items: center
45
48
  >*
46
49
  path#sep
47
50
  trans1 transform
@@ -49,10 +52,8 @@
49
52
  height: 28px
50
53
 
51
54
  // 侧边栏弹出后
52
- .l_body[leftbar] .float-panel, .l_body[rightbar] .float-panel
53
- box-shadow: 0 0 4px -1px var(--theme), 0 0 16px -4px var(--theme), 0 0 32px -12px var(--theme), 0 0 128px -32px var(--theme)
54
- &:before
55
- background: var(--bg-a50)
55
+ .l_body[leftbar] .float-panel button.leftbar-toggle, .l_body[rightbar] .float-panel button.rightbar-toggle
56
+ bar-item-active()
56
57
 
57
58
  .l_body[leftbar] .float-panel button.leftbar-toggle
58
59
  color: var(--theme)
@@ -74,4 +75,3 @@
74
75
  fill-opacity: 0.3
75
76
  path#sep
76
77
  transform: translateX(2px)
77
-
@@ -14,7 +14,6 @@ p>code:not([class]),li>code:not([class])
14
14
  --theme: var(--text-p3)
15
15
  margin: var(--gap-p) 0
16
16
  border-radius: $border-card
17
- corner-shape: superellipse(1.2)
18
17
  overflow: hidden
19
18
  background: var(--block)
20
19
  font-family: $ff-codeblock
@@ -80,7 +79,6 @@ p>code:not([class]),li>code:not([class])
80
79
  .gist-file
81
80
  border: 1px solid var(--block-border)
82
81
  border-radius: $border-card
83
- corner-shape: superellipse(1.2)
84
82
  overflow: hidden
85
83
  .gist-data
86
84
  --bgColor-default: var(--block)
@@ -1,5 +1,6 @@
1
1
  *
2
2
  outline: none
3
+ corner-shape: $corner-shape
3
4
  html
4
5
  height: 100%
5
6
  width: 100%
@@ -9,7 +10,7 @@ html
9
10
  font-size: $fs-root
10
11
  -webkit-text-size-adjust: 100%
11
12
  -ms-text-size-adjust: 100%
12
- scroll-padding-top: 8px
13
+ scroll-padding-top: 0
13
14
  body
14
15
  background: var(--background)
15
16
  margin: 0
@@ -6,4 +6,12 @@ svg.icon
6
6
  overflow: hidden
7
7
 
8
8
  svg.active-icon
9
- color: var(--theme)
9
+ color: var(--theme)
10
+
11
+ // 隐藏的 SVG 渐变定义容器(配合 --item-grad 与 fill:var(--item-grad) 使用)
12
+ svg.grad-def
13
+ position: absolute
14
+ width: 0 !important
15
+ height: 0 !important
16
+ overflow: hidden
17
+ pointer-events: none