Explorer's Guide

Build and Host

This site uses HEXO to build and generate static website files,~~ hosted in the Plesk panel created by the cloud server.~~

~~I configured a webhook in Plesk. When I submit changes to my Github private repository, Actions is triggered to build the website, and the changes will be automatically synchronized to the Plesk website folder for update. ~~

Update 2024/02/06: Because the virtual host was attacked by DDoS many times, I changed the website hosting to CloudFlare Pages.

Why HEXO?

Many years ago when I was a college student, it was cool to tinker with VPS and build websites. At that time, I used BandWagon Host and installed WordPress with NGinx, PHP, MySQL and other supporting software to build my own personal blog. At that time, I also experienced the database being ransomed and lost several articles I wrote (actually, they were nothing, very basic content).

The point is that database-dependent blogs like WordPress require a lot of maintenance. In contrast, statically generated blogs like HEXO and HUGO only need to worry about their own blog article files. At the same time, the Markdown format allows writing and recording at any time using different devices, without any migration burden.

After switching to HEXO, the entire blog site is hosted on GitHub, and new website files can be easily generated automatically through its Actions without worrying about loss.

Personalization

Theme

Updated: 2025/01/18 hand made theme

built with Icarus

Using desktop browsers can provide the best reading experience.

Cover Images

Some of them are from Unsplash, and some are SVG files that I drew and exported myself using Figma. Vector graphics are very clear and very small. After using Figma, it is easier to organize the materials.

Fonts

This site uses a total of 3 open source/free fonts:

  1. Source Serif
    The quick brown fox jumps over the lazy dog
  2. Source Han Serif CN
    The quick brown fox jumps over the lazy dog 敏捷的棕色狐狸跳过了懒狗
  3. LXGWWenKai
    The quick brown fox jumps over the lazy dog 敏捷的棕色狐狸跳过了懒狗

And the system fonts:

font-family
1
2
font-family: SF Pro Text, SF Pro Icons, Helvetica Neue, Helvetica, Arial,
sans-serif;

The font file is simplified by the script collecting all the used characters to increase the loading speed.

generate-font-files.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
const fs = require("fs");
const _ = require("lodash");
const Fontmin = require("fontmin");

const directory = "source/_posts";
const scriptDirectory = "custom-scripts/font";
const fontFileDirectory = `${scriptDirectory}/files`;

const files = fs.readdirSync(directory).filter((item) => item !== ".DS_Store");
const fonts = fs
.readdirSync(fontFileDirectory)
.filter((item) => item !== "index.js");

const getLetters = () => {
const letterMap = {};

_.forEach(files, (file) => {
const fileContent = fs.readFileSync(`${directory}/${file}`, "utf8");
const letters = _.toArray(fileContent);
_.forEach(letters, (letter) => {
if (!letterMap[letter]) {
letterMap[letter] = 1;
}
});
});
return Object.keys(letterMap).join("");
};

const currentLetters = getLetters();

const lettersText = fs.existsSync(`${scriptDirectory}/letters.txt`)
? fs.readFileSync(`${scriptDirectory}/letters.txt`, "utf8")
: "";

if (currentLetters === lettersText) {
return;
}

fs.writeFileSync(`${scriptDirectory}/letters.txt`, getLetters());

const generateFontFile = (letters) => {
_.forEach(fonts, (font) => {
const task = new Fontmin()
.src(`${fontFileDirectory}/${font}`)
.dest("themes/icarus/source/fonts")
.use(
Fontmin.glyph({
text: letters,
hinting: false,
})
)
.use(Fontmin.ttf2woff2());

task.run(function (err, files) {
if (err) {
throw err;
}
});
});
};

generateFontFile(currentLetters);

Plugins

Lazy-load

I use hexo-lazyload-element to process lazy loading of images, videos, and iframe resources to improve page performance while avoiding unnecessary traffic consumption.

WebP Images

~~Some images are uploaded to Qiniu Cloud and use the ?imageMogr2/format/webp parameter to load WebP image format resources. However, for some devices (iOS13 and below, MacOS Mojave and below), compatibility is required. ~~

Update 2024/02/22: The image hosting service has been changed, and the entire site no longer uses Qiniu Cloud resources.

checkwebp.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
/*! modernizr 3.6.0 (Custom Build) | MIT *
* https://modernizr.com/download/?-webp-setclasses !*/
!(function (e, n, A) {
function o(e) {
var n = u.className,
A = Modernizr._config.classPrefix || "";
if ((c && (n = n.baseVal), Modernizr._config.enableJSClass)) {
var o = new RegExp("(^|\\s)" + A + "no-js(\\s|$)");
n = n.replace(o, "$1" + A + "js$2");
}
Modernizr._config.enableClasses &&
((n += " " + A + e.join(" " + A)),
c ? (u.className.baseVal = n) : (u.className = n));
}
function t(e, n) {
return typeof e === n;
}
function a() {
var e, n, A, o, a, i, l;
for (var f in r)
if (r.hasOwnProperty(f)) {
if (
((e = []),
(n = r[f]),
n.name &&
(e.push(n.name.toLowerCase()),
n.options && n.options.aliases && n.options.aliases.length))
)
for (A = 0; A < n.options.aliases.length; A++)
e.push(n.options.aliases[A].toLowerCase());
for (o = t(n.fn, "function") ? n.fn() : n.fn, a = 0; a < e.length; a++)
(i = e[a]),
(l = i.split(".")),
1 === l.length
? (Modernizr[l[0]] = o)
: (!Modernizr[l[0]] ||
Modernizr[l[0]] instanceof Boolean ||
(Modernizr[l[0]] = new Boolean(Modernizr[l[0]])),
(Modernizr[l[0]][l[1]] = o)),
s.push((o ? "" : "no-") + l.join("-"));
}
}
function i(e, n) {
if ("object" == typeof e) for (var A in e) f(e, A) && i(A, e[A]);
else {
e = e.toLowerCase();
var t = e.split("."),
a = Modernizr[t[0]];
if ((2 == t.length && (a = a[t[1]]), "undefined" != typeof a))
return Modernizr;
(n = "function" == typeof n ? n() : n),
1 == t.length
? (Modernizr[t[0]] = n)
: (!Modernizr[t[0]] ||
Modernizr[t[0]] instanceof Boolean ||
(Modernizr[t[0]] = new Boolean(Modernizr[t[0]])),
(Modernizr[t[0]][t[1]] = n)),
o([(n && 0 != n ? "" : "no-") + t.join("-")]),
Modernizr._trigger(e, n);
}
return Modernizr;
}
var s = [],
r = [],
l = {
_version: "3.6.0",
_config: {
classPrefix: "",
enableClasses: !0,
enableJSClass: !0,
usePrefixes: !0,
},
_q: [],
on: function (e, n) {
var A = this;
setTimeout(function () {
n(A[e]);
}, 0);
},
addTest: function (e, n, A) {
r.push({ name: e, fn: n, options: A });
},
addAsyncTest: function (e) {
r.push({ name: null, fn: e });
},
},
Modernizr = function () {};
(Modernizr.prototype = l), (Modernizr = new Modernizr());
var f,
u = n.documentElement,
c = "svg" === u.nodeName.toLowerCase();
!(function () {
var e = {}.hasOwnProperty;
f =
t(e, "undefined") || t(e.call, "undefined")
? function (e, n) {
return n in e && t(e.constructor.prototype[n], "undefined");
}
: function (n, A) {
return e.call(n, A);
};
})(),
(l._l = {}),
(l.on = function (e, n) {
this._l[e] || (this._l[e] = []),
this._l[e].push(n),
Modernizr.hasOwnProperty(e) &&
setTimeout(function () {
Modernizr._trigger(e, Modernizr[e]);
}, 0);
}),
(l._trigger = function (e, n) {
if (this._l[e]) {
var A = this._l[e];
setTimeout(function () {
var e, o;
for (e = 0; e < A.length; e++) (o = A[e])(n);
}, 0),
delete this._l[e];
}
}),
Modernizr._q.push(function () {
l.addTest = i;
}),
Modernizr.addAsyncTest(function () {
function e(e, n, A) {
function o(n) {
var o = n && "load" === n.type ? 1 == t.width : !1,
a = "webp" === e;
i(e, a && o ? new Boolean(o) : o), A && A(n);
}
var t = new Image();
(t.onerror = o), (t.onload = o), (t.src = n);
}
var n = [
{
uri: "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=",
name: "webp",
},
{
uri: "data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==",
name: "webp.alpha",
},
{
uri: "data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA",
name: "webp.animation",
},
{
uri: "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=",
name: "webp.lossless",
},
],
A = n.shift();
e(A.name, A.uri, function (A) {
if (A && "load" === A.type)
for (var o = 0; o < n.length; o++) e(n[o].name, n[o].uri);
});
}),
a(),
o(s),
delete l.addTest,
delete l.addAsyncTest;
for (var p = 0; p < Modernizr._q.length; p++) Modernizr._q[p]();
e.Modernizr = Modernizr;
})(window, document);

Modernizr.on("webp", function (result) {
if (result) {
// supported
} else {
// not-supported
const webpImages = document.querySelectorAll("img");
Array.prototype.filter
.call(
webpImages,
(item) =>
item.getAttribute("src") &&
item.getAttribute("src").includes("")
)
.forEach((img) => {
const currentSrc = img.getAttribute("src");
img.src = currentSrc.replace("?imageMogr2/format/webp", "");
});

const galleryItems = document.querySelectorAll(".gallery-item");
Array.prototype.filter
.call(
galleryItems,
(item) =>
item.getAttribute("href") &&
item.getAttribute("href").includes("")
)
.forEach((img) => {
const currentSrc = img.getAttribute("href");
img.href = currentSrc.replace("?imageMogr2/format/webp", "");
});

const lazyloadItems = document.querySelectorAll(".lazyload-wrap");
Array.prototype.filter
.call(
lazyloadItems,
(item) =>
item.dataset &&
item.dataset.content.includes("%3FimageMogr2%2Fformat%2Fwebp")
)
.forEach((item) => {
const currentSrc = item.dataset.content;
item.dataset.content = currentSrc.replace(
"?%3FimageMogr2%2Fformat%2Fwebp",
""
);
});
}
});

RSS

Use hexo-generator-feed to generate /feed.xml for RSS reader subscription.

You can subscribe to the content using the following address.

RSS
1
https://thelynan.com/feed.xml

Comments

use Gitalk

~~Use Google AdSense, in the sidebar widget (at the bottom of the article for small screen devices), which has little impact on reading. It would be great if it could cover my OSS fees. ~~

Sometimes full-screen ads pop up, which is not a good experience, so I turned it off

Some Hidden Features

iframe

The page and iframe use the postMessage method to communicate, enriching the functionality of static pages.