更新
更新了部分方案,不在原 css/js 上直接修改,而是引入自定义 css/js 来确保更新主题样式不会丢失。

前置

Solitude 确实有些不能满足我日益增长的需求,于是动手对其进行初步修改。

注意
请做好备份工作!魔改不规范,恢复两行泪!

1、修改文章链接格式

如果你的文章标题有中文,那么链接就会包含中文,复制下来直接一团乱麻(尤其是分享的时候)。
我们使用hexo-abbrlink来解决这个问题。
注意应用完成后可能需要修改主题的配置文件(置顶文章链接等),不然会404。
还有评论,可能得去你的数据库手动链接一下评论到新的文章链接。如果你评论数量多的话,那估计比较麻烦(

安装插件

终端定位到博客根目录,输入:

1
npm install hexo-abbrlink --save

修改配置

打开_config.yml,将permalink: :year/:month/:day/:title/替换为:

1
2
3
4
permalink: post/:abbrlink.html
abbrlink:
alg: crc32
rep: hex

一键三连后就完成了,效果如图:
新的链接

2.字体更换

知周所众,默认字体看久了是会疲劳的。作为自己的博客,换字体肯定是必要的。

获得字体

废话,没字体怎么继续!

注意
请注意字体版权,在使用字体前,请确保其可以免费使用或者获得授权!

压缩与裁剪字体

显然一个字体文件大小几十MB,肯定是会拖慢网站加载速度的,所以速度和美观只能二选一。

但是有解决方案,可以使用在线工具或者离线工具。

离线剪裁与压缩字体:

1
2
3
4
brew install ripgrep
brew install fonttools brotli
pyftsubset fonts/{字体名称}.ttf --text=$(rg -e '[\w\d]' -oN --no-filename|sort|uniq|tr -d '\n') --no-hinting
fonttools ttLib.woff2 compress -o fonts/{字体名称}.woff2 fonts/{字体名称}.ttf

参考:

提示
反正我是没成功过,还是用在线工具吧。经过测试压缩为.woff2是最好的。我这直接给它压到1MB不到。
woff2

应用

首先在/source下创建fonts文件夹,然后把字体扔进去。

然后新建/themes/solitude/source/css/custom.css,内容如下:

1
2
3
4
5
@font-face {
font-family: "{字体名称}";
src: url("../fonts/{字体名称}.woff2") format("truetype");
font-weight: 300;
}

提示
请自行调整 font-weight。

附:各种字体格式类型对照:

字体格式 字体类型
.ttf truetype
.otf opentype
.woff woff
.woff2 woff2
.svg svg
.eot embedded-opentype

接着,在_comfig.solitudfe.yml配置文件中head下引入该CSS:

1
2
3
extends:
head:
- <link rel="stylesheet" href="/css/custom.css">

继续修改主题配置文件,应用字体,填入刚才的font-family名称即可:

1
2
3
4
5
font:
font-size: 16px
code-font-size: 16px
font-family: "{字体名称}"
code-font-family: '"monospace", monospace'

最后一键三连就能看到效果了。

3.修改代码高亮样式

个人觉得默认的代码高亮样式有点太亮了,因此对其样式进行修改。

/themes/solitude/css/custom.css中加入:

1
2
3
4
code:not([class*='language-']) {
color: var(--efu-red) !important;
background: var(--efu-background) !important;
}

这样可以覆盖原来的样式。
接着修改color varbackground var的值即可。
/themes/solitude/css/_mode/index.styl中存储了所有预设颜色,填入你需要的颜色即可。
例如:color var(--efu-red),即可将其代码高亮修改为红色(#ff3842)

自定义颜色

注意到该index.styl的格式为:

text
1
--efu-{name} {hex}

同时可以看到data-theme=darkdata-theme=light,这意味着可以分别自定义深色和浅色的颜色。

举个栗子:

/themes/solitude/css/custom.css中加入:

1
2
3
4
5
6
7
8
9
[data-theme="dark"] {
--efu-code-background: #2c2c2c;
--efu-code-text: #f47466;
}

[data-theme="light"] {
--efu-code-background: #f3f4f4;
--efu-code-text: #f47466;
}

接着继续加入:

1
2
3
4
code:not([class*='language-']) {
color: var(--efu-code-text) !important;
background: var(--efu-code-background) !important;
}

深色效果:
修改后的代码高亮(深色)
浅色效果:
修改后的代码高亮(浅色)

4.修改引用块颜色

个人观点,默认的样式有点太淡了。
/themes/solitude/css/custom.css中加入:

1
2
3
4
blockquote {
background-color: var(--efu-secondbg);
color: var(--efu-secondtext)
}

修改两个颜色值即可。
颜色预设依然在/themes/solitude/css/_mode/index.styl

注意
无论什么情况,请不要修改预设颜色,可能造成无法预料的后果!

例如,将引用块颜色与主题色同步:

1
2
3
4
blockquote {
background-color: var(--efu-theme);
color: var(--efu-white)
}

提示
因为我两种颜色都适合白字,所以直接用–efu-white了,因为懒。

附:找到要修改元素对应文件的小技巧

例如找到代码高亮的元素
首先用万能的F12大法打开开发者工具,左上角抓取对应的元素,找到Styles。
Styles
点击右上角的index.css,可以看到文件的大概结构。
index.css
可以确定是css,那么直接定位到/themes/solitude/css/
接下来,关键词搜索,可以找到该条目对应的原始css文件,即例子中的的article-container.styl
找到对应配置,直接修改即可。

5.右键菜单拓展

添加复制当前页面链接

预览
新建/themes/solitude/js/copylink.js,内容如下:

1
2
3
4
5
6
7
8
9
10
function copyPostLink() {
const postLink = window.location.href;

navigator.clipboard.writeText(postLink).then(() => {
utils.snackbarShow("当前页面链接已复制到剪贴板", false, 2000);
}).catch(err => {
console.error("无法复制链接", err);
utils.snackbarShow("复制失败,请重试", false, 2000);
});
}

修改_config.solitude.yml,引入js:

1
2
3
extends:
body:
- <script src="/js/copylink.js" async></script>

然后右键菜单添加该项即可:

1
2
3
4
5
6
7
right_menu:
custom_list:
- name: 复制链接
click: copyPostLink()
id: menu-copyPostLink
class:
icon: fas fa-link

6.边栏添加倒计时

预览
此条目参考了: 梦爱吃鱼的方案
给主题侧边栏添加倒计时 - 梦爱吃鱼
新建/themes/solitude/layout/includes/aside/card_countdown.pug

1
2
3
4
5
6
7
8
9
if theme.aside.card_countdown.enable
.card-widget.card-countdown
.item-content
.cd-count-left#countLeft
span.cd-text 距离
span.cd-name#eventName!= theme.aside.card_countdown.event_name
span.cd-time#daysUntil
span.cd-date#eventDate!= theme.aside.card_countdown.event_date
.cd-count-right#countRight

新建/themes/solitude/source/js/countdown.js

提示
若需要自动切换进度条内字体颜色,请将 .cd-many 后面 color 的值改为 –efu-fontcolor 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
const CountdownTimer = (() => {
const config = {
units: {
day: { text: "今日", divider: 1, unit: "小时" },
week: { text: "本周", divider: 24, unit: "天" },
month: { text: "本月", divider: 24, unit: "天" },
year: { text: "本年", divider: 24, unit: "天" }
}
};

function getTimeUnit(unit) {
const now = new Date();
const start = new Date(now.setHours(0, 0, 0, 0));
const end = new Date(now.setHours(23, 59, 59, 999));

if (unit === 'day') {
const currentHour = new Date().getHours();
const remaining = 24 - currentHour;
const percentage = (currentHour / 24) * 100;

return {
name: config.units[unit].text,
remaining: remaining,
percentage: percentage.toFixed(2),
unit: config.units[unit].unit
};
}

const ranges = {
week: () => {
start.setDate(start.getDate() - start.getDay());
end.setDate(end.getDate() - end.getDay() + 6);
},
month: () => {
start.setDate(1);
end.setMonth(end.getMonth() + 1, 0);
},
year: () => {
start.setMonth(0, 1);
end.setMonth(11, 31);
}
};
ranges[unit]?.();

const total = unit === "day" ? 24 : Math.floor((end - start) / 86400000) + 1;
const passed = Math.floor((now - start) / (3600000 * config.units[unit].divider));
const percentage = (passed / total) * 100;

return {
name: config.units[unit].text,
remaining: total - passed,
percentage: percentage.toFixed(2),
unit: config.units[unit].unit
};
}

function updateCountdown() {
const elements = ['eventName', 'eventDate', 'daysUntil', 'countRight']
.map(id => document.getElementById(id));

if (elements.some(el => !el)) return;

const [eventName, eventDate, daysUntil, countRight] = elements;
const timeData = Object.keys(config.units).reduce((acc, unit) => ({ ...acc, [unit]: getTimeUnit(unit) }), {});
const daysRemaining = Math.round((new Date(eventDate.textContent) - new Date().setHours(0, 0, 0, 0)) / 86400000);

daysUntil.textContent = daysRemaining;
countRight.innerHTML = Object.entries(timeData)
.map(([_, item]) => `
<div class="cd-count-item">
<div class="cd-item-name">${item.name}</div>
<div class="cd-item-progress">
<div class="cd-progress-bar" style="width: ${item.percentage}%; opacity: ${item.percentage / 100}"></div>
<span class="cd-percentage ${item.percentage >= 46 ? 'cd-many' : ''}">${item.percentage}%</span>
<span class="cd-remaining ${item.percentage >= 60 ? 'cd-many' : ''}">
<span class="cd-tip">还剩</span>${item.remaining}<span class="cd-tip">${item.unit}</span>
</span>
</div>
</div>
`).join('');
}

function injectStyles() {
const styles = `
.card-countdown .item-content {
display: flex;
}
.cd-count-left {
position: relative;
display: flex;
flex-direction: column;
margin-right: 0.8rem;
line-height: 1.5;
align-items: center;
justify-content: center;
}
.cd-count-left .cd-text {
font-size: 14px;
}
.cd-count-left .cd-name {
font-weight: bold;
font-size: 18px;
}
.cd-count-left .cd-time {
font-size: 30px;
font-weight: bold;
color: var(--efu-fontcolor);
}
.cd-count-left .cd-date {
font-size: 12px;
opacity: 0.6;
}
.cd-count-left::after {
content: "";
position: absolute;
right: -0.8rem;
width: 2px;
height: 80%;
background-color: var(--efu-fontcolor);
opacity: 0.5;
}
.cd-count-right {
flex: 1;
margin-left: .8rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.cd-count-item {
display: flex;
flex-direction: row;
align-items: center;
height: 24px;
}
.cd-item-name {
font-size: 14px;
margin-right: 0.8rem;
white-space: nowrap;
}
.cd-item-progress {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 100%;
width: 100%;
border-radius: 8px;
background-color: var(--efu-background);
overflow: hidden;
}
.cd-progress-bar {
height: 100%;
border-radius: 8px;
background-color: var(--efu-theme);
}
.cd-percentage,
.cd-remaining {
position: absolute;
font-size: 12px;
margin: 0 6px;
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}
.cd-many {
color: var(--efu-white);
}
.cd-remaining {
opacity: 0;
transform: translateX(10px);
}
.card-countdown .item-content:hover .cd-remaining {
transform: translateX(0);
opacity: 1;
}
.card-countdown .item-content:hover .cd-percentage {
transform: translateX(-10px);
opacity: 0;
}
`;

const styleSheet = document.createElement("style");
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
}

let timer;
const start = () => {
injectStyles();
updateCountdown();
timer = setInterval(updateCountdown, 600000);
};

['pjax:complete', 'DOMContentLoaded'].forEach(event => document.addEventListener(event, () => {
clearInterval(timer);
start();
}));

document.addEventListener('pjax:send', () => timer && clearInterval(timer));

return { start, stop: () => timer && clearInterval(timer) };
})();

修改/themes/solitude/layout/includes/widgets/aside/asideSwitch.pug,加入:

1
2
3
case item
when 'countdown'
include ./card_countdown.pug

_config.solitude.yml中引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
aside:
home: # on the homepage
noSticky: "about,countdown"
Sticky: "newestPost,allInfo"
post: # on the article page
noSticky: "about,countdown"
Sticky: "newestPost"
page: # on the page
noSticky: "about,countdown"
Sticky: "newestPost,allInfo"
card_countdown: # aside countdown
enable: true
event_date: '2025-01-29'
event_name: 春节

最后引入js即可:

1
2
3
extends:
body:
- <script defer pjax src="/js/countdown.js"></script>

7.边栏添加访客卡片

预览
此条目参考了青桔的方案。
Solitude添加来访者卡片 - 青桔气球
在 奶思猫API 申请你的API,用于 IPv4/IPv6 信息查询。

奶思猫API

新建/source/aside.yml,内容如下:

1
2
3
4
5
6
7
8
9
10
- name: visinfo
title: 访客信息
class: card-welcome
id:
icon: fas fa-location-dot
content_id: welcome-info
content_css: 'height:160px;overflow:hidden'
content_html: '<div class="welcome_swiper-container" id="welcome-container" style="width: 100%;height: 100%;margin-top: 6px">
<div id="welcome_container_wrapper" class="swiper-wrapper"></div>
</div>'

新建/themes/solitude/source/js/visinfo.js,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
let ipLocation; // 确保 ipLocation 在全局范围内定义

// 进行 fetch 请求
fetch('https://api.nsmao.net/api/ip/query?key={apikey}') //填入你自己的apikey
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
ipLocation = data;
if (isHomePage()) {
showWelcome();
}
})
.catch(error => console.error('Error:', error));

function getDistance(e1, n1, e2, n2) {
const R = 6371;
const { sin, cos, asin, PI, hypot } = Math;
let getPoint = (e, n) => {
e *= PI / 180;
n *= PI / 180;
return { x: cos(n) * cos(e), y: cos(n) * sin(e), z: sin(n) };
};

let a = getPoint(e1, n1);
let b = getPoint(e2, n2);
let c = hypot(a.x - b.x, a.y - b.y, a.z - b.z);
let r = asin(c / 2) * 2 * R;
return Math.round(r);
}

function showWelcome() {
if (!ipLocation || !ipLocation.data) {
console.error('ipLocation data is not available.');
return;
}

let dist = getDistance(-33.89083, -151.199167, ipLocation.data.lng, ipLocation.data.lat); // 修改自己的经度
let pos = ipLocation.data.country;
let ip = ipLocation.ip;
let posdesc;

switch (ipLocation.data.country) {
case "日本":
posdesc = "よろしく,一起去看樱花吗";
break;
case "美国":
posdesc = "Let us live in peace!";
break;
case "英国":
posdesc = "想同你一起夜乘伦敦眼";
break;
case "俄罗斯":
posdesc = "干了这瓶伏特加!";
break;
case "法国":
posdesc = "C'est La Vie";
break;
case "德国":
posdesc = "Die Zeit verging im Fluge.";
break;
case "澳大利亚":
posdesc = "一起去大堡礁吧!";
break;
case "加拿大":
posdesc = "拾起一片枫叶赠予你";
break;
case "中国":
pos = ipLocation.data.prov + " " + ipLocation.data.city + " " + ipLocation.data.district;
switch (ipLocation.data.prov) {
case "北京市":
posdesc = "北——京——欢迎你~~~";
break;
case "上海市":
posdesc = "走在外滩,感受历史与现代的交融。";
break;
case "广东省":
switch (ipLocation.data.city) {
case "广州市":
posdesc = "看小蛮腰,喝早茶了嘛~";
break;
case "深圳市":
posdesc = "今天你逛商场了嘛~";
break;
default:
posdesc = "带你感受广东的热情与美食!";
break;
}
break;
case "浙江省":
switch (ipLocation.data.city) {
case "杭州市":
posdesc = "西湖美景,三月天~";
break;
case "宁波市":
posdesc = "来宁波,感受大海的气息。";
break;
default:
posdesc = "这里是浙江,充满江南的韵味!";
break;
}
break;
case "四川省":
switch (ipLocation.data.city) {
case "成都市":
posdesc = "宽窄巷子,成都慢生活。";
break;
case "绵阳市":
posdesc = "享受科技城的宁静与创新。";
break;
default:
posdesc = "来四川,品麻辣火锅,赏壮丽山河。";
break;
}
break;
case "福建省":
switch (ipLocation.data.city) {
case "厦门市":
posdesc = "鼓浪屿听海,厦门美食让人流连忘返。";
break;
case "福州市":
posdesc = "有福之州,来此感受千年古城。";
break;
default:
posdesc = "福建山水如画,美景无处不在。";
break;
}
break;
case "山东省":
switch (ipLocation.data.city) {
case "青岛市":
posdesc = "来青岛喝啤酒,看大海吧!";
break;
case "济南市":
posdesc = "泉城济南,四面荷花三面柳。";
break;
default:
posdesc = "山东好客,欢迎来感受齐鲁文化!";
break;
}
break;
case "江苏省":
switch (ipLocation.data.city) {
case "南京市":
posdesc = "六朝古都南京,历史与现代的碰撞。";
break;
case "苏州市":
posdesc = "来苏州,感受园林之美。";
break;
default:
posdesc = "水乡泽国,江南佳丽地。";
break;
}
break;
case "河北省":
posdesc = "燕赵大地,英雄辈出的河北,等你探索!";
break;
case "河南省":
switch (ipLocation.data.city) {
case "郑州市":
posdesc = "中原大地,郑州是交通枢纽与历史重镇。";
break;
case "洛阳市":
posdesc = "千年古都洛阳,牡丹花开的城市。";
break;
default:
posdesc = "这里是河南,历史悠久文化灿烂。";
break;
}
break;
case "湖南省":
switch (ipLocation.data.city) {
case "长沙市":
posdesc = "热辣长沙,吃小龙虾逛黄兴路步行街。";
break;
default:
posdesc = "湖南,烟雨迷蒙的湘江流过这片土地。";
break;
}
break;
case "湖北省":
switch (ipLocation.data.city) {
case "武汉市":
posdesc = "来大武汉,过长江大桥,吃热干面!";
break;
default:
posdesc = "湖北,长江中游的明珠,风景秀丽。";
break;
}
break;
case "安徽省":
switch (ipLocation.data.city) {
case "合肥市":
posdesc = "创新之城合肥,科教文化汇聚地。";
break;
default:
posdesc = "安徽山水,黄山、九华山欢迎你。";
break;
}
break;
case "广西壮族自治区":
switch (ipLocation.data.city) {
case "桂林市":
posdesc = "桂林山水甲天下,风景如画。";
break;
case "南宁市":
posdesc = "绿城南宁,宜居宜游。";
break;
default:
posdesc = "广西山清水秀,民俗风情浓郁。";
break;
}
break;
case "贵州省":
posdesc = "来贵州,品茅台,赏黄果树瀑布。";
break;
case "云南省":
switch (ipLocation.data.city) {
case "昆明市":
posdesc = "春城昆明,四季如春,风景秀丽。";
break;
case "大理市":
posdesc = "苍山洱海,大理古城,你来了就不想走。";
break;
default:
posdesc = "云南风景独特,风情万种。";
break;
}
break;
case "西藏自治区":
posdesc = "世界屋脊西藏,神秘而纯净。";
break;
case "新疆维吾尔自治区":
posdesc = "辽阔新疆,民族风情与壮丽景观并存。";
break;
case "内蒙古自治区":
posdesc = "草原辽阔的内蒙古,等你来策马奔腾。";
break;
case "宁夏回族自治区":
posdesc = "宁夏,塞上江南,黄河流经的美丽地方。";
break;
case "海南省":
posdesc = "阳光、沙滩、椰风海韵,欢迎来海南度假。";
break;
default:
posdesc = "带我去你的城市逛逛吧!";
break;
}
break;
default:
posdesc = "带我去你的国家逛逛吧";
break;
}


let welcomeInfoElement = document.getElementById("welcome-info");

if (welcomeInfoElement) {
welcomeInfoElement.innerHTML =
`欢迎来自 <b><span style="color: var(--efu-main)">${pos}</span></b> 的朋友!💖<br>当前位置距我约 <b><span style="color: var(--efu-main)">${dist.toFixed(2)}</span></b> 公里!<br>我这(?)还蛮大的(bushi),欢迎来我这玩~<br>Tip:<b><span style="font-size: 15px;">${posdesc}</span></b>`;
}
}

function handlePjaxComplete() {
if (isHomePage()) {
showWelcome();
}
}

function isHomePage() {
return window.location.pathname === '/' || window.location.pathname === '/index.html';
}

window.onload = function () {
if (isHomePage()) {
showWelcome();
}
document.addEventListener("pjax:complete", handlePjaxComplete);
}

修改_config.solitude.yml,home 的边栏添加刚才的 visinfo:

1
2
3
4
aside:
home: # on the homepage
noSticky: "about,visinfo"
Sticky: "newestPost,allInfo"

最后引入js即可:

1
2
3
extends:
body:
- <script defer pjax src="/js/visinfo.js"></script>

8.底部添加自定义图片

此条目由青桔的方案修改而来。

给你的博客底部添加一排宠物 - 青桔气球

/themes/solitude/layout/includes/footer.pug加入:

1
2
3
4
5
6
7
8
div#footer-image
img.image.entered.loaded(
src="/img/sample.png" // 修改为你图片的地址
onerror="this.onerror=null;this.src='/img/404.jpg';"
data-lazy-src="/img/sample.png" // 修改为你图片的地址
alt="Photo"
data-ll-status="loaded"
)

/themes/solitude/source/css/custom.css中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#footer-image {
position: relative;
width: 100%;
}

@media screen and (max-width: 1023px) {
#footer-image img.image {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
max-width: 100%;
height: auto;
margin: 0 auto;
display: block;
opacity: 0.9;
object-fit: cover;
}
}

@media screen and (min-width: 1024px) {
#footer-image img.image {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 30%;
max-width: 100%;
height: auto;
opacity: 0.9;
}
}

#footer-bar {
margin-top: 0 !important;
}

9.导航栏模糊效果

/themes/solitude/source/css/custom.css中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[data-theme=light] #nav .menus_items .menus_item .menus_item_child {
background-color: #ffffff4f!important;
backdrop-filter: blur(7px)!important;
-webkit-backdrop-filter: blur(7px)!important
}

[data-theme=dark] #nav .menus_items .menus_item .menus_item_child {
background-color: #1d1e224f!important;
backdrop-filter: blur(7px)!important;
-webkit-backdrop-filter: blur(7px)!important
}

[data-theme=dark] #page-header.nav-fixed #nav {
background: #0000006f!important
}

[data-theme=light] #page-header.nav-fixed #nav {
background: #ffffff4f!important;
}

#page-header.nav-fixed #nav {
outline: 1px #7c7c7c43 solid!important;
backdrop-filter: blur(7px)!important;
-webkit-backdrop-filter: blur(7px)!important
}

@media screen and (max-width: 768px) {
#nav {
background:#fff0!important
}
}

@media screen and (max-width: 768px) {
body[data-type=post] #page-header #nav {
--font-color:#fff
}
}

@media screen and (max-width: 768px) {
#page-header.nav-fixed #nav {
--font-color: var(--efu-fontcolor)!important;
}
}

[data-theme=dark] #page-header.nav-fixed #nav {
background: #1d1e224f!important;
backdrop-filter: blur(7px)!important;
-webkit-backdrop-filter: blur(7px)!important
}

10.自定义鼠标指针

新建/themes/solitude/source/cur文件夹,然后把你的鼠标指针图片放进去。(cur or png)
/themes/solitude/source/css/custom.css中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
html, body {
cursor: url('/cur/nor.cur'), auto !important;
}

a, button, .clickable {
cursor: url('/cur/link_select.cur'), pointer !important;
}

textarea, input, select {
cursor: url('/cur/text_select.cur'), text !important;
}

* {
cursor: inherit !important;
}

/themes/solitude/source/js/custom.js中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
document.addEventListener('DOMContentLoaded', function () {
document.body.style.cursor = 'url("/cur/nor.cur"), auto';

document.querySelectorAll('a, button, .clickable').forEach(element => {
element.addEventListener('mouseenter', function () {
document.body.style.cursor = 'url("/cur/link_select.cur"), pointer';
});
element.addEventListener('mouseleave', function () {
document.body.style.cursor = 'url("/cur/nor.cur"), auto';
});
});

document.querySelectorAll('textarea, input, select').forEach(element => {
element.addEventListener('mouseenter', function () {
document.body.style.cursor = 'url("/cur/text_select.cur"), text';
});
element.addEventListener('mouseleave', function () {
document.body.style.cursor = 'url("/cur/nor.cur"), auto';
});
});
});

注意
请自行修改鼠标指针的名称。当然你要是指针名字一样当我没说。

引入js:

1
2
3
extends:
body:
- <script src="/js/custom.js"></script>

11.修改纯色背景

即当background: false时的全局背景。
/themes/solitude/source/css/custom.css中加入:

1
2
3
4
5
[data-theme="dark"] {
--efu-background: #342024;
--efu-card-bg: #342024;
--efu-secondbg: #342024;
}

这样会将预设中的背景色覆盖,
效果并不如本站所示。(滑稽)

12.修改页面/操作提示词

与以下文件有关:
/themes/solitude/languages/default.yml
/themes/solitude/languages/zh-CN.yml
/themes/solitude/languages/en.yml
/themes/solitude/source/tw_cn.js

13.链接卡

注意
进行此修改会导致 Fancybox 图片灯箱无法正常使用。

链接卡样式参考了Ariasakaの小窝
Ariasakaの小窝

/themes/solitude/source/css/custom.css中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
.efu-link {
display: grid;
margin: 10px;
background: var(--efu-hltools-bg);
border-radius: 10px;
grid-template-rows: 28px min-content;
grid-template-columns: 80px calc(100% - 80px);
height: 100px;
padding: 5px;
transition: .2s;
-webkit-transition: .3s;
-moz-transition: .3s;
-ms-transition: .3s;
-o-transition: .3s;
position: relative
}

.efu-link:after {
content: "";
top: 50%;
position: absolute;
right: 15px;
width: 16px;
height: 16px;
background-color: var(--efu-fontcolor);
-webkit-mask-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB0PSIxNzI1Mjk1MDA0Njc5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE2NTciIGlkPSJteF9uXzE3MjUyOTUwMDQ2NzkiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTgxNi42NTMwOTMgNDY5LjU0NDA0OWMtMS40OTUwNS0xLjQ5NTA1LTMuMDYwNzA5LTIuODc0NDY3LTQuNjc2NTA5LTQuMTU5NzRMNDI4LjQ3NTE5IDc5Ljg5NTY1NWMtMjEuMzUxMjg0LTIxLjM1MDI2MS01NS45Njc2ODYtMjEuMzUwMjYxLTc3LjMxNzk0NiAwLTIxLjM1MDI2MSAyMS4zNTEyODQtMjEuMzUwMjYxIDU1Ljk2NjY2MyAwIDc3LjMxNzk0NmwzNDkuMjU5NTIyIDM1MS4yNDc4MDZMMzQzLjY1NjQwOSA4NjQuODgzMDQ5Yy0yMS4zNTAyNjEgMjEuMzUxMjg0LTIxLjM1MDI2MSA1NS45NjY2NjMgMCA3Ny4zMTg5NyAyMS4zNTAyNjEgMjEuMzUwMjYxIDU1Ljk2NjY2MyAyMS4zNTAyNjEgNzcuMzE3OTQ2IDBsMzkwLjk5ODEzNS0zOTAuNjYxNDY4YzEuNjE2ODI0LTEuMjg2Mjk2IDMuMTgzNTA2LTIuNjY0Njg5IDQuNjc5NTc5LTQuMTYwNzYzIDEwLjc0MDYyMi0xMC43NDA2MjIgMTYuMDczMDctMjQuODM5NzM1IDE2LjAwNjU1Ni0zOC45MTczNThDODMyLjcyNjE2NCA0OTQuMzgyNzYgODI3LjM5MzcxNSA0ODAuMjg0NjcxIDgxNi42NTMwOTMgNDY5LjU0NDA0OXoiIGZpbGw9InZhcigtLWFyaWEtZm9udGNvbG9yKSIgcC1pZD0iMTY1OCI+PC9wYXRoPjwvc3ZnPg==);
mask-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB0PSIxNzI1Mjk1MDA0Njc5IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE2NTciIGlkPSJteF9uXzE3MjUyOTUwMDQ2NzkiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTgxNi42NTMwOTMgNDY5LjU0NDA0OWMtMS40OTUwNS0xLjQ5NTA1LTMuMDYwNzA5LTIuODc0NDY3LTQuNjc2NTA5LTQuMTU5NzRMNDI4LjQ3NTE5IDc5Ljg5NTY1NWMtMjEuMzUxMjg0LTIxLjM1MDI2MS01NS45Njc2ODYtMjEuMzUwMjYxLTc3LjMxNzk0NiAwLTIxLjM1MDI2MSAyMS4zNTEyODQtMjEuMzUwMjYxIDU1Ljk2NjY2MyAwIDc3LjMxNzk0NmwzNDkuMjU5NTIyIDM1MS4yNDc4MDZMMzQzLjY1NjQwOSA4NjQuODgzMDQ5Yy0yMS4zNTAyNjEgMjEuMzUxMjg0LTIxLjM1MDI2MSA1NS45NjY2NjMgMCA3Ny4zMTg5NyAyMS4zNTAyNjEgMjEuMzUwMjYxIDU1Ljk2NjY2MyAyMS4zNTAyNjEgNzcuMzE3OTQ2IDBsMzkwLjk5ODEzNS0zOTAuNjYxNDY4YzEuNjE2ODI0LTEuMjg2Mjk2IDMuMTgzNTA2LTIuNjY0Njg5IDQuNjc5NTc5LTQuMTYwNzYzIDEwLjc0MDYyMi0xMC43NDA2MjIgMTYuMDczMDctMjQuODM5NzM1IDE2LjAwNjU1Ni0zOC45MTczNThDODMyLjcyNjE2NCA0OTQuMzgyNzYgODI3LjM5MzcxNSA0ODAuMjg0NjcxIDgxNi42NTMwOTMgNDY5LjU0NDA0OXoiIGZpbGw9InZhcigtLWFyaWEtZm9udGNvbG9yKSIgcC1pZD0iMTY1OCI+PC9wYXRoPjwvc3ZnPg==)
}

.efu-link-tip {
grid-column-start: 1;
grid-column-end: 3;
grid-row: 1;
display: inline-flex;
white-space: nowrap;
font-size: 12px;
line-height: 1.5;
color: var(--efu-fontcolor);
margin: 0 15px;
padding-top: 5px;
border-bottom: var(--efu-code-background)
}

.efu-link-subtitle {
grid-column: 2;
grid-row: 3
}

.efu-link-img {
grid-column: 1;
grid-row-start: 2;
grid-row-end: 4;
margin: auto;
border-radius: 50%;
width: 60px;
height: 60px;
object-fit: cover
}

.efu-link-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.efu-link-subtitle {
font-size: 12px;
color: var(--efu-fontcolor)
}

.efu-link:hover {
background-color: var(--efu-theme);
box-shadow: 0 0 8px var(--efu-theme);
border: 1px solid var(--efu-theme)
}

.efu-link:hover * {
color: #fff
}

.efu-link:hover:after {
background-color: #fff
}

.article-container img:not(.post_bg) {
border-radius: 12px;
object-fit: cover;
display: block;
margin: auto;
max-width: 90%;
max-height: 450px;
}

新建/themes/solitude/source/js/link-decorator.js,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function decorateLinks() {
const postContainer = document.querySelector('.post-content');
if (!postContainer) return;

const links = postContainer.querySelectorAll('a[href]');
links.forEach(link => {
const href = link.getAttribute('href');
const title = link.innerText.trim();

if (href.startsWith('http') && !href.includes(window.location.hostname)) {
const card = document.createElement('div');
card.classList.add('efu-link');
card.style.cursor = 'pointer';

const url = new URL(href);
const domain = url.hostname.replace(/^www\./, '');

card.innerHTML = `
<div class="efu-link-img">
<img src="https://s1.imagehub.cc/images/2024/12/02/632726f0a6dadcc20df6371b774148db.png" alt="Link Thumbnail">
</div>
<div class="efu-link-tip">这是一个外部链接,不能保证安全性。</div>
<div class="efu-link-title">${title}</div>
<div class="efu-link-subtitle">${domain}</div>
`;

card.addEventListener('click', () => {
window.open(href, '_blank');
});

link.replaceWith(card);
}
});
}

setTimeout(() => {
document.addEventListener('pjax:complete', decorateLinks);
}, 1000);

document.addEventListener('DOMContentLoaded', function () {
decorateLinks();
document.addEventListener('pjax:complete', decorateLinks);
});

最后引入一下 js 就大功告成!

1
2
3
extends:
body:
- <script defer pjax src="/js/link-decorator.js"></script>

14.加个聊天气泡

Hexo 根目录下新建一个scripts 文件夹,里面存放chat.js,内容如下:

1
2
3
4
5
6
7
8
9
10
hexo.extend.tag.register('chat', function(args, content) {
const [role, nickname] = args;
const text = hexo.render.renderSync({ text: content, engine: 'markdown' });
return `
<div class="chat-item ${role}">
<div class="chat-nickname">${nickname}</div>
<div class="chat-bubble">${text}</div>
</div>
`;
}, {ends: true});

/themes/solitude/source/css/custom.css中加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
[data-theme="dark"] {
--efu-chat-background: #4b3539;
--efu-chat-a-background: #2b3a5c;
}

[data-theme="light"] {
--efu-chat-background: #332c36;
--efu-chat-a-background: #f7f9fe;
}

.article-post-content-chat-container {
margin: 0;
}

.chat-item {
display: flex;
margin-bottom: 0rem;
position: relative;
}

.chat-item.user {
justify-content: flex-end;
margin-bottom: 0rem
}

.chat-item.assistant {
justify-content: flex-start;
margin-bottom: 0rem
}

.chat-bubble {
padding: 0px 12px;
border-radius: 10px;
max-width: 70%;
max-height: 50%;
word-wrap: break-word;
word-break: break-word;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.chat-item.user .chat-bubble {
background-color: var(--efu-chat-background);
text-align: left;
color: #ffffff;
margin-top: 15px;
}

.chat-item.assistant .chat-bubble {
background-color: var(--efu-chat-a-background);
text-align: left;
color: var(--efu-fontcolor);
margin-top: 15px;
}
.chat-item.user .chat-nickname {
position: absolute;
top: -18px;
right: 0;
font-size: 12px;
font-weight: bold;
color: var(--efu-fontcolor);
background-color: var(--efu-background);
padding: 2px 5px;
margin-top: 15px;
border-radius: 5px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
}

.chat-item.assistant .chat-nickname {
position: absolute;
top: -18px;
left: 0;
font-size: 12px;
font-weight: bold;
color: var(--efu-fontcolor);
background-color: var(--efu-background);
padding: 2px 5px;
margin-top: 15px;
border-radius: 5px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
}

这样就完成了,如果需要在Markdown中引用,这么写就可以:

1
2
3
4
5
6
7
{% chat assistant "English" %}
Hello, world!
{% endchat %}

{% chat user "简体中文" %}
你好,世界!
{% endchat %}

效果:

English

Hello, world!

简体中文

你好,世界!

15.安卓样式加载条

找到/themes/solitude/source/css/layout/pace.styl,直接全部替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
if hexo-config('loading.pace')
.pace
pointer-events: none
user-select: none
z-index: 2000
position: fixed
top: 55px
left: 0
right: 0
height: 4px
background: var(--efu-loader)
overflow: hidden

&.pace-inactive
opacity: 0
transition: .3s

.pace-progress
opacity: 0
transition: .3s ease-in

.pace-progress
box-sizing: border-box
transform: translate3d(0, 0, 0)
z-index: 2000
display: block
position: absolute
top: 0
left: 0
height: 100%
width: 0
background: var(--efu-theme)
animation: android-loader 2s ease infinite
background-size: 200%

@keyframes android-loader
0% {
width: 0;
}
50% {
width: 50%;
}
100% {
width: 100%;
}

Troubleshoot

如果遇到样式不生效不正常不显示的问题,一般可以通过清除浏览器缓存解决。
如果还是不行,那就检查一下修改有没有问题,或者有没有保存吧…

先更到这吧,如果还想改什么就接着更新…