diff --git a/2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/index.html b/2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/index.html new file mode 100644 index 0000000..034a124 --- /dev/null +++ b/2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/index.html @@ -0,0 +1,717 @@ + + + + + + + + + + + + 我的世界自定义配方脚本生成器 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 我的世界自定义配方脚本生成器 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

前言

在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣!

+

想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。

+

模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性

+

和平模式:没有难度、没有敌对势力生成、玩家受到非致死伤害时自动恢复生命

+

怪物的凋落物:一般情况下能够获得怪物的凋落物,意味着玩家困难度设置的是“简单、中等、困难”

+

工具介绍

这个工具名为我的世界自定义配方脚本生成器,它的作用是向游戏中添加新的工作台配方,在和平难度下获得的物品来合成非和平难度下的物品。可以不用手写代码工具会自动生成,只需要你选择生成前后的配方即可。

+

工具界面

+

基于CraftTweaker手动生成配方

CraftTweaker 允许你使用简单的脚本语言来自定义你的整合包或服务器,你可以添加或删除配方、更改配方、更改名称、更改熔炉配方、矿物词典、工具描述等。代码和说明必须是英文

+

手动写代码自定义几个配方还是很容易的,如果想完整实现我的设想就需要大量的重复性操作来解决这种问题。

+
1
2
3
4
5
6
7
8
9
10
11
// 龙蛋: 黑曜石围绕着鸡蛋, 最坚硬的蛋
craftingTable.addShapeless("StringOD_LongDan", <item:minecraft:dragon_egg> * 1, [
<item:minecraft:obsidian>, <item:minecraft:obsidian>, <item:minecraft:obsidian>,
<item:minecraft:obsidian>, <item:minecraft:egg>, <item:minecraft:obsidian>,
<item:minecraft:obsidian>, <item:minecraft:obsidian>, <item:minecraft:obsidian>]);

// 刷怪箱: 铁栅栏围绕着箱子, 网格一样的箱子
craftingTable.addShapeless("StringOD_ShuaGuaiXiang", <item:minecraft:spawner> * 1, [
<item:minecraft:iron_bars>, <item:minecraft:iron_bars>, <item:minecraft:iron_bars>,
<item:minecraft:iron_bars>, <item:minecraft:chest>, <item:minecraft:iron_bars>,
<item:minecraft:iron_bars>, <item:minecraft:iron_bars>, <item:minecraft:iron_bars>]);
+ + + +

自动生成配方工具设计思路

AutoSpawnZScript思路

+

工具界面演化

AutoSpawnZScript - v0.1

+

AutoSpawnZScript_v0.2

+

AutoSpawnZScript - v0.3

+

AutoSpawnZScript - v0.4

+

AutoSpawnZScript - v0.5

+

AutoSpawnZScript - v0.8

+

AutoSpawnZScript - v0.9

+

AutoSpawnZScriptEToJava - v1.0

+

下载地址

目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/index.html b/2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/index.html new file mode 100644 index 0000000..e468119 --- /dev/null +++ b/2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/index.html @@ -0,0 +1,877 @@ + + + + + + + + + + + + 文明六自定义市中心区域建筑模组 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 文明六自定义市中心区域建筑模组 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

前言

在文明 6 的创意工坊中订阅了两个功能极为相似的模组,感觉这两个模组的代码应该差不多,于是打算分析完成这两个模组就自己写一个.

+

文明六Sid Meier's Civilization VI 是一款回合制策略游戏,让玩家尝试建立起一个帝国,并接受时间的考验.玩家将创建及带领自己的文明从石器时代迈向信息时代,并成为世界的领导者.

+

模组 Modification 缩写为 Mod ,简体中文翻译为模组,它是游戏的一种修改或增强程序.比如玩家向游戏中添加了游戏本身不存在的物体使游戏更加有趣.

+

创意工坊Steam 游戏平台针对特定游戏提供一个 Mod 下载页面,玩家可以下载自己喜欢的 Mod 来增强自己的游戏体验

+

游戏中开局你将扮演一个领袖在地图上创建你的第一座城市,城市下有很多的区域比如:学院、市场、娱乐区域、圣地区域(市民进行宗教活动的区域)、军营和港口等.

+

市中心区域是城市创建后首先要建造的基础设施用来保障市民的生活.

+

前段时间在 B 站看到了一个文明六的创建一个简单模组的教程,于是乎把压抑已久的想法想要实现一下.

+

但是当打开模组开发工具时,我发现模组开发并不那么容易,跟着这个 B 站教程做了一遍之后遇到了许多困难.过了几个月,也就是前几天,我决定从别人的模组开始入手学习,制作一个属于自己的模组.

+

本文主要讲解这个模组的设计思路,以及如何创建属于自己的模组.

+

模组介绍

这个模组名为StringOD of House ,它的作用是,向文明六任意国家的任意已创建城市的市中心区域下新增一个带有各种加成的建筑物,目前添加的建筑为 “少年的出租屋

+

它的加成和模组信息如下图:

+

资源加成表

+

Mod 信息

+

建筑信息

+

下载地址

如果你不想学习创建属于自己的 Mod 你可以直接下载并体验它【目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我】

+

模组的存放路径为 %userprofile%\Documents\My Games\Sid Meier's Civilization VI\Mods,你需要解压缩把StringOD 文件夹放在 Mods 目录下.

+

如果你想学习创建属于自己的 Mod 并向游戏中添加自己更多的想法,请耐心的花费十分钟的时间看完这篇文章.

+

参考链接

+

搭建环境

    +
  1. Steam 中打开
  2. +
  3. 点击 下面的 游戏 下拉框,勾选 工具
    +

    库 下面的 主页 下面的

    +
    +
  4. +
  5. 安装 Sid Meier's Civilization VI Development Tools
  6. +
  7. 启动之后选择 ModBuddy
  8. +
+

至此,环境搭建完成

+

创建你的第一个建筑模组

这个时候你应该能看到 Start Page 的页面.

+
    +
  1. 创建新的模组选择 New Mod
  2. +
  3. 更改下面的模组名称 Name 最好是英文
  4. +
  5. 更改模组的存储位置 Location (当然可以不更改)
  6. +
  7. 选择右小角的 OK
  8. +
+

至此,当你看到一个英文的 Starter Project 页面时说明你的第一个模组创建成功.

+

开始分析别人的模组

模组介绍

这是上文提到的两个相似的模组,他们都在市中心区域添加了单独的建筑:

+
    +
  • God's House - Cheat Mod 针对当前城市修改了城市的产量,这其中包含:黄金,信仰,文化,科技,食物,生产力,住房以及宜居度.
  • +
  • Player Only Cheat Building 针对当前城市修改了城市的产量,这其中包含:黄金,信仰,文化,科技,食物,生产力,住房,宜居度.战略资源包含了:铁,煤,马,硝石,铝,铀.
  • +
+

这两个模组在每一回合都会生产预先设置好的资源.

+

寻找模组文件

首先,得找到模组的文件路径.我发现从创意工坊下载的模组都是一串数字代替的名字.比如:

+
1
2
1109505115
1122081356
+ +

这个时候我想到每个模组文件中一定会包含模组在游戏中显示的名字或模组的建筑名称.(用已知字符串搜索整个可能存在这些文件的目录下的每个文件中的内容)

+

启动 HBuilderX 打开 Steam 的创意工坊目录 ..\SteamLibrary\steamapps\workshop\content\289070,然后在这个目录下的所有文件中查找 包含有 God's House 字符串的文件,根据搜索结果得到模组的目录名称 1395251854

+

第二个模组的文件路径所使用的搜索字符串是游戏中显示的建筑名称,创意工坊给出的模组名称和模组中显示的名称不一样.最后根据游戏中显示的建筑名称 YoRHa_Core 在整个目录检索到的目录名称为 1956601153

+

至此,得到了两个模组的文件路径.

+

分析文件结构

从文件名称猜测

先对比一下两个模组的文件结构.

+
1
2
3
4
5
6
7
8
9
10
11
12
1956601153 - YoRHa_Core
Blu.png
YoRHaCore.modinfo
YoRHa_Core_Gameplay.xml
YoRHa_Core_Icons.xml
YoRHa_Core_Text.xml

1395251854 - God's House
GodsHouseCheatMod.modinfo
NewBuilding_Gameplay.xml
NewBuilding_Icons.xml
NewBuilding_Text.xml
+ +

可以看出感觉这两个模组差不多,唯一不同的是 YoRHa_Core 目录下多了一个 Blu.png 这是一个图片文件,猜测可能是背景图.

+

从模组的文件名字中能大概猜测到每个文件的作用.

+
    +
  • xxx_Gameplay.xml 应该是整个模组最主要的部分
  • +
  • xxx_Icons.xml 应该是该模组在游戏中显示的图标
  • +
  • xxx_Text.xml 应该是存放文本的文件,比如游戏中显示文字
  • +
  • xxx.modinfo 应该是用来存储当前模组的基本信息文件
  • +
+

为了进一步确定每个文件的作用和功能就得打开文件读代码.

+

打开每个文件读代码

在这里就用 YoRHa_Core 这个 Mod 来解释和翻译.

+

打开文件翻译每对标签的中文翻译.先从最外层往内层翻译.

+
    +
  • xxx_Gameplay.xml 模组的最重要的游戏数据文件
  • +
+
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
<!-- 游戏数据, 整个模组的代码都得写在这个里面 -->
<GameData>

<!-- 建筑类名称和类型 Kind为建筑分类, 一般情况下不用改-->
<Types>
<Row Type="BUILDING_YORHA_CORE" Kind="KIND_BUILDING" />
</Types>

<!-- 建筑成本 附加属性 -->
<Buildings>
<!--
BuildingType 这是建筑类型, 一般的和建筑名称可以是一样的(自己命名)
Name 这是建筑名称
PrereqDistrict="DISTRICT_CITY_CENTER" 表示这个模组中的建筑将添加在市中心区域下
PurchaseYield="YIELD_FAITH" 表示当前模组的建筑建造成本必须拥有的信仰值, 实际上也需要生产力
Cost="10" 表示需要多少信仰值才能建造当前模组中提供的建筑
AdvisorType="ADVISOR_GENERIC" 经过翻译是顾问类型为一般, 具体不知道什么意义, 默认不用改
-->
<Row BuildingType="BUILDING_YORHA_CORE" />
</Buildings>

<!-- 建筑加成 建筑每回合生产的资源 -->
<Building_YieldChanges>
<!-- 建筑类型, 建筑生产资源类型为信仰, 生产数值为1 -->
<Row BuildingType="BUILDING_YORHA_CORE" YieldType="YIELD_FAITH" YieldChange="1"/>
</Building_YieldChanges>

<!-- 修改类型 每个建筑可以被附加各种不同的属性需要提前声明才能被引用和赋值 -->
<Modifiers>
<!--
ModifierId 建筑属性ID, 每种加成都需要一个特殊的名字
ModifierType 建筑引用类型 这个必须调用游戏已有的建筑类型
OwnerRequirementSetId 指定只有玩家可以使用, AI不可以
-->
<Row>
<ModifierId>YORHA_CORE_FAITH</ModifierId>
<ModifierType>MODIFIER_SINGLE_CITY_ADJUST_CITY_YIELD_MODIFIER</ModifierType>
<OwnerRequirementSetId>PLAYER_IS_HUMAN</OwnerRequirementSetId>
</Row>
</Modifiers>

<!-- 修改参数 引用修改类型并重新赋值 -->
<ModifierArguments>
<!-- 这里是主要修改部分, 其他部分可以不用管, 每一对 Value 标签中的数字可以随便改, 这就是城市的生产资源 -->
<!-- 信仰 -->
<Row>
<!-- 引用 Modifiers 中的名称 -->
<ModifierId>YORHA_CORE_FAITH</ModifierId>
<!-- 生产收益 -->
<Name>Amount</Name>
<!-- 生产信仰 116 -->
<Value>116</Value>
</Row>
<Row>
<!-- 引用 Modifiers 中的名称 -->
<ModifierId>YORHA_CORE_FAITH</ModifierId>
<!-- 生产收益类型名称 -->
<Name>YieldType</Name>
<Value>YIELD_FAITH</Value>
</Row>
</ModifierArguments>

<!-- 建筑类型引用 -->
<BuildingModifiers>
<!-- 建筑类型名称 和 建筑ID名称 -->
<Row>
<BuildingType>BUILDING_YORHA_CORE</BuildingType>
<ModifierId>YORHA_CORE_FAITH</ModifierId>
</Row>
</BuildingModifiers>

</GameData>
+ +
    +
  • xxx_Icons.xml 这是一个图标引用文件,主要作用是针对这个模组在生产队列中给建筑提供图标
  • +
+
1
2
3
4
<!-- 图标命名 引用游戏自带图标 26代表工厂建筑图标 -->
<IconDefinitions>
<Row />
</IconDefinitions>
+ +
    +
  • xxx_Text.xml 这是一个语言文件,当你的游戏主要语言更改时模组会选择一个已提供的语言进行显示,如果没有就默认显示英文(模组默认支持的语种)
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 在游戏中显示的各种名称和解释以及描述, 针对不同的语种 -->
<LocalizedText>
<!-- 英语 -->
<Row Tag="LOC_BUILDING_YORHA_CORE" Language="en_US">
<Text>Core of YoRHa Android</Text>
</Row>
<!-- 韩语 -->
<Row Tag="LOC_BUILDING_YORHA_CORE" Language="ko_KR">
<Text>요르하 안드로이드 코어</Text>
</Row>
<!-- 简体中文 -->
<Row Tag="LOC_BUILDING_YORHA_CORE" Language="zh_CN">
<Text>YoRHa的机器人核心</Text>
</Row>
</LocalizedText>
+ +
    +
  • xxx.modinfo 这是一个模组创建完成且在发布时所需要填写的信息文件
  • +
+
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
<!-- 模组唯一ID -->
<Mod id="b630189f-13ab-4eb7-a131-fc4c32625397" version="1">
<!-- 属性 -->
<Properties>
<!-- 模组名称 在构建模组时名称必须为英文, 但是可以用其他文本编辑器更改成中文, 这是我后来汉化的文件 -->
<Name>YoRHa的机器人核心</Name>
<!-- 描述 -->
<Description>
只添加玩家操作建筑。
这个mod是以下mod的集成:
1. 上帝的房子-欺骗Mod由,僵尸马特。
2. 由Surt Ignir Magnis设计的资源生产建筑。
3. 由 阳光下的少年(StringOD) 汉化, 做出大量调整使得玩家在局域网模式下可以更好的发展
</Description>
<!-- 创建编号 -->
<Created>1577944373</Created>
<!-- 概要 -->
<Teaser>
只添加玩家操作建筑。
这个mod是以下mod的集成:
1. 上帝的房子-欺骗Mod由,僵尸马特。
2. 由Surt Ignir Magnis设计的资源生产建筑。
3. 由 阳光下的少年(StringOD) 汉化, 做出大量调整使得玩家在局域网模式下可以更好的发展
</Teaser>
<!-- 作者 -->
<Authors>Unknown Enemy</Authors>
<!-- 感谢名单 -->
<SpecialThanks>L U N A / R \, Zombie Matt, and Surt Ignir Magnis.</SpecialThanks>
<!-- 兼容版本号 -->
<CompatibleVersions>1.2,2.0</CompatibleVersions>
</Properties>
<!-- 依赖关系 -->
<Dependencies>
<!-- 依赖的模组ID和名称 -->
<Mod id="4873eb62-8ccc-4574-b784-dda455e74e68" title="Expansion: Gathering Storm" />
</Dependencies>

<!-- 整个模组所作出的更改将更新到游戏文件中 -->
<InGameActions>
<UpdateDatabase id="Gameplay">
<File>YoRHa_Core_Gameplay.xml</File>
</UpdateDatabase>
<UpdateIcons id="Icons">
<File>YoRHa_Core_Icons.xml</File>
</UpdateIcons>
<UpdateText id="Text">
<File>YoRHa_Core_Text.xml</File>
</UpdateText>
</InGameActions>
<!-- 需要更新的文件 -->
<Files>
<File>YoRHa_Core_Gameplay.xml</File>
<File>YoRHa_Core_Icons.xml</File>
<File>YoRHa_Core_Text.xml</File>
</Files>
</Mod>
+ +

至此,每个文件的作用以及具体写法已经了解了.

+

开始编写模组

至此,我们应该已经创建完成了属于我们的第一个模组.如果你还没有完成创建,你可能需要先完成上一个步骤

+

描述需求

添加一个市中心下的建筑,建造成本为 100 金币,每回合生产3文化值,每个回合建筑生产1个点数艺术家,作家,音乐家工程师,城市每回合产出 100 黄金.

+

编写模组

打开 Solution Explorer 面板时,我们创建的项目下多了两个文件 xxx_Art.xml 直接删掉这个文件,我也不知道这个文件的作用,GettingStarted.html 这个也可以删掉这是一个帮助页面.为了学习别人的模组最好和别人保持一致.

+

你可能会感到疑惑,为什么没有出现 xxx.modinfo 文件?

+

我后来生成模组时发现,这是因为这个文件是在后来构建或者运行模组时编译器自动生成的.

+

xxx_Gameplay.xml 文件代码编写

直接看代码,先把英文注释翻译为中文.

+
1
2
3
4
5
6
7
8
9
10
<GameData>
<!-- 几乎所有的游戏类型都应该首先添加到类型表中。 -->
<!-- 这是引用任何类型的主要方式 -->
<Types></Types>
<!--在这里定义建筑。
关于附加属性和价值的例子,请看建筑物。在这个目录下的所有文件中 /Base/Assets/Gameplay/Data/-->
<Buildings></Buildings>
<Building_YieldChanges></Building_YieldChanges>
<Building_GreatPersonPoints></Building_GreatPersonPoints>
</GameData>
+ +

这里最有意思的就是第三个注释中提供的文件路径,打开资源浏览器找到这个目录..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\Gameplay\Data

+

这个目录下有很多的文件都是英文,猜测应该是游戏中每个分类的数据文件.我是说再写模组时想要修改游戏本身的数据就得需要这些文件.还是得先翻译一下这些英文都是什么意思才行.在当前目录下打开命令行

+
1
2
# 输出当前目录所有文件名到 CivFileList.txt
dir /B > CivFileList.txt
+ +

然后打开 CivFileList.txt 这个文件,拷贝所有英文到 谷歌或其他翻译词典里面翻译.得到中文翻译:

+
1
2
3
4
5
6
7
8
9
10
11
12
# 部分翻译
Agendas
Barbarians
Behavior Trees
Beliefs
Buildings

议程
野蛮人
行为树
信仰
建筑
+ +

先用代码编辑器打开一个与我们写模组最有直接关系的文件 Buildings.xml,没了解过 xml 文件的大兄弟,就可以把它当作 HTML 文件一样看待.主要关注标签的译文和标签内的代码,尤其是出现频率最高的单词比如:xxxName, xxxType 之类的.先通读这两千多行代码,主要关注注释和标签内前几行内容,得先知道这个文件整体结构怎么写的.

+

到这里,已经了解完了这个文件.至少是靠着翻译读了一遍.看这个文件的目的为了能在自己的模组中添加更多的内容.

+

打开别人的模组 YoRHaCore 中对应的文件 YoRHa_Core_Gameplay.xml 来复制粘贴到我的模组中然后测试运行,看效果.

+
    +
  • 根据 YoRHa_Core_Gameplay.xml 复制粘贴的修改城市每回合黄金产量为 116,每回合产出伟人点数 + 1(艺术家,工程师,音乐家,作家),BUILDING_TestCase 建筑每回合产出 3 个文化值.
  • +
+
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
<?xml version="1.0" encoding="utf-8" ?>
<GameData>
<!-- 几乎所有的游戏类型都应该首先添加到类型表中。 -->
<!-- 这是引用任何类型的主要方式 -->
<Types>
<Row Type="BUILDING_TestCase" Kind="KIND_BUILDING" />
</Types>

<!-- 在这里定义建筑。关于附加属性和价值的例子,请看建筑物。都在这个目录下的所有xml文件中 /Base/Assets/Gameplay/Data/-->
<Buildings>
<Row BuildingType="BUILDING_TestCase" />
</Buildings>
<Building_YieldChanges>
<!-- 建筑每回合生产3个文化值 -->
<Row BuildingType="BUILDING_TestCase" YieldType="YIELD_CULTURE" YieldChange="3"/>
</Building_YieldChanges>
<!-- 伟人点数 -->
<Building_GreatPersonPoints>
<!-- 艺术家 -->
<Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_ARTIST" PointsPerTurn="1"/>
<!-- 工程师 -->
<Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_ENGINEER" PointsPerTurn="1"/>
<!-- 音乐家 -->
<Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_MUSICIAN" PointsPerTurn="1"/>
<!-- 作家 -->
<Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_WRITER" PointsPerTurn="1"/>
</Building_GreatPersonPoints>
<Modifiers>
<Row>
<!-- 黄金 -->
<ModifierId>YORHA_CORE_GOLD</ModifierId>
<ModifierType>MODIFIER_SINGLE_CITY_ADJUST_CITY_YIELD_MODIFIER</ModifierType>
<OwnerRequirementSetId>PLAYER_IS_HUMAN</OwnerRequirementSetId>
</Row>
</Modifiers>
<ModifierArguments>
<Row>
<ModifierId>YORHA_CORE_GOLD</ModifierId>
<Name>Amount</Name>
<!-- 城市每回合产出黄金数 -->
<Value>116</Value>
</Row>
<Row>
<ModifierId>YORHA_CORE_GOLD</ModifierId>
<Name>YieldType</Name>
<Value>YIELD_GOLD</Value>
</Row>
</ModifierArguments>
<BuildingModifiers>
<Row>
<BuildingType>BUILDING_YORHA_CORE</BuildingType>
<ModifierId>YORHA_CORE_GOLD</ModifierId>
</Row>
</BuildingModifiers>
</GameData>
+ +

这其中的几行的解释已经在前面解释过了.我想在看一遍每行代码的注解

+

从游戏提供的代码抽取需要的代码到模组中

写到这里你可以看得出每对标签都对应着每一个文件中的内容,比如:..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\Gameplay\Data 目录下的文件 Buildings.xml 对应着代码中的标签 <Buildings></Buildings><Modifiers></Modifiers>

+

每对标签中对应的代码其实在原始游戏文件中都已经存在,只不过我们是把我们需要的某个文件中的东西拿出来组成一个新的东西.

+

比如:Buildings.xml 文件中第 380 行中建筑提供每回合的 2 科技值的生产

+
1
2
<!-- 图书馆 生产2科技-->
<Row BuildingType="BUILDING_LIBRARY" YieldType="YIELD_SCIENCE" YieldChange="2"/>
+ +

ModifierArguments 标签中允许修改建筑属性参数,在Buildings.xml的第 879 行中是对金字塔奇观的建造费用调整,这个值可以是任意的某个值.

+
1
2
3
4
5
6
<Row>
<!-- PYRAMID ADJUST BUILDER CHARGES 金字塔调整建造费 -->
<ModifierId>PYRAMID_ADJUST_BUILDER_CHARGES</ModifierId>
<Name>Amount</Name>
<Value>1</Value>
</Row>
+ +

示例:在我上文提到的两个模组中,建筑每回合生产都只是写了一个信仰每回合 + 1.

+
1
2
3
<Building_YieldChanges>
<Row BuildingType="BUILDING_YORHA_CORE" YieldType="YIELD_FAITH" YieldChange="1"/>
</Building_YieldChanges>
+ +

可以根据收益类型看出,肯定不止有信仰这一种.首先打开最有可能出现的文件 Buildings.xml ,尝试找一下金币,文化,科技的加成.

+
    +
  • 第 338 行,这是一个剧场奇观.每回合 + 2 的文化
  • +
+
1
<Row BuildingType="BUILDING_AMPHITHEATER" YieldType="YIELD_CULTURE" YieldChange="2"/>
+ +
    +
  • 第 363 行,这是一个银行建筑,每回合 + 5 黄金
  • +
+
1
<Row BuildingType="BUILDING_BANK" YieldType="YIELD_GOLD" YieldChange="5"/>
+ +
    +
  • 第 380 行,这是一个图书馆,每回合 + 2 科技值
  • +
+
1
<Row BuildingType="BUILDING_LIBRARY" YieldType="YIELD_SCIENCE" YieldChange="2"/>
+ +

至此,这个文件下的代码就是把需要的代码添加到我们创建模组中对应的标签下,如果模组中没有对应添加的标签,那就从你抽取的那个代码的文件中把标签照着添加到你的模组中缺少的文件中.不知道你写的对不对就构建运行进游戏看看,有没有生效,如果没有就是你写的有问题.

+

xxx_Icons.xml 图标文件编写

这个文件默认是不用改的,除非你不想使用默认的图标.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 
For simplicity, reuse an existing icon.
为简单起见,重用现有的图标。
-->
<IconDefinitions>
<!--
Name 是建筑显示的图标名称
Atlas 是图标所属建筑
Index 是图标对应所属建筑的序号
26 是工厂的图标
-->
<Row />
</IconDefinitions>
+ +

这个目录下的所有文件是针对每个分类下的图标显示 ..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\Icons ,这个 Index="26" 的来历,就在这个目录下的 Icons_Buildings.xml 文件中的 第 41 行.

+
1
<Row />
+ +

如果你想使用其他图标你只需要更改的属性,只有两个 AltasIndex 务必要保持这两个一致.

+

你也可以使用其他类型的单位图标,比如不是建筑的图标.

+

xxx_Text.xml 模组建筑描述文件编写

这个文件主要存放的是建筑的语言信息,看到这里你也发现了.所有的代码都是英文没有包含一个中文.(除注释之外)

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<GameData>
<LocalizedText>
<!--
Tag 是建筑的英文名称, 这样方便在模组中的各个文件互相引用
Language 是语言类型
en_US 是(英语)美国
zh_CN 是(简中)中国
ko_KR 是(韩语)韩国
Text 是这个建筑模组在游戏中显示的名称
-->
<Row Tag="LOC_BUILDING_GAME_STUDIO_NAME" Language="en_US">
<Text>在游戏中显示的建筑名称</Text>
</Row>

<Row Tag="LOC_BUILDING_GAME_STUDIO_DESCRIPTION" Language="en_US">
<Text>模组的描述</Text>
</Row>

<Row Tag="LOC_PEDIA_BUILDINGS_PAGE_BUILDING_GAME_STUDIO_CHAPTER_HISTORY_PARA_1" Language="en_US">
<Text>模组的小故事或者说历史背景</Text>
</Row>
</LocalizedText>
</GameData>
+ +

xxx_modinfo 模组信息文件编写

这个文件不需要自己创建 ,它会由编译器自动生成.

+

找到菜单栏中的 PROJECT 中的 XXX Properties... 会打开一个界面,选择左侧的 Mod Info 然后填写你的模组名称和描述以及小故事,还有感谢的人.

+

在选择左侧的 In-Game Actions 你看到了左边显示的三个Type 和三个 Id 选择第一个 Type 中的 UpdataDatabase ID 为 Gameplay ,这时你会看到右侧会显示一个大框里面显示了一个文件 NewBuilding_Gameplay.xml 文件,选中这个文件然后点击右侧的 Remove 然后在选择 Add 在弹出的新窗口中选择 File 列表框中的你模组对应的那个 xxx_Gameplay.xml 文件,然后点击 OK .然后在依次更新 Icons 文件和 Text 文件.

+

完成之后保存所有文件,至此就可以构建解决方案和模组了,并测试运行了.

+

生成你的模组

    +
  1. 菜单栏中选择 BUILD然后选择 Build Solution
  2. +
  3. 菜单栏中选择 BUILD 然后选择 Build xxxx
  4. +
+

此时,你的模组已经成功生成.你可以打开游戏并在"额外内容中启用你的本地模组".

+

删除你的模组

在调试模组中经常出现问题,所以我们不得不删除解决方案和模组重新生成.

+
    +
  1. 菜单栏中选择 BUILD 然后选择 Clear Solution
  2. +
  3. 菜单栏中选择 BUILD 然后选择 Clear xxxx
  4. +
+

这只是删除了你生成的模组文件,你的代码并没有被删除.

+

至此,你已经学会了如何编写你的第一个模组.

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/index.html b/2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/index.html new file mode 100644 index 0000000..93a2f1b --- /dev/null +++ b/2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/index.html @@ -0,0 +1,769 @@ + + + + + + + + + + + + 文明六自定义领袖特色建筑 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 文明六自定义领袖特色建筑 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

只允许主机、特定领袖、特定文明使用特定功能的模组

+

背景

一直想实现一个在联机模式下,只允许主机,特定领袖,特定文明使用特定功能的模组.

+

模组介绍

这是一个建筑添加与德国特色工业区下,每回合建筑生产一定的资源并修改城市每回合所产出的资源.

+

建筑本身加成:黄金 + 5, 食物 + 5, 文化 + 1, 科技 + 1, 信仰 + 5, 生产力 + 5

+

城市资源加成:黄金 + 116, 信仰 + 116, 文化 + 5, 生产力 + 20, 食物 + 20, 科技 + 5

+
建筑名称加成类型建筑每回合生产城市每回合生产
少年的野心/DefinCivilizationTraitBuilding黄金+5+116
食物+5+116
文化+1+5
科技+1+5
信仰+5+116
生产力+5+20
+ + + +

订阅地址

你可以直接订阅这个模组:Stem 创意工坊

+

参考链接

+

思路记录

玉皇大帝开发者终于回复我了

+

下午打开 Steam 时收到来自玉皇大帝模组开发者的回复.

+
+

墨影斋主人:在你的建筑下添加一个修改器,判定建造条件是 “玩家”,如果是绑定领袖或者文明,就直接用 TraitType

+
+

我迫不及待想要去尝试下

+

打开 HBX(HBuilderX) 检索一下 TraitType 看下这玩意怎么写的.

+

我在 Civilizations.xml 文件中的 第 200 行 找到了一条相关有可能的但与玉皇大帝作者说的完全不同的代码

+
1
<Row CivilizationType="CIVILIZATION_GERMANY" LeaderType="LEADER_BARBAROSSA"
+ +

第 2657 - 2659 行有点像

+
1
2
3
<Row CivilizationType="CIVILIZATION_GERMANY" TraitType="TRAIT_CIVILIZATION_IMPERIAL_FREE_CITIES"/>
<Row CivilizationType="CIVILIZATION_GERMANY" TraitType="TRAIT_CIVILIZATION_DISTRICT_HANSA"/>
<Row CivilizationType="CIVILIZATION_GERMANY" TraitType="TRAIT_CIVILIZATION_UNIT_GERMAN_UBOAT"/>
+ +

我没有在修改器文件Modifiers.xml文件中找到相关代码,据猜测这可能是自由添加,或许游戏源文件没有可能说明那只是一部分示例代码,并且游戏开发商不可能想象到各种可能性.

+

修改器文件如何编写

第一种可能性

他说的是修改器Modifiers 应该不可能是 ModifierArgumentsBuildingModifiers这两个修改器.

+

我没有见过单独绑定领袖的写法,在读一遍修改器文件吧Modifiers.xml

+

这两千多行代码全都是一个写法

+
1
2
3
4
5
6
7
8
9
10
<Types>
<Row Type="MODIFIER_ALL_PLAYERS_ATTACH_MODIFIER" Kind="KIND_MODIFIER"/>
</Types>
<DynamicModifiers>
<Row>
<ModifierType>MODIFIER_ALL_PLAYERS_ATTACH_MODIFIER</ModifierType>
<CollectionType>COLLECTION_ALL_PLAYERS</CollectionType>
<EffectType>EFFECT_ATTACH_MODIFIER</EffectType>
</Row>
</DynamicModifiers>
+ +

根据这段代码,可以猜测出一种可能性.

+
    +
  • 第六行代码的修改器类型和第 2 行的类型相等,这说明引用的是建筑名称
  • +
  • 后面第7和8行不知道表达的什么意思
  • +
  • 可能需要拼接代码
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
<Types>
<Row Type="BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING" Kind="KIND_BUILDING" />
</Types>
<Modifiers>
<Row>
<!-- 声明建筑要修改的建筑名称 -->
<ModifierType>BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING</ModifierType>
<!-- 绑定玩家拥有使用当前建筑权限 -->
<OwnerRequirementSetId>PLAYER_IS_HUMAN</OwnerRequirementSetId>
<!-- 绑定指定的领袖使用当前建筑: 德国 -->
<TraitType>CIVILIZATION_GERMANY</TraitType>
</Row>
</Modifiers>
+ +

第 10 行完全是文明类型和区域类型的两种结合写法,先构建一下试试效果吧.

+

woc难道我成功了

+

试了下其他文明是否有这个建筑,很遗憾,并没有成功绑定领袖

+

第二种可能性

我打开了领袖Leaders.xml 文件,终于发现了一个比较合理的代码.

+

换了好几个关键字,终于用巴巴罗萨的英译名在第 828 行找到了LEADER_BARBAROSSA

+
1
<Row LeaderType="LEADER_BARBAROSSA" TraitType="TRAIT_LEADER_HOLY_ROMAN_EMPEROR"/>
+ +

应该是这一行了,很遗憾,也不是这个

+

第三种可能性

我阅读了玉皇大帝的领袖文件,我猜测写法应该是这样.

+
1
2
3
4
5
6
7
8
<Row 
BuildingType="BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING"

PrereqDistrict="DISTRICT_CITY_CENTER"
PurchaseYield="YIELD_GOLD"
Cost="0"
AdvisorType="ADVISOR_GENERIC"
TraitType="TRAIT_LEADER_HOLY_ROMAN_EMPEROR"/>
+ +

我直接追加到了建筑名称的后面,当我使用其它领袖的时候,他成功的没有显示出来.当然不排除是我写错了,在没有写正确的情况下是不会显示的.

+

我再试试德国,万一我写对了呢.对吧!

+

这下才是真正成功了

+

竟然真成功了,再试试一下热座模式,因为仅允许玩家使用和德国使用,看看这两个条件冲突不

+

还真能用,没有任何冲突,终于完成了

+

总结

即时在不依靠国内的各种文档,视频教程,依然可以依靠阅读英文文档和官网的帮助以及自己的猜测和摸索一样能达成目标.

+

虽然我最后实现功能的方式和玉皇大帝模组开发者说的答案有点出入,但还是他提醒了我,我愣是依靠一条信息找到了答案"领袖,文明,特征TraitType"才完成了我一直以来的目标.

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/index.html b/2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/index.html new file mode 100644 index 0000000..e670b45 --- /dev/null +++ b/2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/index.html @@ -0,0 +1,961 @@ + + + + + + + + + + + + Absolute Pace 绝对和平的生存 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ Absolute Pace 绝对和平的生存 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

前言

在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣!

+

想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。

+

模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性

+

和平模式:没有难度、没有敌对势力生成、玩家受到非致死伤害时自动恢复生命

+

怪物的凋落物:一般情况下能够获得怪物的凋落物,意味着玩家困难度设置的是“简单、中等、困难”

+

模组介绍

这个模组名为 Absolute Pace,它的作用是向游戏中添加模拟怪物被击杀时的凋落物。

+

扩展模块

扩展模块将突破绝对和平的限制,将会围绕有困难度的模式下做出修改。

+

未来可能添加的扩展模块:

+
    +
  • 安全的矿物世界
  • +
  • 绝对和平专用合成工作台、熔炉、烟熏炉
  • +
  • 绝对和平专用方块、箱子、发光方块
  • +
  • 更加简单的物品合成配方、合成数量
  • +
  • 生物物品掉落盒
  • +
  • 蜘蛛恐惧症模式
  • +
+

现在已经具备的功能

理论上支持1.16.5以上的版本

+

和平模式下提供更多非可合成的配方

    +
  • 牛奶合成恶魂之泪【牛奶恶魂之泪毕竟都是 白色
  • +
+

恶魂之泪

+
    +
  • 压缩书架【解压缩书架时不知道为什么多出来了很多个
  • +
+

压缩书架

+
    +
  • 三叉戟【昨晚溺尸把三叉戟的制作配方传授给了我
  • +
+

三叉戟

+
    +
  • 这两天熬夜和史蒂夫肝了更多的配方
      +
    • 可以合成大部分敌对生物的凋落物
    • +
    • 降低了常用物品合成难度:比如9个书架能合成64个书架
    • +
    • 可以合成怪物蛋、各种马鞍、烟花、潜影盒、鞘翅、烈焰棒、暗影珍珠
    • +
    +
  • +
+

更多安全的维度

+

安全的维度:不会自然生成敌对怪物和友善的生物,只有你才能进入的世界

+
+
    +
  • 铁块空岛【魔改末地
  • +
+

铁块空岛

+
    +
  • 绿宝石大陆
  • +
+

绿宝石大陆

+
    +
  • 受支持的高级维度还包括:
      +
    • 黄金块空岛
    • +
    • 钻石块大陆
    • +
    • 煤炭大陆
    • +
    • 石英石大陆
    • +
    +
  • +
+

更多的独有特殊方块

+

特殊方块:具有象征性意义的方块,在未来的规划中将会添加新的工作模式至该方块【看上去很炫的样子在夜晚

+
+
    +
  • 就是用来滑稽的
  • +
+

QQ头像方块

+
    +
  • 微信和博客头像的方块【目前每10s能生成一个绿宝石
  • +
+

微信和博客头像的方块

+

绿宝石盒

+

高级燃料

+

高级燃料:可燃烧更长的时间

+

水桶:据说地球上水资源是古老文明最强大的能量

+

岩浆桶:地球内部喷出地表的热腾腾液体

+
+
    +
  • 水桶:提供四分钟的燃烧时间
  • +
  • 岩浆桶:提供八分钟的燃烧时间
  • +
+

怪物盒:模拟怪物被干死之后掉落的物品【复现了不支持当前版本的 Tiny Farm

+

预计添加所有生物的怪物盒,这其中包含了敌对生物盒和友善的生物

+
+
    +
  • 蜘蛛盒
  • +
+

蜘蛛盒

+
    +
  • 烈焰人盒
  • +
  • 骷髅生物盒
  • +
  • 苦力怕生物盒
  • +
  • 粘液怪生物盒
  • +
  • 岩浆怪生物盒
  • +
  • 末影人生物盒
  • +
  • 僵尸生物盒
  • +
  • 溺尸生物盒
  • +
  • 僵尸猪人生物盒
  • +
  • 唤魔者生物盒
  • +
  • 掠夺者生物盒
  • +
+

打印机:可以复制物品

打印机:复制物品

+

蜘蛛恐惧症模式

    +
  • 当新的实体生成时会干掉距离玩家50个方块之内的所有蜘蛛
  • +
+

矿石

    +
  • 经验矿石:生成范围1-64层,掉落1236经验
  • +
+

武器

40米大刀

+
    +
  • 40米大刀:你有事么?!先跟我的40米大刀聊聊吧
      +
    • 攻击速度为6,是钻石剑攻击速度的3.75倍
    • +
    • 附魔能力提升至69
    • +
    • 对生物的攻击伤害提升至369
    • +
    • 使用次数耐久设置为了3699
    • +
    +
  • +
+

工具

铁匠稿

+
    +
  • 铁匠稿:给铁匠塞了 亿 点点钱,他就把祖传的 铁匠稿 送给了我
      +
    • 效率为36,相当于效率Ⅴ附魔书的1.38倍
    • +
    • 附魔能力设置为了69
    • +
    • 攻击速度设置为了1
    • +
    • 对生物的攻击伤害设置为了9
    • +
    • 使用次数耐久设置为了3699
    • +
    +
  • +
+

斧子

+
    +
  • 斧子:woc,你拿我斧子干啥了?!咋变成屎黄色了?
      +
    • 效率为:36
    • +
    • 附魔能力:69
    • +
    • 攻击速度:1
    • +
    • 攻击伤害:9
    • +
    • 耐久:3699
    • +
    +
  • +
+

铲子

+
    +
  • 铲子:嗯?!女朋友送了一顶好看的绿帽子,还顺手帮我做了把绿色的铲子,可真贴心呐
      +
    • 效率:36
    • +
    • 附魔能力:69
    • +
    • 攻击速度:1
    • +
    • 伤害:4
    • +
    • 耐久:3699
    • +
    +
  • +
+

矿场

去掉了掉落经验瓶,由采集经验矿石代替

+

矿场

+
    +
  • 木炭盒
  • +
  • 煤盒
  • +
  • 铜锭盒
  • +
  • 钻石盒
  • +
  • 绿宝石盒
  • +
  • 黄金盒
  • +
  • 铁盒
  • +
  • 下届碎片盒
  • +
+

超大容量的箱子

永远不会装满的箱子

+
    +
  • 180存储位,比大箱子大3.33倍
  • +
  • 一个红色位删除多余物品
  • +
+

超大容量箱子

+

跳的超高

    +
  • 跳跃因子是普通跳跃的4倍,高度约为17格
  • +
  • 跳跃自带1级缓降效果
  • +
+

跳的超高

+

高清画

添加几幅高清画

+

阳光下的狐狸

+

少女

+

盔甲

真正强大的盔甲是自己的那颗心

+
    +
  • 头盔
      +
    • +33 护甲值
    • +
    • +9 盔甲韧性
    • +
    • +50 击退抗性
    • +
    +
  • +
  • 胸甲
      +
    • +36 护甲值
    • +
    • +9 盔甲韧性
    • +
    • +50 击退抗性
    • +
    +
  • +
  • 护腿
      +
    • +36 护甲值
    • +
    • +9 盔甲韧性
    • +
    • +50 击退抗性
    • +
    +
  • +
  • 靴子
      +
    • +69 护甲值
    • +
    • +9 盔甲韧性
    • +
    • +50 击退抗性
    • +
    +
  • +
+

盔甲外观

+

压缩的煤块

    +
  • 生成范围1-125
  • +
  • 掉落5个煤
  • +
+

压缩的煤块

+

回收站

将超大容量箱子中的删除物品位单独分离出来

+

回收站

+

铜装备

铜变得更加实用

+
    +
  • 武器:铜剑
      +
    • 伤害:10 【钻石剑:7】
    • +
    • 耐久:1741
    • +
    +
  • +
  • 工具:铜斧、稿、锹
      +
    • 伤害:8
    • +
    • 耐久:1741
    • +
    +
  • +
  • 铜锹
      +
    • 伤害:7
    • +
    • 耐久:1741
    • +
    +
  • +
  • 盔甲:被一个TNT炸的只剩下1格血
      +
    • 帽子
        +
      • 护甲:8
      • +
      • 耐久:660
      • +
      +
    • +
    • 上衣
        +
      • 护甲:20
      • +
      • 耐久:960
      • +
      +
    • +
    • 裤子
        +
      • 护甲:24
      • +
      • 耐久:900
      • +
      +
    • +
    • 鞋子
        +
      • 护甲:8
      • +
      • 耐久:780
      • +
      +
    • +
    +
  • +
+

下载地址

目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/index.html b/2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/index.html new file mode 100644 index 0000000..1b1f0f6 --- /dev/null +++ b/2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/index.html @@ -0,0 +1,732 @@ + + + + + + + + + + + + Minecraft BSL 汉化包 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ Minecraft BSL 汉化包 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

BSL 是 Minecraft 的光影包,前两天在官网下载了最新版发现没有中文支持,然后自己就花了两天时间汉化完了。

+

需要注意的

    +
  • 只有一两处还是英文,这两个还有英文的地方我在原英文语言文件中没找到,应该是其他我不知道文件中的,不过总体上不影响使用。
  • +
  • 有几个单词翻译过来比较奇怪,保守一点就没翻译。
  • +
  • 中文语言文件位置 BSL_v8.2.zip\shaders\lang\zh_CN.lang,有翻译不准确的兄弟们自己看着改吧!
  • +
+

实际效果图

+

+

+

+

下载

不想替换语言文件的兄弟可以直接拿去用

+

请确保您具有吾爱破解的账号,本帖的权限设置旨在防止第三方网站未经告知私自获取

+

汉化思路

    +
  1. 先打开光影包找到语言文件路径 BSL_v8.2.zip\shaders\lang\en_US.lang ,从文件名不难看出 lang 应该是 language 语言的缩写,其次是 en_US 这表示 英语 美国 ,直接百度查一下 en_US 是什么意思,然后根据这个意思搜索 中文简体 的表示方法
  2. +
  3. en_US.lang 文件解压出来,重命名为 zh_CN.lang,准备汉化
  4. +
  5. 打开文件看到第一行是 #shaders/lang/en_us.langen_us.lang 改成对应的 zh_cn.lang
  6. +
  7. 从第3行不难看出 = 前面的是代码,后面的则是要翻译的语言,并且 # 是注释后面写什么都行
  8. +
+
1
2
3
4
5
6
7
8
9
10
#Profiles en_US
profile.LOW=Low
profile.MEDIUM=Medium
profile.HIGH=High
profile.ULTRA=Ultra

#Profiles zh_cn
profile.LOW=最低
profile.MEDIUM=中等
profile.HIGH=高
+ +

全部翻译完就可以把文件压缩到与 en_US.lang 同一目录,导入游戏就可以用了

+

我才发现我前面几行连代码都改成中文了,第一张图里

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/index.html b/2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/index.html new file mode 100644 index 0000000..1b6e322 --- /dev/null +++ b/2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/index.html @@ -0,0 +1,813 @@ + + + + + + + + + + + + 植物大战僵尸阳光修改器 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 植物大战僵尸阳光修改器 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

前言

一直都想搞一个植物大战僵尸的修改器,想通过学习自己制作一个

+

打算使用Cheat Engine修改器找到阳光的真正地址,然后再用易语言制作一个针对植物大战僵尸的阳光修改器

+

名词释义

植物大战僵尸:可怕的僵尸即将入侵你的家,唯一的防御方式就是你栽种的植物!武装你的植物,切换他们不同的功能,诸如强悍的豌豆射手或樱桃炸弹,更加快速有效的将僵尸阻挡在入侵的道路上。不同的敌人,不同的玩法构成五种不同的游戏模式,加之夕阳、浓雾以及泳池之类的障碍增加了其挑战性,奇特的游戏乐趣永无止境!

+

Cheat Engine:是一款旨在帮助您在没有互联网连接的情况下修改单人游戏的修改器,以便您可以根据自己的喜好使游戏变得更难或更容易(例如:发现 100hp 太容易了,尝试玩最大 1 HP 的游戏),简称CE

+
+

Cheat Engine is a tool designed to help you with modifying single player games without internet connection so you can make them harder or easier depending on your preference(e.g: Find that 100hp is too easy, try playing a game with a max of 1 HP)

+
+

易语言:是一个自主开发,适合国情,不同层次不同专业的人员易学易用的汉语编程语言。易语言降低了广大电脑用户编程的门槛,尤其是根本不懂英文或者英文了解很少的用户,可以通过使用本语言极其快速地进入Windows程序编写的大门

+

功能

    +
  • 设置一个很大的阳光数值
  • +
+

下载地址

目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

+

参考链接

+

准备工具

    +
  • Cheat Engine v6.7
  • +
  • 易语言 v5.9
  • +
  • 易语言超级模块 v9.2
  • +
+

基础概念

内存

+

内存,一切皆内存,我们操作的全部都是内存。

+

所有地址相关的概念,都可以理解为坐标,用来给我们做标记的而已。

+

如果实在搞不明白,你想想指南针为啥指向北边,为啥叫南北,只是定义,只是公认

+
+

基址

+

以阳光为例,由于内存是动态分配的,我们每一次搜索存放阳光的内存地址都会不一样。

+

基址,就是指每一次分配我们都能通过偏移,来找到动态的地址,一般是由模块地址+固定的偏移实现。

+

例:我知道阳光在内存中存放地址,是模块地址+400的内存位置,所以阳光基址就一定是模块地址+400。所以动态就变成了,30格的时候,30+400。在40格的时候,40+400了。

+
+

指针

+

指针其实就可以理解成外号,你的外号叫二狗子,别人先知道你的外号,然后熟悉了才可以根据外号找到你的真名。

+

更多的用处是引用,就像别人往往更愿意叫你二狗子,而不愿意叫你真名一样。

+

例:一个指针叫做point,他指向内存的0xFF923200。那他就可以有两个作用,一个就是直接使用指向的内存地址FF923200;另一个可以直接查到FF923200里的值。

+
+

为什么要找基址

+

因为游戏退出基址不会变化,只有游戏更新后才会变化,而要是用找到的内存地址,例如:血的内存地址,游戏退出是会变化的,不方便我们编写辅助调用。

+
+

寻找阳光的数量的地址

需要注意的是每次、每个人搜索的地址都是不是一样的,所以只需要和我的步骤保持一致就行

+

首先打开游戏,使用CE加载游戏进程。

+

+

进入游戏,在CE的数值处输入起始默认的50阳光,然后点击首次扫描

+

+

选择好要加入战斗的植物,然后种下一个向日葵,此时阳光数量变为0,再次使用CE修改数值为0,然后点击再次扫描

+

+

此时列表中出现了两个结果,第二个结果的当前值就是阳光现在的数量,选中这个结果右键选择找出是什么改写了这个地址,然后在弹出的确认提示框中点击Yes,然后回到游戏收获一个阳光

+

+

选中0041F4D0 - 01 88 78550000 - add [eax+00005578],ecx这一行点击右侧的详细信息,然后在弹出的窗口中复制这个红色框的地址,需要记住的是这个蓝色框的地址5578

+

+

勾选Hex复选框,输入刚才复制的红色框地址,点击新的扫描,然后再次点击首次扫描会看到有47个结果,首先关注蓝色框地址这一列,如果没有绿色的地址那么就从上自下找一个地址相差比较大的,比如红色框中的地址就和上面的地址相差比较大。选中这个红色框的地址右键选择找出是什么访问了这个地址

+

+

和第5步骤相同,在弹出的窗口中可以看到有很多指令,如果你的窗口中没有任何指令,可以在游戏中拾取1个阳光。选择第一个地址004577C3 - 8B B7 68080000 - mov esi,[edi+00000868]然后点击右侧的详细信息,在弹出的窗口中可以看到和之前一样的界面,这里同样复制红色框中的地址,并手动记录下蓝色框的数字868

+

+

和第6步骤相同,把刚才复制的地址粘贴到数字窗口并确保你已经选中了Hex复选框,然后点击新的扫描,再次点击首次扫描,到这里你应该能在地址列看到绿色的地址,当然如果你并没有看到绿色的地址,那么你可以向下滚动鼠标。如果你依然没有找到绿色的地址,那么请重复以上的步骤直到找到绿色的地址。当你找到绿色的地址时选中其中一个地址并双击会被添加到下面的窗口

+

+

双击地址00731C50会打开一个小窗口,取消勾选十六进制,勾选指针,填写刚才手动记住的两个值5578和868,这里的5578和868是阳光地址的偏移量,然后你会看到这个窗口中的地址20994C40后面的数字是25,这就是游戏中阳光的真正地址和阳光的真实数值,然后点击确定

+

+

你会看到在下面窗口中刚才的地址00731C50已经变成了P->20994C40,然后导出保存这个地址,下次打开CE直接加载这个文件就可以直接修改。需要注意的是当前这个地址的数值还是25,你需要双击数值并修改成你需要的一个数值,比如4789。下次打开这个脚本就可以直接修改阳光为4789

+

+

使用CE生成修改器

在CE中依次点击文件从表单中生成通用修改器Lua脚本,点击添加热键设置激活的键位和功能描述,也可以设置激活和禁用的声音,修改完成可以设置修改器的程序名称、标题、程序图标。这里需要注意的是一定要确保CE和CE要生成的修改器名称和路径都是英文并不含特殊字符的,否则会生成失败。

+
+

特殊字符:\ / * ? : | “<> . , ; :

+
+

+

下次打开游戏就可以直接使用CE生成的修改器来修改游戏的阳光数量了。如果你的修改器没有生效,但是你发现却可以使用之前导出的P->20994C40地址的脚本来修改游戏,在这种情况下最好还是使用其他语言编写修改器来达到和CE生成修改器一样的目的

+

使用易语言编写修改器

搭建环境

新建一个Windows窗口程序,创建好之后双击窗口会打开代码编辑页面

+

+

首先需要把超级模块加入模块引用列表

+

+

编写代码

思路

首先根据之前CE的操作步骤,我们可以先记录下每一个步骤的关键信息

+
    +
  1. 首先使用CE打开了游戏进程
  2. +
  3. 寻找阳光在当前游戏进程中的地址
  4. +
  5. 根据阳光的地址找到偏移量
  6. +
  7. 根据阳光的地址偏移量找到了游戏基址
  8. +
  9. 根据游戏基址又找到了阳光的真实地址
  10. +
  11. 最后生成修改器
  12. +
+

开始编写

首先创建四个程序集变量,如果担心出问题可以按照我的写,接着空白处往下写就行

+

+

根据游戏的进程名获取进程ID,从CE界面的上面不难发现进程名popcapgame1.exe

+
1
进程ID = 取进程ID (“popcapgame1.exe”)
+ +

然后打开CE修改器就是刚才导出的脚本,看到下面红色框出来的地址00731C50,这个地址就是游戏的基址

+

+
1
游戏基址 = 读内存整数型 (进程ID, 十六到十 (“00731C50”))
+ +

然后在游戏的基址之上加入5578和868的偏移量,切记先加868,然后才是5578

+
1
2
阳光偏移 = 读内存整数型 (进程ID, 游戏基址 + 十六到十 (“868”))
阳光基址 = 阳光偏移 + 十六到十 (“5578”)
+ +

然后就可以设置阳光的数量了,这块的到整数 (阳光基址) + 54188是每次打开修改器修改的阳光数量,值得注意的是每次打开游戏时阳光基址都会变在加上54188就会变得更大。这里的阳光基址相对于游戏基址不会改变,因为每次打开游戏的游戏基址都并不是固定的,阳光基址是在游戏基址基础之上确定的相对的地址

+
1
写内存整数型 (进程ID, 阳光基址, 到整数 (阳光基址) + 54188)
+ +

然后保存代码,设置窗口标题和背景图

+

+

还可以设置程序的图标和个人信息

+

+

调试并运行修改器

打开游戏进入战斗界面,就可以点击红色框的运行修改器,蓝色框为停止运行修改器。如果你的修改器不能正常工作,请停止运行修改器并检查重复以上步骤

+

+

生成修改器

依次点击编译—独立编译,在弹出的对话框中点击确定,然后选择你要放在哪个位置下

+

+

效果图

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/index.html b/2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/index.html new file mode 100644 index 0000000..9f98a75 --- /dev/null +++ b/2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/index.html @@ -0,0 +1,946 @@ + + + + + + + + + + + + 基于MCreator+Fabric+Forge的自定义工作台 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 基于MCreator+Fabric+Forge的自定义工作台 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

前言

玩Minecraft已经有9年了一直都想给Minecraft写Mod,但苦于不会写代码并且几年前就连搭建开发环境都一直没有成功。现在MCreator可以解决这些问题,只需要几分钟就能设计出属于你的Mod

+

名词释义

模组 Modification 缩写为 Mod ,简体中文翻译为模组,它是游戏的一种修改或增强程序.比如玩家向游戏中添加了游戏本身不存在的物体使游戏更加有趣.

+

MCreator是开源软件,用于使用直观的易于学习的界面或集成的代码编辑器制作Minecraft Java版mod, Bedrock Edition Add-Ons和数据包。它在世界范围内被Minecraft玩家,mod开发者使用,用于教育和STEM研讨会

+
+

MCreator is open source software used to make Minecraft Java Edition mods, Bedrock Edition Add-Ons, and data packs using an intuitive easy-to-learn interface or with an integrated code editor. It is used worldwide by Minecraft players, mod developers, for education and by STEM workshops.

+
+

Fabric是一个轻量级的,实验性的Minecraft建模工具链。

+
+

Fabric is a lightweight, experimental modding toolchain for Minecraft.

+
+

Forge是Minecraft的主流API接口,其功能与Fabric相似

+

Minecraft《我的世界》是一款3D沙盒电子游戏,由Mojang Studios开发。玩家可无拘无束地与由方块、实体构成的3个维度环境互动。

+

参考链接

+

搭建环境

MCreator+Forge

    +
  1. 打开科学上网
  2. +
  3. 下载安装并打开MCreator
  4. +
  5. 设置MCreator科学上网
  6. +
+
1
2
3
4
5
6
7
8
9
10
11
# 打开 C:\Users\username\.mcreator\gradle\gradle.properties
systemProp.http.proxyHost=127.0.0.1
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=你的科学端口
systemProp.http.proxyPort=你的科学端口

#如果没有效果就安装Android Studio,打开 C:\Users\username\.gradle\gradle.properties
systemProp.http.proxyHost=127.0.0.1
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=你的科学端口
systemProp.http.proxyPort=你的科学端口
+ +
    +
  1. 设置完保存并重启Mcreator

    +
  2. +
  3. 设置中文简体,依次点击:Preferences—Interface language—Chinese(China)—Apply—Save,设置完保存并重启Mcreator

    +
  4. +
  5. 新建工作区,工作区类型选择 Minecraft Forge 模组,模组显示名称 testcase,模组ID / 命名空间 testcase,Minercaft版本(生成器) **Minecraft Forge for 1.19.2 (43.2.0)**,模组Java包名称 str.stringod.testcase,工作区文件夹 D:\code\Minecraft\testcase,点击创建新的工作区。如果Minecraft版本没有1.19.2可以选择任意版本,如果在新建工作区的时候提示Build Field类似的提示请重复第3-4步骤

    +
  6. +
+

如果你想给旧版本开发模组,但是在Minecraft版本中没有找到,那么你需要在MCreator plugins下载对应版本,然后把下载的文件放在.\MCreator\plugins\目录下,然后重启MCreator

+

MCreator+Fabric

    +
  1. 打开科学上网
  2. +
  3. 下载安装并打开MCreator
  4. +
  5. 设置MCreator科学上网
  6. +
+
1
2
3
4
5
6
7
8
9
10
11
# 打开 C:\Users\username\.mcreator\gradle\gradle.properties
systemProp.http.proxyHost=127.0.0.1
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=你的科学端口
systemProp.http.proxyPort=你的科学端口

#如果没有效果就安装Android Studio,打开 C:\Users\username\.gradle\gradle.properties
systemProp.http.proxyHost=127.0.0.1
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=你的科学端口
systemProp.http.proxyPort=你的科学端口
+ +
    +
  1. 设置完保存并重启Mcreator

    +
  2. +
  3. 设置中文简体,依次点击:Preferences—Interface language—Chinese(China)—Apply—Save,设置完保存并重启Mcreator

    +
  4. +
  5. MCreator plugins下载Fabric版本,然后把下载的文件放在.\MCreator\plugins\目录下,然后重启MCreator

    +
  6. +
  7. 新建工作区,工作区类型选择 Minecraft Fabric 模组,模组显示名称 testcase,模组ID / 命名空间 testcase,Minercaft版本(生成器) Minecraft Fabric for 1.19.2 - 0.67.1,模组Java包名称 str.stringod.testcase,工作区文件夹 D:\code\Minecraft\testcase,点击创建新的工作区。如果Minecraft版本没有1.19.2可以选择任意版本,如果在新建工作区的时候提示Build Field类似的提示请重复第3-4步骤

    +
  8. +
+

制作Forge模组

方块外观

    +
  1. 新建模组元素
  2. +
  3. 方块
  4. +
  5. 方块名称StringodCrafttable
  6. +
  7. 创建新方块
  8. +
  9. 导入方块并使用纹理
  10. +
+

1

+

方块属性

    +
  1. 创造模式物品栏选项卡Building Blocks
  2. +
  3. 硬度15
  4. +
  5. 亮度15
  6. +
  7. 光不透明度0
  8. +
  9. 启用发光渲染
  10. +
  11. 能够摧毁它的工具pickaxe
  12. +
+

2

+

方块生成

    +
  1. 此方块可以替换:删除可以替换的方块
  2. +
  3. 保存模组元素
  4. +
+

3

+

方块配方

    +
  1. 新建模组元素
  2. +
  3. 配方
  4. +
  5. 配方名称MakeStringodCrafttable
  6. +
  7. 创建新配方
  8. +
  9. 配方组名称stringod
  10. +
+

4

+

添加完配方后,保存模组元素

+

方块用户图形界面

    +
  1. 新建模组元素
  2. +
  3. 用户图形界面
  4. +
  5. 用户图形界面名称UiStringodCrafttable
  6. +
  7. 创建新用户图形界面
  8. +
  9. 在界面左侧按照顺序从左到右,从上到下依次添加9个输入槽
  10. +
  11. 在界面右侧添加一个输出槽
  12. +
  13. 在界面中间添加一个文本标签,文本———>
  14. +
+

5

+

添加完成后保存模组元素

+

牛蛋配方流程

    +
  1. 新建模组元素
  2. +
  3. 流程
  4. +
  5. 流程名称CrafttableCowEgg
  6. +
  7. 创建新流程
  8. +
  9. 流程模板
  10. +
  11. GUI Template - 3 x 3 Crafting table
  12. +
  13. 然后删除非必要的代码块
  14. +
+

6

+

添加好代码块,保存模组元素

+

配方调用流程

    +
  1. 新建模组元素
  2. +
  3. 流程
  4. +
  5. 流程名称StringodCrafttableCall
  6. +
  7. 创建新流程
  8. +
  9. 点击左侧的高级,选择调用流程,选取CrafttableCowEgg流程
  10. +
+

7

+

添加好代码块,保存模组元素

+

方块绑定用户图形界面

    +
  1. 打开StringodCrafttable
  2. +
  3. 方块实体
  4. +
  5. 为此方块启用实体功能
  6. +
  7. 绑定此方块到图形界面:UiStringodCrafttable
  8. +
  9. 启用右键打开绑定的界面
  10. +
  11. 物品栏大小(储存槽数量):10
  12. +
+

8

+

完成后保存模组元素

+

用户图形界面绑定配方调用流程

    +
  1. 打开UiStringodCrafttable
  2. +
  3. 界面左下角展开GUI流程触发器
  4. +
  5. 当这个界面打开时,每刻发生StringodCrafttableCall
  6. +
+

9

+

完成后保存模组元素

+

用户图形界面绑定输入槽和输出槽配方调用流程

    +
  1. 打开UiStringodCrafttable
  2. +
  3. 依次双击9个输入槽和1个输出槽
  4. +
  5. 当储存槽内容改变时执行StringodCrafttableCall
  6. +
+

10

+

完成后保存模组元素

+

构建并运行模组

点击右上角的绿色三角形运行游戏

+

11

+

制作Fabric模组

方块外观

    +
  1. 新建模组元素
  2. +
  3. 方块
  4. +
  5. 方块名称StringodCrafttable
  6. +
  7. 创建新方块
  8. +
  9. 导入方块并使用纹理
  10. +
+

1

+

方块属性

    +
  1. 创造模式物品栏选项卡Building Blocks
  2. +
  3. 硬度15
  4. +
  5. 亮度15
  6. +
  7. 光不透明度0
  8. +
  9. 启用发光渲染
  10. +
  11. 能够摧毁它的工具pickaxe
  12. +
+

2

+

方块生成

    +
  1. 此方块可以替换:删除可以替换的方块
  2. +
  3. 保存模组元素
  4. +
+

3

+

方块配方

    +
  1. 新建模组元素
  2. +
  3. 配方
  4. +
  5. 配方名称MakeStringodCrafttable
  6. +
  7. 创建新配方
  8. +
  9. 配方组名称stringod
  10. +
+

4

+

添加完配方后,保存模组元素

+

方块用户图形界面

    +
  1. 新建模组元素
  2. +
  3. 用户图形界面
  4. +
  5. 用户图形界面名称UiStringodCrafttable
  6. +
  7. 创建新用户图形界面
  8. +
  9. 在界面左侧按照顺序从左到右,从上到下依次添加9个输入槽
  10. +
  11. 在界面右侧添加一个输出槽
  12. +
  13. 在界面中间添加一个文本标签,文本———>
  14. +
+

5

+

添加完成后保存模组元素

+

牛蛋配方流程

    +
  1. 新建模组元素
  2. +
  3. 流程
  4. +
  5. 流程名称CrafttableCowEgg
  6. +
  7. 创建新流程
  8. +
  9. 流程模板
  10. +
  11. GUI Template - 3 x 3 Crafting table
  12. +
  13. 然后删除非必要的代码块
  14. +
+

6

+

添加好代码块,保存模组元素

+

配方调用流程

    +
  1. 新建模组元素
  2. +
  3. 流程
  4. +
  5. 流程名称StringodCrafttableCall
  6. +
  7. 创建新流程
  8. +
  9. 点击左侧的高级,选择调用流程,选取CrafttableCowEgg流程
  10. +
+

7

+

添加好代码块,保存模组元素

+

方块绑定用户图形界面

    +
  1. 打开StringodCrafttable
  2. +
  3. 方块实体
  4. +
  5. 为此方块启用实体功能
  6. +
  7. 绑定此方块到图形界面:UiStringodCrafttable
  8. +
  9. 启用右键打开绑定的界面
  10. +
  11. 物品栏大小(储存槽数量):10
  12. +
+

8

+

完成后保存模组元素

+

用户图形界面绑定配方调用流程

    +
  1. 打开UiStringodCrafttable
  2. +
  3. 界面左下角展开GUI流程触发器
  4. +
  5. 当这个界面打开时StringodCrafttableCall
  6. +
+

Fabric 1.19.2版本的插件显然不支持在图形用户界面每刻更新,所以只能在第二次选择当打开图形界面时更新配方

+

9_1

+

完成后保存模组元素

+

用户图形界面绑定输入槽和输出槽配方调用流程

    +
  1. 打开UiStringodCrafttable
  2. +
  3. 依次双击9个输入槽和1个输出槽
  4. +
  5. 当储存槽内容改变时执行StringodCrafttableCall
  6. +
+

10

+

完成后保存模组元素

+

构建并运行模组

点击右上角的绿色三角形运行游戏

+

10_2

+

导出模组

    +
  1. 点击MCreator窗口顶部菜单栏中的工作区
  2. +
  3. 导出模组用于分发
  4. +
  5. 导出模组而不捐赠,如果你有多余的钱可以捐赠
  6. +
  7. 选择一个位置并给你的模组命名
  8. +
+

下载示例模组

目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/index.html b/2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/index.html new file mode 100644 index 0000000..c4b5d1f --- /dev/null +++ b/2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/index.html @@ -0,0 +1,1328 @@ + + + + + + + + + + + + 基于Node.js跨平台局域网消息传输与QNAP部署 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 基于Node.js跨平台局域网消息传输与QNAP部署 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

0x1 引言

0x1.1 目的与背景

当我刚进入大学时,我得到了我的第一台游戏本。当时,我经常需要在手机和电脑之间传输文件和文档。最初,我使用QQ和手机热点来进行传输,这显得相当繁琐。后来,我尝试了蓝牙传输,但速度过慢。尽管我使用了小米的热点文件传输功能,但仅为了传输简单的文本或图片,这样的操作还是过于复杂。

+

毕业后,我的生活出现了很大的改变。拥有了路由器后,内网传输变得相对简单。但当我获得了iPhone和MacBook,我发现跨操作系统传输文件并不简单。例如,从iPhone向Windows传输文本或链接时,我还需依赖QQ等软件。尝试使用Windows的SMB共享服务和MacBook的文件共享都有其局限性和问题。

+

后来,我转向开源的云盘解决方案,但传输速度的限制使我非常不满意。直到我获得了QNAP,大部分的文件传输问题得到了解决。然而,在局域网下保持Windows和MacBook数据的一致性,尤其是文本数据,依然是一个问题。显然,依赖QQ这样的方式效率低下。

+

因此,创建一个简单的网页成为了一种必要,让任何设备仅通过输入一个地址就能发送消息,避免了下载和设置各种软件的麻烦。

+

0x1.2 目标受众

    +
  1. 大学生和职场新人:他们经常在不同的设备之间传输文件和文档,尤其是在手机和电脑之间,需要一个高效的解决方案。

    +
  2. +
  3. 跨操作系统用户:特别是那些使用iPhone和Windows或MacBook的人,他们经常面临操作系统之间的传输难题。

    +
  4. +
  5. 网络技术爱好者:对于那些寻求局域网下文件和数据传输解决方案的人,他们可能已经尝试了各种方法,但仍然在寻找更加高效的方法。

    +
  6. +
  7. 对效率有追求的人:不想依赖于多个应用程序和软件,而是寻求一个集中的、简化的解决方案来处理日常的文件和数据传输任务。

    +
  8. +
  9. 云存储和网络存储用户:特别是那些使用开源云盘或QNAP这样的解决方案的人,他们在寻找更快、更直接的传输方法。

    +
  10. +
+

0x2 必要的理论知识解释

0x2.1 基础概念

    +
  1. 文件传输

    +
      +
    • 定义:文件传输指的是从一个设备、系统或者位置将文件或数据移动到另一个设备、系统或位置的过程。
    • +
    • 应用:例如,通过USB、网络或云服务将文档从电脑传输到手机。
    • +
    +
  2. +
  3. 操作系统差异

    +
      +
    • 定义:不同的操作系统(如Windows、MacOS、Linux等)在架构、功能和界面上有所不同。
    • +
    • 应用:某些软件可能只在特定操作系统上运行,或在不同系统上有不同的表现。
    • +
    +
  4. +
  5. 局域网(LAN)原理

    +
      +
    • 定义:局域网是一个限定在较小地理范围内(如家庭、办公室)的计算机网络。
    • +
    • 应用:局域网使得同一位置的多台设备可以共享资源,如打印机或文件。
    • +
    +
  6. +
  7. 蓝牙和热点传输

    +
      +
    • 定义:蓝牙是一种无线技术标准,用于短距离数据交换,而热点传输则是通过Wi-Fi将文件从一个设备分享到另一个设备。
    • +
    • 应用:例如,用蓝牙耳机听音乐或使用手机热点给电脑上网。
    • +
    +
  8. +
  9. 云存储与网络存储

    +
      +
    • 定义:云存储是通过互联网存储数据在远程服务器上的服务;网络存储则是在局域网内提供集中化的数据存储。
    • +
    • 应用:如,使用Google Drive或Dropbox来存储文件或使用NAS来在家庭网络中共享文件。
    • +
    +
  10. +
  11. 跨平台兼容性

    +
      +
    • 定义:指的是软件或应用在多个操作系统或设备上的运行能力。
    • +
    • 应用:例如,一个应用既可以在Android上运行,也可以在iOS上运行。
    • +
    +
  12. +
  13. Web技术基础

    +
      +
    • 定义:关于如何构建和呈现互联网内容的技术和原理。
    • +
    • 应用:例如,使用HTML、CSS和JavaScript来构建网页。
    • +
    +
  14. +
  15. Node.js

    +
      +
    • 定义:是一个允许在服务器上运行JavaScript的运行时环境。
    • +
    • 应用:例如,构建后端API或Web应用。
    • +
    +
  16. +
  17. HTML

    +
      +
    • 定义:是用来描述网页结构的标记语言。
    • +
    • 应用:例如,定义网页中的标题、段落和链接。
    • +
    +
  18. +
  19. CSS

    +
  20. +
+
    +
  • 定义:是用于描述网页外观和格式的语言。
  • +
  • 应用:例如,设置字体、颜色和布局。
  • +
+
    +
  1. Socket
  2. +
+
    +
  • 定义:是计算机之间进行通信的端点。
  • +
  • 应用:例如,实时聊天应用。
  • +
+
    +
  1. Server
  2. +
+
    +
  • 定义:是为其他计算机或应用提供资源、服务或数据的系统或应用。
  • +
  • 应用:例如,Web服务器存储和提供网页内容。
  • +
+
    +
  1. Docker
  2. +
+
    +
  • 定义:是一个平台,用于创建、运行和管理容器化的应用。
  • +
  • 应用:例如,为应用提供一致的运行环境。
  • +
+
    +
  1. IP地址
  2. +
+
    +
  • 定义:是分配给联网设备的数字地址,用于识别和定位。
  • +
  • 应用:例如,访问特定的网站或远程服务器。
  • +
+
    +
  1. User Agent
  2. +
+
    +
  • 定义:是描述浏览器或其他客户端如何与服务器通信的字符串。
  • +
  • 应用:例如,网站可以识别用户的设备和浏览器,并据此提供优化的内容。
  • +
+

0x2.2 相关技术背景

随着技术的日益发展,文件传输和数据共享已经成为现代工作和生活中不可或缺的部分。无论是学生、企业家还是日常用户,我们都经常面临跨设备、跨操作系统的数据传输挑战。

+
    +
  1. 文件传输:这是一种基本的需求,涉及将文件从一个设备传送到另一个设备。尽管有许多可用的方法,例如USB、蓝牙和热点传输,但它们都有自己的局限性和速度问题。

    +
  2. +
  3. 操作系统差异:由于Windows、MacOS和其他操作系统的内在差异,许多应用和服务在跨操作系统通信时面临挑战。

    +
  4. +
  5. 局域网原理:局域网为设备提供了一种在有限的地理范围内共享资源的方式。例如,家庭或办公室内的设备可以通过局域网共享打印机或文件。

    +
  6. +
  7. 蓝牙与热点传输:尽管这两种技术都提供了一种无线传输方式,但它们通常受限于传输距离和速度。

    +
  8. +
  9. 云存储与网络存储:为了解决大文件传输的问题,许多用户转向了云存储或网络存储解决方案,如Google Drive或NAS。

    +
  10. +
  11. 跨平台兼容性:这是当今技术世界中的一大挑战。由于多样性的增加,需要开发可以在多个平台上运行的应用。

    +
  12. +
  13. Web技术基础:HTML、CSS和JavaScript等技术为创建跨平台解决方案提供了基础,允许用户通过浏览器访问应用和服务。

    +
  14. +
  15. 服务器和后端技术:Node.js提供了一个强大的平台,用于构建后端服务和API,而Socket技术使实时通信成为可能。这些技术经常被用于创建动态、实时的Web应用。

    +
  16. +
  17. 容器化技术:Docker为开发者提供了一种方式,将其应用及其所有依赖性封装在一个容器中,确保其在任何环境中的一致性和可移植性。

    +
  18. +
  19. 网络识别:IP地址和User Agent允许设备和网络服务识别请求的来源,从而为用户提供个性化的体验和内容。

    +
  20. +
+

0x3 Node.js原型开发

0x3.1 环境配置

初始化项目

+
1
2
mkdir realtime-text
cd realtime-text
+ +

安装Node.js,最好选择LTS版本,在项目根目录执行

+
1
2
3
npm init -y
npm install express socket.io
npm install multer
+ +

0x3.2 基本功能实现

创建服务器 (server.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
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

app.use('/socket.io', express.static('node_modules/socket.io/client-dist/'));

app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
console.log('a user connected');

socket.on('send text', (text) => {
io.emit('receive text', text);
});

socket.on('disconnect', () => {
console.log('user disconnected');
});
});

server.listen(3000, () => {
console.log('listening on *:3000');
});
+ +

创建前端页面 (index.html)

+
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
<!doctype html>
<html>
<head>
<title>Realtime Text</title>
</head>
<body>
<textarea id="textArea" rows="10" cols="50"></textarea>
<button id="sendButton">Send</button>
<hr>
<h3>Received Text:</h3>
<div id="receivedText"></div>

<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();

document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
socket.emit('send text', text);
};

socket.on('receive text', function(text) {
document.getElementById('receivedText').innerText = text;
});
</script>
</body>
</html>
+ +

启动服务器

+

本地服务器默认地址:http://localhost:3000/

+
1
node server.js
+ +

创建启动服务器脚本 (run.bat)

+
1
2
node server.js
pause
+ + + +

0x4 按需复制,无需深究学习

不想学习思路直接运行代码,按照以下步骤操作,如果需要上传NAS请继续阅读 0xA Docker封装与测试

+

创建对应的目录结构,并把index.htmlserver.js这两个的代码复制到你的网站根目录下

+

下载JetBrains Mono字体,解压JetBrainsMono-Regular.ttfJetBrainsMono-Regular.woff2到网站根目录

+

现在你的目录结构应该是这样的

+
1
2
3
4
5
6
7
8
 D:\realtime_text 的目录
2023/08/27 04:51 11,332 index.html
2023/01/14 23:20 273,900 JetBrainsMono-Regular.ttf
2023/01/14 23:20 92,164 JetBrainsMono-Regular.woff2
2023/08/27 01:47 <DIR> node_modules
2023/08/27 01:12 52 run.bat
2023/08/27 01:49 1,942 server.js
2023/08/27 13:27 <DIR> uploads
+ +

0x4.1 index.html

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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
<!doctype html>
<html>
<head>
<title>Realtime Text</title>
<style>
[data-theme="dark"] input, [data-theme="dark"] textarea, [data-theme="dark"] button {
color: white; /* 将文字颜色设置为白色 */
border-color: white; /* 如果需要, 也可以设置边框颜色 */
}

:root {
/* 浅色模式的设置 */
--text-color: black;
--background-color: white;
--input-bg: #fff;
--container-bg: #fff;
--header-bg: #fff;
--button-color: black; /* 新增 */
}

[data-theme="dark"] {
/* 深色模式的设置 */
--text-color: black; /* 文本颜色为黑色 */
--background-color: white; /* 背景颜色为白色 */
}

.bottom-controls {
display: flex;
justify-content: center;
}
.bottom-controls > div {
display: flex; /* 这确保了内部容器的子元素水平对齐 */
align-items: center;
}
/* 为固定底部工具栏添加填充 */
div[style*="position: fixed; bottom: 0;"] {
padding-left: 10px;
padding-right: 10px;
}
/* 保证body的背景颜色被应用 */
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--background-color) !important;
color: var(--text-color);
font-family: "JetBrainsMono", Arial, sans-serif;
}
.container {
border: 1px solid #ddd;
background-color: #fff;
}
/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
.bottom-controls {
justify-content: flex-start; /* 这将使内部容器与起始位置对齐 */
}
.bottom-controls > div {
flex-direction: column; /* 这确保了内部容器的子容器是垂直堆叠的 */
align-items: center;
width: 100%;
}

/* 更新底部控件的容器样式 */
div[style*="position: fixed; bottom: 0;"] {
background-color: var(--container-bg);

padding-left: 10px;
padding-right: 10px;
display: flex;
flex-direction: column; /* 确保控件垂直堆叠 */
}

/* 确保所有控件拉伸以填充可用宽度 */
#deviceName, #textArea, #imageUpload, #sendButton {
background-color: var(--container-bg);

width: 100%;
box-sizing: border-box; /* 确保填充和边框包含在宽度中 */
margin-bottom: 5px; /* 添加控件之间的间距 */
}

/* 增加高度,便于触摸 */
#deviceName, #textArea, #sendButton {
height: 25px;
}
/* 在邮件列表的底部添加填充 */
#messages {
padding-bottom: 70px; /* 根据底部工具栏的高度调整此值 */
}
#deleteButton{
width: 100%;
height: 25px;
}
.container{
max-height: 480px;
}
#themeToggle{
width: 100%;
height: 25px;
margin-bottom: 5px;
}
}
/* 大屏幕的样式(例如,Windows, MacBook) */
@media (min-width: 501px) {
#deviceName, #textArea, #imageUpload, #sendButton {
background-color: var(--input-bg);

height: 60px;
font-size: 18px;
padding: 10px;
margin-bottom: 10px;
}
#textArea {
width: 50%;
}
#sendButton {
padding: 10px 20px; /* 调整按钮的填充 */
margin-left: 10px; /* 在发送按钮的左侧添加一些边距,以保持间距 */
margin-right: 5px;
width: 100px;
}
#deleteButton{
margin-left: 5px;
width: 150px;
height: 50px;
}
#themeToggle{
margin-right: 10px;
width: 100px;
height: 50px;
}
}
body, button {
background-color: var(--bg-color);
color: var(--text-color);
}
/* 为了确保按钮的文本颜色能够在两种主题下都清晰可见 */
button {
color: var(--button-color);
}
/* 底部控件的文本颜色 */
.bottom-controls input,
.bottom-controls textarea,
.bottom-controls button {
color: var(--text-color);
border-color: var(--text-color);
}
.container {
background-color: var(--container-bg);
overflow-y: auto;
min-width: 350px;
position: sticky;
margin-bottom: 100px;
}
body.dark-theme {
/* 深色模式的设置 */
--text-color: white;
--background-color: #555;
--input-bg: #555;
--container-bg: #444;
--header-bg: #444;
--button-color: white; /* 新增 */
}
#header {
background-color: var(--header-bg);
}
@font-face {
font-family: "JetBrainsMono"; /* 可以为字体起一个名字 */
src: url('JetBrainsMono-Regular.woff2') format('woff2'), /* 最优先 */
url('JetBrainsMono-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}

</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
</head>
<body>

<div class="container">
<div id="header" style="background-color: var(--container-bg);">
<ul id="messages" style="max-height: 70vh"></ul>
</div>
</div>

<!-- 调整底部工具栏结构 -->
<div class="bottom-controls" style="position: fixed; bottom: 0; width: 100%; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1); background-color: var(--container-bg);">
<div style="display: flex; align-items: center;">
<button id="themeToggle">切换主题</button>
<input type="text" id="deviceName" placeholder="Device Name">
<input type="file" id="imageUpload" accept="image/*">
<textarea id="textArea" placeholder="写点什么?!"></textarea>
<button id="sendButton">发送</button>
<button id="deleteButton">删除选中消息</button>
</div>
</div>

<script src="/socket.io/socket.io.js"></script>
<script>
window.addEventListener('load', function() {
setTimeout(function() {
window.scrollTo(0, 1);
}, 0);

// 请求历史消息
fetch('/messages')
.then(response => response.json())
.then(data => {
const messagesDiv = document.getElementById('messages');
data.messages.forEach(message => {
const p = document.createElement('p');
p.textContent = message;
messagesDiv.appendChild(p);
});
})
.catch(error => {
console.error('获取消息错误:', error);
});
});


document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;


var socket = io();

document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
var deviceName = document.getElementById('deviceName').value || "匿名设备";
var imageFile = document.getElementById('imageUpload').files[0];
var listCheckBox = document.getElementById('listCheckBox');

if (imageFile) {
var formData = new FormData();
formData.append('image', imageFile);
formData.append('deviceName', deviceName);
formData.append('text', text);
formData.append('listCheckBox', listCheckBox);

fetch('/upload-image', {
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
socket.emit('send text', data);
});
} else {
socket.emit('send text', {text, deviceName });
}
// 在所有情况下都清除textArea和图片选择器的值
document.getElementById('textArea').value = '';
document.getElementById('imageUpload').value = ''; // 这将清除文件输入选择器的内容
};

var messageId = 0; // 初始化一个全局变量
socket.on('receive text', function(data) {
var li = document.createElement("li");

// 创建复选框
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.className = "messageCheckbox";
li.appendChild(checkbox); // 将复选框添加到列表项中

var time = data.timestamp || new Date().toLocaleString(); // 获取时间戳,如果没有则使用当前时间

var content = `[${data.deviceName} @ ${time}]: `; // 显示设备名和时间
if (data.text.startsWith("```") && data.text.endsWith("```")) {
// 如果文本以 "```" 开头和结尾,将其识别为代码
content += `<pre>${data.text.slice(3, -3)}</pre>`;
} else {
content += `${data.text}`;
}

if (data.imageUrl) {
content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
}

li.innerHTML += content;
document.getElementById('messages').appendChild(li);

li.setAttribute('data-message-id', messageId); // 为每个消息设置唯一ID
messageId++;

console.log("设备名称和IP地址:", data.deviceName);
});

// 发送HTTP DELETE请求到后端来删除消息
document.getElementById('deleteButton').onclick = function() {
var selectedMessages = document.querySelectorAll('.messageCheckbox:checked');
var messageIdsToDelete = [];
selectedMessages.forEach(function(checkbox) {
var li = checkbox.closest('li');
messageIdsToDelete.push(li.getAttribute('data-message-id'));
li.remove();
});
socket.emit('delete messages', messageIdsToDelete);

// 向后端发送删除请求
messageIdsToDelete.forEach(id => {
fetch(`/message/${id}`, {
method: 'DELETE'
}).then(response => {
if (!response.ok) {
// 以某种方式处理错误响应
console.error('从服务器删除消息失败');
}
});
});
};


socket.on('delete messages', function(messageIds) {
messageIds.forEach(function(messageId) {
var li = document.querySelector('li[data-message-id="' + messageId + '"]');
if (li) li.remove();
});
io.emit('delete messages', messageIds); // 向所有客户端广播消息ID,以供删除
});



document.addEventListener('click', function(event) {
// 如果消息中的图像被点击
if (event.target.tagName === 'IMG' && event.target.closest('#messages')) {
var modal = document.getElementById('imageModal');
var modalImage = document.getElementById('modalImage');

modalImage.src = event.target.src;
modal.style.display = 'flex';

modal.addEventListener('click', function() {
modal.style.display = 'none';
});
}
});

// 按下'Escape'关闭模式
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
var modal = document.getElementById('imageModal');
modal.style.display = 'none';
}
});

function getDeviceNameFromUserAgent() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;

// Windows Phone必须先行一步,因为它的用户界面也包含“Android”。
if (/windows phone/i.test(userAgent)) {
return "Windows Phone";
}

if (/android/i.test(userAgent)) {
return "Android Device";
}

// iOS检测
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return "iOS Device";
}

if (/macintosh|mac os x/i.test(userAgent)) {
return "Mac";
}

if (/windows/i.test(userAgent)) {
return "Windows PC";
}

return "匿名设备";
}

// 使用函数来自动填充设备名称
document.getElementById('deviceName').value = getDeviceNameFromUserAgent();

// 切换主题
document.getElementById('themeToggle').addEventListener('click', function() {
document.body.classList.toggle('dark-theme');
});


socket.on('load history', function(history) {
history.forEach(data => {
var li = document.createElement("li");
var content = `[${data.deviceName}]: ${data.text}`;
if (data.imageUrl) {
content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
}
li.innerHTML = content;
document.getElementById('messages').appendChild(li);
});
});

</script>

<!-- 图像模块 -->
<div id="imageModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 9999; align-items: center; justify-content: center;">
<img id="modalImage" src="" style="max-width: 90%; max-height: 90%;">
</div>

</body>
</html>
+ +

0x4.2 server.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
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const multer = require('multer');
const path = require('path');
const sqlite3 = require('sqlite3').verbose();

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

const messageHistory = [];


// 创建或打开数据库
const db = new sqlite3.Database('./messages.db');

// 创建message表,如果存在则什么都不做
// 包含id、content、deviceName、 ipAddress、 timestamp字段
db.run("CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, deviceName TEXT, ipAddress TEXT, timestamp TEXT)", (err) => {
if (err) {
console.error("创建/检查表错误:", err);
return;
}
console.log("表检查/创建成功");
});



// 在服务器启动时从数据库加载消息历史记录
db.all("SELECT id, content, deviceName, ipAddress, timestamp FROM messages", [], (err, rows) => {
if (err) {
console.error("从数据库获取历史消息出错:", err);
return;
}
// 遍历数据库每条记录
rows.forEach(row => {
const message = {
id: row.id, // 确保这里保留 id
text: row.content,
deviceName: row.deviceName,
ipAddress: row.ipAddress,
timestamp: row.timestamp
};
// 将数据库中的数据保存在 messageHistory
messageHistory.push(message);
});
console.log("从数据库加载历史消息");
});


app.use(express.json());

app.post('/send', (req, res) => {
const messageContent = req.body.message;

if (messageContent) {
// 存储消息到数据库
db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)", [data.text, data.deviceName, clientIpAddress, data.timestamp],
(err) => {
if (err) {
console.error("插入数据库出错:", err);
return;
}
console.log('收到的信息:', data.text || messageContent);
});
} else {
res.status(400).send({error: "消息内容为空"});
}
});


app.use('/socket.io', express.static('node_modules/socket.io/client-dist/'));

app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
console.log('有一个用户连接');

function formatMessageForEmission(originalMessage) {
return {
...originalMessage,
deviceName: `${originalMessage.deviceName} (${originalMessage.ipAddress})`
};

}

// 发送历史消息给新连接的用户
messageHistory.forEach((message) => {
socket.emit('receive text', formatMessageForEmission(message));
// console.log("format的设备和IP地址:",message)
});




socket.on('send text', (data) => {
// 获取客户端的IP地址
let clientIpAddress = socket.request.connection.remoteAddress.replace(/^::ffff:/, ''); // 清除IPv6前缀

if (clientIpAddress === '::1' || clientIpAddress === '127.0.0.1') {
clientIpAddress = 'Localhost';
}

// 添加ipAddress字段到data对象
data.ipAddress = clientIpAddress;

// 默认设备名称
if (!data.deviceName || data.deviceName.trim() === "") {
data.deviceName = "匿名设备";
}

// 添加消息发送时间
data.timestamp = new Date().toLocaleString();

// 添加消息到数据库中
db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)",
[data.text, data.deviceName, clientIpAddress, data.timestamp],
function(err) { // 使用函数关键字以便访问 this.lastID
if (err) {
console.error("插入数据库出错:", err);
return;
}
data.id = this.lastID; // 获取新插入的行的 ID
messageHistory.push(data); // 将新消息添加到历史记录
console.log('通过套接字收到的消息:', data.text);
}
);

io.emit('receive text', formatMessageForEmission(data));
});

socket.on('disconnect', () => {
console.log('有一个用户断开连接');
});
});

// 注意:仅使用一个侦听器,app.listen 或 server.listen
server.listen(3000, () => {
console.log('服务器地址:http://localhost:3000');
});

const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/')
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname))
}
});

const upload = multer({ storage: storage });

app.post('/upload-image', upload.single('image'), (req, res) => {
let imageUrl = `/uploads/${req.file.filename}`;
res.json({ text: req.body.text, deviceName: req.body.deviceName, imageUrl: imageUrl });
});

app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

// 静态文件处理
app.use('/css', express.static(path.join(__dirname, 'css')));
app.use('/js', express.static(path.join(__dirname, 'js')));
app.use('/', express.static(path.join(__dirname, 'JetBrainsMono-Regular.ttf')));
app.use('/', express.static(path.join(__dirname, 'JetBrainsMono-Regular.woff2')));

app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});

app.delete('/message/:id', (req, res) => {
const messageId = req.params.id;
if (messageId) {
db.run("DELETE FROM messages WHERE id=?", [messageId], (err) => {
if (err) {
console.error("从数据库删除消息出错:", err);
res.status(500).send({error: "删除消息失败"});
return;
}

// 从 messageHistory 数组中删除消息
const index = messageHistory.findIndex(message => message.id === Number(messageId));
if (index !== -1) {
messageHistory.splice(index, 1);
}

res.send({status: "消息已删除"});
});
} else {
res.status(400).send({error: "无效的消息ID"});
}
});
+ +

启动服务器

+
1
node server.js
+ +

0x4.3 直接下载已打包好的

避免重复上传已打包文件参考:局域网文件和消息传输

+

请确保您具有吾爱破解的账号,且用户组为锋芒初露,本帖的权限设置旨在防止第三方网站未经告知私自获取

+

要继续学习请往下看

+ + + + + + + +

0x5 界面图像优化与适配

0x5.1 消息展示与设备自适应

0x5.1.1 设备信息消息中心

    +
  • 美化布局:使用简单的内联CSS让布局居中
  • +
  • 显示多条消息:而不是只覆盖显示一条
  • +
  • 显示设备名称/IP:为简化起见,将让用户输入一个设备名称。关于IP地址,我们可以从请求头中获取
  • +
+

更新index.html如下:

+
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
<!doctype html>
<html>
<head>
<title>Realtime Text</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f4f4f4;
}
.container {
padding: 20px;
border: 1px solid #ddd;
background-color: #fff;
}
</style>
</head>
<body>
<div class="container">
<input id="deviceName" placeholder="Enter Device Name" />
<textarea id="textArea" rows="4" cols="50" placeholder="Type your message..."></textarea>
<button id="sendButton">Send</button>
<hr>
<h3>Received Messages:</h3>
<ul id="messages"></ul>
</div>

<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();

document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
var deviceName = document.getElementById('deviceName').value || "Unknown";
socket.emit('send text', { text, deviceName });
};

socket.on('receive text', function(data) {
var li = document.createElement("li");
li.innerText = `[${data.deviceName}]: ${data.text}`;
document.getElementById('messages').appendChild(li);
});
</script>
</body>
</html>
+ +

server.js中,我们也要做相应的修改以支持新的消息格式并发送设备的IP地址:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
io.on('connection', (socket) => {
console.log('a user connected');

socket.on('send text', (data) => {
let clientIpAddress = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
data.ipAddress = clientIpAddress;
io.emit('receive text', data);
});

socket.on('disconnect', () => {
console.log('user disconnected');
});
});
+ +

这样,当用户发送消息时,它们将以[DeviceName]: Message的格式显示,并在服务器端的console中记录IP地址。

+

我们可以在server.js中获取连接的客户端的局域网IP地址,并将其与设备名称一同发送到前端。

+

首先,修改server.js的消息处理部分以包含IP地址:

+
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
io.on('connection', (socket) => {
console.log('a user connected');

socket.on('send text', (data) => {
// 获取客户端的IP地址,清除IPv6前缀,使其更易读
let clientIpAddress = socket.request.connection.remoteAddress.replace(/^::ffff:/, '');
// 如果从运行服务器的同一台机器上连接,将会显示“Localhost”而不是::1或127.0.0.1
if (clientIpAddress === '::1' || clientIpAddress === '127.0.0.1') {
clientIpAddress = 'Localhost';
}


// 默认设备名称
if (!data.deviceName || data.deviceName.trim() === "") {
data.deviceName = "匿名设备";
}

// 将设备名和IP地址结合在一起
data.deviceName = `${data.deviceName} (${clientIpAddress})`;

io.emit('receive text', data);
});

socket.on('disconnect', () => {
console.log('user disconnected');
});
});
+ +

更新index.html如下:

+
1
2
3
4
5
6
7
var socket = io();

document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
var deviceName = document.getElementById('deviceName').value || "匿名设备";
socket.emit('send text', { text, deviceName });
};
+ +

现在,在发送消息时,如果用户没有输入设备名称,它将显示为“匿名设备”,并且每个消息都会在设备名称后面带有IP地址。

+

当用户发送消息时,它们将以 [DeviceName (IP Address)]: Message 的格式显示。

+

0x5.1.2 iPhone页面自适应

现在我希望在iPhone的浏览器中更好的显示,因为我在iPhone中需要先缩小页面,然后在移动到页面中间再放大才能继续输入消息

+

为了确保页面在移动设备上正常显示,需要添加一个viewport元数据标签来控制页面的缩放。

+

index.html文件的<head>部分,添加以下meta标签:

+
1
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+ +

所以<head>部分现在应该是这样的:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<head>
<title>Realtime Text</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f4f4f4;
}
.container {
padding: 20px;
border: 1px solid #ddd;
background-color: #fff;
}
</style>
</head>
+ +

添加此元数据标签后,页面应该会根据设备的屏幕大小自动调整缩放,并且在iPhone等移动设备上应该可以正常显示。

+

0x5.2 图像优化与传输

0x5.2.1 图像传输逻辑

    +
  1. 在前端添加一个文件输入以选择图片
  2. +
  3. 上传图片到服务器
  4. +
  5. 服务器保存图片并返回一个可访问的URL
  6. +
  7. 在客户端中显示图片
  8. +
+

修改index.html来添加文件输入,在<div class="container">内添加一个文件输入:

+
1
<input type="file" id="imageUpload" accept="image/*">
+ +

并修改发送按钮的逻辑来检查是否选择了图片:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
var deviceName = document.getElementById('deviceName').value || "匿名设备";
var imageFile = document.getElementById('imageUpload').files[0];

if (imageFile) {
var formData = new FormData();
formData.append('image', imageFile);
formData.append('deviceName', deviceName);
formData.append('text', text);

fetch('/upload-image', {
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
socket.emit('send text', data);
});
} else {
socket.emit('send text', { text, deviceName });
}
};
+ +

multer在服务器上处理图像上传,它是一个node.js中间件,用于处理multipart/form-data,主要用于上传文件

+

然后,在server.js中添加以下代码来处理图片上传:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const multer = require('multer');
const path = require('path');

const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/')
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname))
}
});

const upload = multer({ storage: storage });

app.post('/upload-image', upload.single('image'), (req, res) => {
let imageUrl = `/uploads/${req.file.filename}`;
res.json({ text: req.body.text, deviceName: req.body.deviceName, imageUrl: imageUrl });
});

app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
+ +

确保在服务器目录中创建一个名为uploads的文件夹来存储上传的图片

+

修改前端显示逻辑,修改socket.on('receive text', ...)逻辑以处理可能的图片消息:

+
1
2
3
4
5
6
7
8
9
socket.on('receive text', function(data) {
var li = document.createElement("li");
var content = `[${data.deviceName}]: ${data.text}`;
if (data.imageUrl) {
content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
}
li.innerHTML = content;
document.getElementById('messages').appendChild(li);
});
+ +

现在,应该能够在前端选择图片,上传到服务器,并在接收消息时看到图片显示

+

0x5.2.2 图片显示与优化

现在我发现在发送多条消息时,发送消息的控件显示在屏幕之外时无法再继续输入消息了

+

这个问题出现的原因是,随着消息的增加,页面内容增长,但浏览器并没有自动滚动以保持输入控件在可见范围内。

+

为了解决这个问题,我们可以强制页面在添加新消息后滚动到底部。这样,输入框和按钮始终保持在屏幕上的可见范围内

+

首先更新HTML结构,我们需要对HTML的结构进行一些修改以分离消息列表和输入区域。将输入区域和按钮放入一个固定的底部工具栏中,使其始终位于屏幕底部。

+

index.html<div class="container">应该更改为以下结构:

+
1
2
3
4
5
6
7
8
9
10
<div class="container">
<ul id="messages" style="max-height: 80vh; overflow-y: auto;"></ul>

<div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 10px;">
<input type="text" id="deviceName" placeholder="Device Name">
<textarea id="textArea" placeholder="Type your message..."></textarea>
<input type="file" id="imageUpload" accept="image/*">
<button id="sendButton">Send</button>
</div>
</div>
+ +

上述更改将消息列表的最大高度限制为视口的80%,并为其添加了滚动条。发送消息的工具栏被固定在底部。

+

添加自动滚动逻辑,在每次添加消息到列表后,执行滚动到底部的逻辑

+

socket.on('receive text', ...)的末尾添加以下代码:

+
1
document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
+ +

这段代码会将消息列表滚动到最底部,确保新消息总是可见的。

+

这些更改应该确保无论发送多少消息,输入框和按钮始终保持在屏幕上的可见范围内。

+
消息多发导致标签被遮挡

我们需要对HTML和CSS进行调整以确保”Received Messages:”标签始终可见。

+

以下是如何修改的,修改HTML结构

+

index.html中,将”Received Messages:”标签从当前的位置移到工具栏上方,并为其添加一个包围元素:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="container">
<div id="header" style="background-color: white; padding: 10px;">
Received Messages:
</div>

<ul id="messages" style="max-height: 70vh; overflow-y: auto;"></ul>

<div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 10px;">
<input type="text" id="deviceName" placeholder="Device Name">
<textarea id="textArea" placeholder="Type your message..."></textarea>
<input type="file" id="imageUpload" accept="image/*">
<button id="sendButton">Send</button>
</div>
</div>
+ +

调整CSS样式,修改<ul id="messages">的样式来考虑标题的高度。确保消息列表在标题和工具栏之间:

+
1
2
3
4
5
#messages {
margin-top: 30px;
max-height: 70vh;
overflow-y: auto;
}
+ +

这些更改应该确保”Received Messages:”标签不会被挡住,并且在添加消息时,列表仍然会正确滚动。

+

0x5.3 iPhone界面与交互优化

0x5.3.1 点击图片放大原尺寸

为了实现图片点击放大的效果,可以使用一个简单的模态窗口(modal)。当用户点击图片时,模态窗口会显示,并且在其中展示原始尺寸的图片。当用户点击模态窗口之外的任何位置或按下Esc键时,模态窗口会关闭。

+

index.html<body> 末尾添加模态窗口的HTML结构:

+
1
2
3
4
<!-- Image Modal -->
<div id="imageModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 9999; align-items: center; justify-content: center;">
<img id="modalImage" src="" style="max-width: 90%; max-height: 90%;">
</div>
+ +

添加显示和关闭模态窗口的逻辑,在现有的JavaScript代码下添加以下代码:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
document.addEventListener('click', function(event) {
// If an image inside the messages is clicked
if (event.target.tagName === 'IMG' && event.target.closest('#messages')) {
var modal = document.getElementById('imageModal');
var modalImage = document.getElementById('modalImage');

modalImage.src = event.target.src;
modal.style.display = 'flex';

modal.addEventListener('click', function() {
modal.style.display = 'none';
});
}
});

// Close modal on pressing 'Escape'
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
var modal = document.getElementById('imageModal');
modal.style.display = 'none';
}
});
+ +

这些代码首先会检查是否点击了消息列表中的图片。如果是,则显示模态窗口并设置图片的源。点击模态窗口的任何位置都会关闭它。如果按下’Escape’键,也会关闭模态窗口。

+

现在,当点击图片时,应该能够在模态窗口中放大显示图片,而点击模态窗口的任何位置或按下Esc键都会关闭它。

+

0x5.3.2 iPhone浏览器send按钮超屏

这个问题可能是由于固定底部工具栏的内容太多或样式设置不合适,在某些屏幕尺寸或设备上导致了溢出。为了解决这个问题,我们需要对工具栏的布局和样式进行调整,确保在较小的屏幕上也能适应。

+

我们可以采用以下策略:

+
    +
  1. 将输入框和发送按钮放在一行。
  2. +
  3. 调整宽度和样式以适应不同的屏幕尺寸。
  4. +
  5. 使用flex布局简化样式和布局。
  6. +
+

更新HTML结构,在index.html中,将工具栏部分修改为:

+
1
2
3
4
5
6
7
8
<div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1);">
<div style="display: flex; align-items: center;">
<input type="text" id="deviceName" placeholder="Device Name" style="flex: 1; margin-right: 5px;">
<input type="file" id="imageUpload" accept="image/*" style="margin-right: 5px;">
<textarea id="textArea" placeholder="Type your message..." style="flex: 2; margin-right: 5px;"></textarea>
<button id="sendButton">Send</button>
</div>
</div>
+ +

调整样式,确保每个元素都有合适的宽度和间距,使其适应小屏幕。这里,我们使用了flex布局,让deviceNametextArea元素根据剩余空间动态调整其宽度。

+

现在,工具栏应该能够更好地适应较小的屏幕,如iPhone的浏览器,并且发送按钮应该始终可见和可用。可能需要根据实际设备和屏幕尺寸进行进一步的微调。

+

0x5.3.3 iPhone输入框超出屏幕

为了在iPhone的浏览器中获得更好的响应式布局,我们可以采用以下策略:

+
    +
  1. 使用媒体查询来针对不同的屏幕尺寸进行样式调整。
  2. +
  3. 在较小的屏幕上,考虑简化布局,可能需要将一些控件移至其他位置。
  4. +
+

更新HTML结构,在index.html中,继续使用上一个给出的工具栏HTML结构。

+

添加CSS媒体查询,我们将为小屏幕(如iPhone)添加特定的样式。为此,在的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
/* 大屏幕的默认样式 */
#deviceName, #textArea {
flex: 1;
margin-right: 5px;
}

/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
#deviceName {
display: none; /* 在小屏幕上隐藏设备名称输入 */
}

#imageUpload {
width: 40px; /* 根据需要调整此值 */
margin-right: 5px;
}

#textArea {
flex: 3; /* 给文本区域更多的空间 */
}

#sendButton {
flex-shrink: 0; /* 防止按钮收缩 */
}
}
+ +

这里的做法是,当屏幕宽度小于或等于500px时(可以根据需要调整此值),隐藏了deviceName输入框以节省空间,并给予textArea更多的空间。此外,还调整了上传按钮和发送按钮的尺寸和样式,以更好地适应小屏幕。

+

这些调整应该会改进iPhone浏览器上的布局,使其更加友好,可能需要根据实际设备和屏幕尺寸进行进一步的微调

+

0x5.3.4 iPhone按钮输入框重叠修正

为了实现这个目标,我们需要对较小屏幕的布局进行改进,使其变为多行排列。

+

更新HTML结构,在index.html中,继续使用上一个给出的工具栏HTML结构。

+

调整CSS媒体查询以实现多行排列布局,我们将在小屏幕上采用多行排列布局。添加或修改以下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
/* 大屏幕的默认样式 */
#deviceName, #textArea {
flex: 1;
margin-right: 5px;
}

/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
/* 对于较小的屏幕,将伸缩方向更改为列 */
div[style*="position: fixed; bottom: 0;"] > div {
flex-direction: column;
}

#deviceName {
margin-bottom: 5px;
}

#imageUpload {
margin-right: 0; /* 重置margin-right */
margin-bottom: 5px;
width: 100%;
}

#textArea {
margin-right: 0; /* 重置margin-right */
margin-bottom: 5px;
flex: 3;
}

#sendButton {
width: 100%;
margin-bottom: 5px; /* 留出一点间距 */
}
}
+ +

这样的改变会使得在小屏幕上,设备名称、图片上传、文本输入框和发送按钮都单独占据一行,而不会相互重叠。这应该会在iPhone上提供更好的用户体验

+

0x5.3.5 iPhone布局对齐与点击优化

更新媒体查询

+
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
/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
/* 对于较小的屏幕,将伸缩方向更改为列 */
div[style*="position: fixed; bottom: 0;"] > div {
flex-direction: column;
}

#deviceName, #textArea {
margin-right: 0; /* 重置margin-right */
margin-bottom: 5px;
width: 100%; /* 全宽 */
height: 50px; /* 增加高度,便于触摸 */
}

#imageUpload {
margin-right: 0; /* 重置margin-right */
margin-bottom: 5px;
width: 100%; /* 全宽 */
}

#sendButton {
width: 100%; /* 全宽 */
height: 50px; /* 增加高度,便于触摸 */
margin-bottom: 5px; /* 留出一点间距 */
}
}
+ +

这些调整会使得在小屏幕上,每一个元素都占满了整个屏幕宽度,并且增大了输入框和按钮的高度,以便用户更容易地进行触摸操作。

+

这应该会提供更好的用户体验,特别是考虑到iPhone和其他移动设备上的拇指操作。可能还需要根据实际设备和需求进行进一步的微调。

+

0x5.3.6 控件边缘重叠与iPhone点击

    +
  1. 控件与屏幕边缘的间距问题
  2. +
  3. iPhone上首次加载时的交互问题
  4. +
+

设置控件与屏幕边缘的间距

+

为了增加边距,可以为工具栏内部的元素添加适当的间距。在的CSS中,可以添加或修改以下样式:

+
1
2
3
4
5
6
7
/* 为固定底部工具栏添加填充 */
div[style*="position: fixed; bottom: 0;"] {
padding-left: 10px;
padding-right: 10px;
}

/* ... (CSS的其余部分) */
+ +

这会为底部工具栏的左右两侧增加10px的间距。可以根据需要调整这个值

+

iPhone上首次加载时的交互问题

+

iPhone上的Safari浏览器和其他移动浏览器有一个常见的问题,即当页面首次加载时,固定的元素可能不会响应触摸事件。这是因为浏览器的地址栏和底部工具栏可能会覆盖页面的部分内容。通常,通过滚动页面可以暂时解决这个问题。

+

为了解决这个问题,可以尝试在页面首次加载时自动滚动页面一小段距离,这样可以确保底部工具栏不会被浏览器的界面元素覆盖。以下是如何实现:

+

index.html<script>标签内部,添加以下JavaScript代码:

+
1
2
3
4
5
window.addEventListener('load', function() {
setTimeout(function() {
window.scrollTo(0, 1);
}, 0);
});
+ +

上述代码会在页面加载完成后,立即将页面向上滚动1px。这样可以确保底部的固定工具栏不会被浏览器的界面元素覆盖,并且应该可以响应用户的触摸事件

+

0x5.3.7 iPhone上两侧间距问题

对于在iPhone上的边距问题,我们需要更具体地处理与小屏幕或特定设备相关的CSS样式。

+

下面是如何更具体地针对较小屏幕(如iPhone)设置边距:

+

首先,确保已经使用了适当的元数据标签来控制视口。在的HTML文件的<head>部分,添加以下标签:

+
1
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+ +

这会确保页面的宽度匹配设备的宽度,并且页面的初始缩放级别是1.0。

+

接下来,修改的CSS媒体查询以增加边距。将下面的代码添加或合并到的CSS中:

+
1
2
3
4
5
6
7
8
9
10
11
12
/* 大屏幕的默认样式 */
/* ... (你现在的样式) */

/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
/* ... (你当前的小屏幕风格) */

/* 为整个底部工具栏添加填充 */
div[style*="position: fixed; bottom: 0;"] {
padding: 0 10px; /* 这将在左右两边添加10px的内边距 */
}
}
+ +

通过添加这些更改,iPhone上的边距应该会生效,并且页面内容会根据设备的屏幕宽度适当地调整。

+

0x5.3.8 iPhone输入框宽度异常

这个问题可能是由于paddingwidth: 100%的组合引起的。当我们为一个元素同时设置了paddingwidth: 100%时,该元素的总宽度会超过其父元素的宽度,因为这里的宽度是指内容宽度,而不包括边距、边框或内边距。

+

为了解决这个问题,可以使用box-sizing属性,将其设置为border-box,这样元素的总宽度将包括内容、内边距和边框(但不包括外边距)。

+
    +
  1. 保证边距。
  2. +
  3. 使用flexbox来确保子元素在小屏幕上垂直排列。
  4. +
+

在CSS中,找到与小屏幕相关的媒体查询,并进行如下修改:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@media (max-width: 500px) {
div[style*="position: fixed; bottom: 0;"] {
width: 100%;
padding: 5px;
box-sizing: border-box;
}

#deviceName, #textArea, #imageUpload, #sendButton {
box-sizing: border-box;
width: 100%;
margin-bottom: 5px;
}

#deviceName, #textArea {
height: 50px;
}

#sendButton {
height: 50px;
}
}
+ +

这些样式应该确保了在小屏幕设备上,特别是iPhone上,所有的控件都正常排列,并且有足够的边距。

+

0x5.3.9 iPhone布局校验与滑动优化

    +
  1. 控件应该按照一行行排列: 这需要调整底部控件的样式。当前的代码中已经使用了flex-direction: column,这是正确的,但的底部控件的子控件仍然在一个flex容器中,这使得它们在同一行上。需要分离它们以使它们在单独的行上。
  2. +
  3. 两侧与屏幕保持10px的间距: 可以在底部控件中设置padding-leftpadding-right10px
  4. +
  5. 第一次打开页面需要滚动一小段距离: 已经有了这个功能,使用window.scrollTo(0, 1)在页面加载时向下滚动。这通常用于隐藏浏览器的地址栏,但在最新版本的iOS中可能不起作用。
  6. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* ... 现有的样式 ... */

@media (max-width: 500px) {
/* ... 其他样式 ... */

div[style*="position: fixed; bottom: 0;"] {
padding-left: 10px;
padding-right: 10px;
display: flex;
flex-direction: column;
}

#deviceName, #textArea, #imageUpload, #sendButton {
width: 100%;
box-sizing: border-box;
margin-bottom: 5px;
}

#deviceName, #textArea, #sendButton {
height: 50px;
}
}
+ +
1
2
3
4
5
6
7
8
9
<!-- ... 现有的HTML ... -->

<!-- 调整底部工具栏结构 -->
<div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1);">
<input type="text" id="deviceName" placeholder="Device Name">
<input type="file" id="imageUpload" accept="image/*">
<textarea id="textArea" placeholder="Type your message..."></textarea>
<button id="sendButton">Send</button>
</div>
+ +

0x5.3.A iPhone布局遮挡与间距修正

    +
  1. 两侧控件的距离问题: 如果右侧和屏幕之间的距离是10px,但左侧并非如此,可能是某些样式或其他浏览器默认样式干扰了。可以考虑为每个子控件设置margin-leftmargin-right,以确保他们都有正确的边距。
  2. +
  3. 图片消息被底部的输入框挡住: 一个常见的方法是为消息列表添加底部内边距,该边距的高度与底部工具栏的高度相同,这样最后一条消息(无论是文本还是图片)都可以完全滚动到视图中而不被挡住。
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* ... 其他样式 ... */

@media (max-width: 500px) {
/* ... 其他样式 ... */

#deviceName, #textArea, #imageUpload, #sendButton {
margin-left: 10px;
margin-right: 10px;
/* ... 现有的其他样式 ... */
}

#messages {
padding-bottom: 70px;
}
}
+ + + +

0x5.4 消息发送与设备名自动填充

0x5.4.1 发送后清除已选图像

为了在发送完消息后清除上次选择的图像,可以在发送消息的函数中清除 <input type="file"> 的值。这将重置文件输入,并确保下次用户点击发送按钮时不会再次发送同一张图片。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
var deviceName = document.getElementById('deviceName').value || "匿名设备";
var imageFile = document.getElementById('imageUpload').files[0];

if (imageFile) {
var formData = new FormData();
formData.append('image', imageFile);
formData.append('deviceName', deviceName);
formData.append('text', text);

fetch('/upload-image', {
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
socket.emit('send text', data);

// 在此处清除文件输入的值
document.getElementById('imageUpload').value = "";
});
} else {
socket.emit('send text', { text, deviceName });
}
};
+ +

以上的修改确保了每次发送图像消息后,文件输入都会被清除。这样,用户在发送下一条消息时,之前选择的图片不会再次被发送

+

0x5.4.2 自动填充设备名称

为了自动获取设备名称,首先需要明确一个事实:Web 浏览器出于隐私和安全的考虑,并不允许直接获取设备的真实名称。但是,可以使用一些方法来近似识别或标识设备,例如使用浏览器的用户代理字符串。

+

用户代理 (User Agent) 字符串包含关于浏览器、操作系统和设备的信息,可以通过这些信息来推断或标识用户的设备类型

+
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
function getDeviceNameFromUserAgent() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;

// Windows Phone必须先行一步,因为它的用户界面也包含“Android”。
if (/windows phone/i.test(userAgent)) {
return "Windows Phone";
}

if (/android/i.test(userAgent)) {
return "Android Device";
}

// iOS检测
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return "iOS Device";
}

if (/macintosh|mac os x/i.test(userAgent)) {
return "Mac";
}

if (/windows/i.test(userAgent)) {
return "Windows PC";
}

return "Unknown Device";
}

// 使用函数来自动填充设备名称
document.getElementById('deviceName').value = getDeviceNameFromUserAgent();
+ +

此函数根据用户代理字符串来近似地识别设备,并返回一个描述性名称。注意,这只是一个基本的示例,而真实的环境中可能需要更复杂的逻辑来准确识别设备。

+

这种方法只是提供了一个近似的设备名称,并不能真正的识别具体的设备型号或品牌

+

0x5.4.3 消息发送后自动清空输入框

为了在发送完消息后自动清除 textArea (你之前的代码中使用这个ID代表消息输入框)中的值,你可以在发送消息的函数中添加一行代码来实现这个功能

+
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
document.getElementById('sendButton').onclick = function() {
var text = document.getElementById('textArea').value;
var deviceName = document.getElementById('deviceName').value || "匿名设备";
var imageFile = document.getElementById('imageUpload').files[0];

// 如果选择了图片,则发送图片
if (imageFile) {
var formData = new FormData();
formData.append('image', imageFile);
formData.append('deviceName', deviceName);
formData.append('text', text);

fetch('/upload-image', {
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
socket.emit('send text', data);
});
} else {
// 如果没有选择图片,则直接发送文本消息
socket.emit('send text', { text, deviceName });
}

// 在所有情况下都清除textArea和图片选择器的值
document.getElementById('textArea').value = '';
document.getElementById('imageUpload').value = ''; // 这将清除文件输入选择器的内容
};
+ +

我在函数的底部添加了清除textArea和图片选择器imageUpload的代码。这样,无论是否选择了图片,都会在发送完消息后清除文本框和图片选择器的内容。

+

0x5.5 大屏适配与控件布局调整

0x5.5.1 Windows居中 iPhone逐行

修改CSS以在大屏幕上对底部控件进行水平居中:

+
1
2
3
4
5
6
7
8
9
10
11
12
.bottom-controls {
display: flex;
justify-content: center; /* 内容器水平居中 */
}

.bottom-controls > div {
display: flex; /* 这确保了内部容器的子元素水平对齐 */
align-items: center;
}

/* ... 其他样式 ... */

+ +

使用媒体查询来特定地为小屏幕(例如iPhone)定义样式:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
.bottom-controls {
justify-content: flex-start; /* 这将使内部容器与起始位置对齐 */
}
.bottom-controls > div {
flex-direction: column; /* 这确保了内部容器的子容器是垂直堆叠的 */
align-items: center;
width: 100%;
}
#deviceName, #textArea, #imageUpload, #sendButton {
display: block;
width: 100%; /* 确保控件在小屏幕上占满宽度 */
}
/* ... 你的其他小屏幕风格 ... */
}
+ +

这样,底部的控件在大屏幕上会水平居中,而在小屏幕(如iPhone)上,则会按照行排列。

+

0x5.5.2 扩大大屏幕底部控件尺寸

为了在大屏幕上使控件更大,可以使用媒体查询来区分大屏幕和小屏幕的样式。在大屏幕的样式中,可以增加控件的高度和字体大小。

+
1
2
3
4
5
6
7
8
9
10
11
/* 大屏幕的样式(例如,Windows, MacBook) */
@media (min-width: 501px) {
#deviceName, #textArea, #imageUpload, #sendButton {
height: 60px; /* 增加控件的高度 */
font-size: 18px; /* 增加字体大小以获得更好的可视性 */
padding: 10px; /* 为美观添加一些填充 */
}
#sendButton {
padding: 10px 20px; /* 调整按钮的填充 */
}
}
+ +

这段代码将确保在宽度超过501px的屏幕上,控件的高度、字体大小和内部间距都增加了,从而更适合大屏幕使用。可以根据实际需要进一步调整这些值。

+

0x5.5.3 大屏幕输入框长度及控件间距

为了在大屏幕上调整输入框的长度并在输入框和按钮之间添加合适的距离,可以进行以下修改:

+
    +
  1. 在大屏幕上为输入框和按钮添加合适的间距
  2. +
  3. 为大屏幕上的输入框增加宽度
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 大屏幕的样式(例如,Windows, MacBook) */
@media (min-width: 501px) {
#deviceName, #textArea, #imageUpload, #sendButton {
height: 60px;
font-size: 18px;
padding: 10px;
margin-right: 10px;
}
#sendButton {
padding: 10px 20px;
}
#textArea {
width: 60%;
}
/* 可以移除伸缩容器中最后一个元素的右距 */
div[style*="position: fixed; bottom: 0;"] > div > *:last-child {
margin-right: 0;
}
}
+ +

这段代码增加了#textArea的宽度并在所有底部控件之间添加了margin-right。为了确保最后一个元素(按钮)不与其父元素的右边界有太大的间隔,我添加了一个特定选择器来移除其右边距

+

0x5.6 增加深浅色主题

    +
  1. 在HTML中添加一个切换按钮。
  2. +
  3. 为深色模式定义CSS样式。
  4. +
  5. 使用JavaScript监听按钮点击事件来切换主题。
  6. +
+

更新index.html如下:

+
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
[data-theme="dark"] input, [data-theme="dark"] textarea, [data-theme="dark"] button {
color: white; /* 将文字颜色设置为白色 */
border-color: white; /* 如果需要, 也可以设置边框颜色 */
}

:root {
/* 浅色模式的设置 */
--text-color: black;
--background-color: white;
--input-bg: #fff;
--container-bg: #fff;
--header-bg: #fff;
--button-color: black; /* 新增 */
}

[data-theme="dark"] {
/* 深色模式的设置 */
--text-color: black; /* 文本颜色为黑色 */
--background-color: white; /* 背景颜色为白色 */
}

.bottom-controls {
display: flex;
justify-content: center;
}
.bottom-controls > div {
display: flex;
align-items: center;
}
/* 为固定底部工具栏添加填充 */
div[style*="position: fixed; bottom: 0;"] {
padding-left: 10px;
padding-right: 10px;
}
/* 保证body的背景颜色被应用 */
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--background-color) !important;
color: var(--text-color);
font-family: "JetBrainsMono", Arial, sans-serif;
}
/* 如果有特定的元素想要应用不同的文本或背景颜色,可以为它们单独设置: */
.element {
color: var(--text-color);
background-color: var(--background-color);
}
.container {
padding: 20px;
border: 1px solid #ddd;
background-color: #fff;
}
/* 小屏幕的样式(如iphone) */
@media (max-width: 500px) {
.bottom-controls {
justify-content: flex-start;
}
.bottom-controls > div {
flex-direction: column;
align-items: center;
width: 100%;
}

/* 更新底部控件的容器样式 */
div[style*="position: fixed; bottom: 0;"] {
background-color: var(--container-bg);

padding-left: 10px;
padding-right: 10px;
display: flex;
flex-direction: column;
}

/* 确保所有控件拉伸以填充可用宽度 */
#deviceName, #textArea, #imageUpload, #sendButton {
background-color: var(--container-bg);

width: 100%;
box-sizing: border-box;
margin-bottom: 5px;
}

/* 增加高度,便于触摸 */
#deviceName, #textArea, #sendButton {
height: 50px;
}
/* 在邮件列表的底部添加填充 */
#messages {
padding-bottom: 70px;
}
}
/* 大屏幕的样式(例如,Windows, MacBook) */
@media (min-width: 501px) {
#deviceName, #textArea, #imageUpload, #sendButton {
background-color: var(--input-bg);

height: 60px;
font-size: 18px;
padding: 10px;
margin-bottom: 10px;
}
#textArea {
width: 50%;
}
#sendButton {
padding: 10px 20px;
margin-left: 10px;
}
}
body, button {
background-color: var(--bg-color);
color: var(--text-color);
}
/* 为了确保按钮的文本颜色能够在两种主题下都清晰可见 */
button {
color: var(--button-color);
}
/* 底部控件的文本颜色 */
.bottom-controls input,
.bottom-controls textarea,
.bottom-controls button {
color: var(--text-color);
border-color: var(--text-color);
}
.container {
background-color: var(--container-bg);
}
body.dark-theme {
/* 深色模式的设置 */
--text-color: white;
--background-color: #555;
--input-bg: #555;
--container-bg: #444;
--header-bg: #444;
--button-color: white; /* 新增 */
}
#header {
background-color: var(--header-bg);
}
+ +
1
2
3
4
// 切换主题
document.getElementById('themeToggle').addEventListener('click', function() {
document.body.classList.toggle('dark-theme');
});
+ +

现在,当点击”Toggle Theme”按钮,页面的主题应该会在深色和浅色模式之间切换,并且文本应该会根据相应的模式改变颜色。

+

0x6 导入自定义字体

    +
  1. 选择字体: 首先,需要一个字体文件。这可以是.ttf.woff.woff2.eot等格式。确保拥有使用该字体的权利,特别是如果打算在商业项目中使用。
  2. +
  3. 字体文件上传: 将字体文件上传到的服务器或CDN,并确保知道文件的URL。
  4. +
  5. 在CSS中导入字体: 在CSS文件或<style>标签中,使用@font-face规则来定义和导入字体。
  6. +
+

下载JetBrains Mono字体,解压JetBrainsMono-Regular.ttfJetBrainsMono-Regular.woff2到网站根目录

+
1
2
3
4
5
6
7
@font-face {
font-family: "JetBrainsMono"; /* 可以为字体起一个名字 */
src: url('JetBrainsMono-Regular.woff2') format('woff2'), /* 最优先 */
url('JetBrainsMono-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
+ +

在这里,path-to-your-font应该是字体文件的URL。如果有多种格式的字体文件,列出它们都是一个好主意,因为这可以确保更好的兼容性

+
1
2
3
4
5
/* 保证body的背景颜色被应用 */
body {
/* ... 其他样式 ... */
font-family: "JetBrainsMono", Arial, sans-serif;
}
+ +

在这里,"MyCustomFont"是我们为自定义字体定义的名字,Arial, sans-serif是后备字体,用于在某些情况下(例如,如果自定义字体加载失败)

+

考虑性能:字体文件可能很大,所以考虑只包括真正需要的字体权重和样式。例如,如果只需要常规和加粗,那么不要加载斜体或其他权重

+

0x7 添加自动加载历史记录功能

如果你想添加一个自动加载历史记录的功能,你需要在服务器端存储这些历史消息。这样,当新用户连接时,你就可以将这些历史消息发送给他们。

+

更新server.js如下:

+

在服务器上添加一个变量来存储消息

+
1
const messageHistory = [];
+ +

当接收到新的文本消息时,将它添加到历史记录中

+
1
2
3
4
5
6
7
8
socket.on('send text', (data) => {
// ...(其他代码不变)

// 将消息添加到历史记录
messageHistory.push(data);

io.emit('receive text', data);
});
+ +

当新用户连接时,发送所有历史消息给他们

+
1
2
3
4
5
6
7
8
9
10
io.on('connection', (socket) => {
console.log('a user connected');

// 发送历史消息给新连接的用户
messageHistory.forEach((message) => {
socket.emit('receive text', message);
});

// ...(其他代码不变)
});
+ +

index.html在客户端的 JavaScript 里,你不需要做任何修改。因为客户端已经设置了一个监听函数来处理 ‘receive text’ 事件,所以当它接收到历史消息时,会自动将其添加到消息列表。

+

这样,当有新用户加入聊天时,服务器就会自动发送所有历史消息给他们

+

0x8 添加消息代码格式化和时间戳

为了在消息中添加发送时间,可以在服务器端获取当前时间,并将其与其他数据一起发送。同时,也需要在客户端的JavaScript中修改接收数据的部分,以显示这一时间。

+

首先,在 server.js'send text' 事件处理程序中添加发送时间:

+
1
2
3
4
5
6
7
8
9
10
11
socket.on('send text', (data) => {
// ...其它代码

// 添加消息发送时间
data.timestamp = new Date().toLocaleString();

// 将新消息添加到历史记录
messageHistory.push(data);

io.emit('receive text', data);
});
+ +

修改 index.html 在接收消息部分,添加代码格式支持和时间戳

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
socket.on('receive text', function(data) {
var li = document.createElement("li");
var time = data.timestamp || new Date().toLocaleString(); // 获取时间戳,如果没有则使用当前时间

var content = `[${data.deviceName} @ ${time}]: `; // 显示设备名和时间
if (data.text.startsWith("```") && data.text.endsWith("```")) {
// 如果文本以 "```" 开头和结尾,将其识别为代码
content += `<pre>${data.text.slice(3, -3)}</pre>`;
} else {
content += `${data.text}`;
}

if (data.imageUrl) {
content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
}

li.innerHTML = content;
document.getElementById('messages').appendChild(li);
});
+ +

这样,如果用户在 textArea 输入的文本以 **”```”** 开头和结尾,这段文本将会被认为是代码,并用 <pre> 标签进行包裹,以维持其格式。同时,每条消息也会显示发送时间。

+

0x9 增加消息删除与数据库

0x9.1 增加消息删除

创建一个复选框要对应每条消息,所以必须设置每条消息和复选框的ID保持一致,在选择复选框时应该同时删除后面的消息

+

修改index.html文件

+
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
var messageId = 0;  // 初始化一个全局变量
socket.on('receive text', function(data) {
var li = document.createElement("li");

// 创建复选框
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.className = "messageCheckbox";
li.appendChild(checkbox); // 将复选框添加到列表项中

var time = data.timestamp || new Date().toLocaleString(); // 获取时间戳,如果没有则使用当前时间

var content = `[${data.deviceName} @ ${time}]: `; // 显示设备名和时间
if (data.text.startsWith("```") && data.text.endsWith("```")) {
// 如果文本以 "```" 开头和结尾,将其识别为代码
content += `<pre>${data.text.slice(3, -3)}</pre>`;
} else {
content += `${data.text}`;
}

if (data.imageUrl) {
content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
}

li.innerHTML += content;
document.getElementById('messages').appendChild(li);

li.setAttribute('data-message-id', messageId); // 为每个消息设置唯一ID
messageId++;

console.log("设备名称和IP地址:", data.deviceName);
});
+ +

向服务器发送删除请求

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
document.getElementById('deleteButton').onclick = function() {
var selectedMessages = document.querySelectorAll('.messageCheckbox:checked');
var messageIdsToDelete = [];
selectedMessages.forEach(function(checkbox) {
var li = checkbox.closest('li');
messageIdsToDelete.push(li.getAttribute('data-message-id'));
li.remove();
});
socket.emit('delete messages', messageIdsToDelete);

// 向后端发送删除请求
messageIdsToDelete.forEach(id => {
fetch(`/message/${id}`, {
method: 'DELETE'
}).then(response => {
if (!response.ok) {
// 以某种方式处理错误响应
console.error('从服务器删除消息失败');
}
});
});
};
+ +

向所有客户端广播消息ID,以供删除

+
1
2
3
4
5
6
7
socket.on('delete messages', function(messageIds) {
messageIds.forEach(function(messageId) {
var li = document.querySelector('li[data-message-id="' + messageId + '"]');
if (li) li.remove();
});
io.emit('delete messages', messageIds);
});
+ +

0x9.2 前端数据库联动删消息

0x9.2.1 创建数据库

修改server.js文件

+
1
const db = new sqlite3.Database('./messages.db');
+ +

0x9.2.2 创建表

创建message表,如果存在则什么都不做,包含id、content、deviceName、 ipAddress、 timestamp字段

+
1
2
3
4
5
6
7
db.run("CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, deviceName TEXT, ipAddress TEXT, timestamp TEXT)", (err) => {
if (err) {
console.error("创建/检查表错误:", err);
return;
}
console.log("表检查/创建成功");
});
+ +

0x9.2.3 读取数据库所有消息

从数据库中查询所有的消息记录,并将每条消息存储到messageHistory数组中,若查询过程中出现错误,会在控制台中打印错误信息

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
db.all("SELECT id, content, deviceName, ipAddress, timestamp FROM messages", [], (err, rows) => {
if (err) {
console.error("从数据库获取历史消息出错:", err);
return;
}
// 遍历数据库每条记录
rows.forEach(row => {
const message = {
id: row.id, // 确保这里保留 id
text: row.content,
deviceName: row.deviceName,
ipAddress: row.ipAddress,
timestamp: row.timestamp
};
// 将数据库中的数据保存在 messageHistory
messageHistory.push(message);
});
console.log("从数据库加载历史消息");
});
+ +

0x9.2.4 保存前端消息到数据库

定义了一个HTTP POST路由/send,用于从请求体中接收消息内容,并在消息内容存在时将其存储到数据库中,若消息内容为空,则返回一个错误响应

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.post('/send', (req, res) => {
const messageContent = req.body.message;

if (messageContent) {
// 存储消息到数据库
db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)", [data.text, data.deviceName, clientIpAddress, data.timestamp],
(err) => {
if (err) {
console.error("插入数据库出错:", err);
return;
}
console.log('收到的信息:', data.text || messageContent);
});
} else {
res.status(400).send({error: "消息内容为空"});
}
});
+ +

0x9.2.5 广播所有消息给用户

当一个用户与服务器建立Socket连接时的操作:首先打印出用户已连接的消息,然后将存储在messageHistory数组中的每条历史消息(经过修改以显示设备名和IP地址)发送给新连接的用户

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
io.on('connection', (socket) => {
console.log('有一个用户连接');

function formatMessageForEmission(originalMessage) {
return {
...originalMessage,
deviceName: `${originalMessage.deviceName} (${originalMessage.ipAddress})`
};
}

// 发送历史消息给新连接的用户
messageHistory.forEach((message) => {
socket.emit('receive text', formatMessageForEmission(message));
// console.log("format的设备和IP地址:",message)
});
+ +

0x9.2.6 广播Socket客户端消息

当服务器通过Socket接收到客户端发送的send text事件时,首先获取和处理客户端的IP地址,为没有提供设备名称的消息设置默认名称,然后将消息及相关信息添加到数据库中。之后,会修改消息的设备名称,将新消息添加到历史记录数组,并通过Socket广播这条消息给所有连接的客户端

+
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
socket.on('send text', (data) => {
// 获取客户端的IP地址
let clientIpAddress = socket.request.connection.remoteAddress.replace(/^::ffff:/, ''); // 清除IPv6前缀

if (clientIpAddress === '::1' || clientIpAddress === '127.0.0.1') {
clientIpAddress = 'Localhost';
}

// 添加ipAddress字段到data对象
data.ipAddress = clientIpAddress;

// 默认设备名称
if (!data.deviceName || data.deviceName.trim() === "") {
data.deviceName = "匿名设备";
}

// 添加消息发送时间
data.timestamp = new Date().toLocaleString();

// 添加消息到数据库中
db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)",
[data.text, data.deviceName, clientIpAddress, data.timestamp],
function(err) { // 使用函数关键字以便访问 this.lastID
if (err) {
console.error("插入数据库出错:", err);
return;
}
data.id = this.lastID; // 获取新插入的行的 ID
messageHistory.push(data); // 将新消息添加到历史记录
console.log('通过套接字收到的消息:', data.text);
}
);

io.emit('receive text', formatMessageForEmission(data));
});
+ +

0x9.2.7 处理DELETE请求删数据库消息

当收到HTTP DELETE请求时,根据指定的消息ID从数据库中删除相应的消息记录;若删除过程中出现错误,则返回错误信息,否则通知请求者消息已被删除。如果未提供有效的消息ID,则返回“无效的消息ID”错误。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app.delete('/message/:id', (req, res) => {
const messageId = req.params.id;
if (messageId) {
db.run("DELETE FROM messages WHERE id=?", [messageId], (err) => {
if (err) {
console.error("从数据库删除消息出错:", err);
res.status(500).send({error: "删除消息失败"});
return;
}

// 从 messageHistory 数组中删除消息
const index = messageHistory.findIndex(message => message.id === Number(messageId));
if (index !== -1) {
messageHistory.splice(index, 1);
}

res.send({status: "消息已删除"});
});
} else {
res.status(400).send({error: "无效的消息ID"});
}
});
+ + + + + +

0xA Docker封装与测试

部署Node.js网站到QNAP NAS上的一个优雅的方式是使用Docker。这种方法提供了隔离,便于管理和升级,而不会影响主机系统

+

0xA.1 Docker环境准备

0xA.1.1 创建Dockerfile

    +
  1. 打开终端或命令提示符:导航到Node.js项目的根目录。

    +
  2. +
  3. 创建一个新的Dockerfile

    +

    如果使用的是Linux或Mac,可以在终端中输入以下命令:

    +
    1
    touch Dockerfile
    + +

    如果在Windows上,可以使用命令提示符或PowerShell并输入以下命令:

    +
    1
    echo. > Dockerfile
  4. +
  5. 编辑Dockerfile

    +

    使用喜欢的文本编辑器打开新创建的Dockerfile。例如,可以使用VS Code、Notepad++、vim等。

    +
  6. +
  7. 在Dockerfile中输入内容

    +

    复制上文给出的Dockerfile内容,并粘贴到的Dockerfile中:

    +
  8. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 或者选择的其他Node.js版本
FROM node:14 AS build-stage

# 设置工作目录
WORKDIR /usr/src/app

# 将package.json和package-lock.json文件复制到工作目录
COPY package*.json ./

# 安装应用程序的依赖项
RUN npm install

# 复制应用程序的源代码到工作目录
COPY . .

# 暴露端口
# 或的应用程序运行的其他端口
EXPOSE 3000

# 定义Docker容器的启动命令
CMD ["npm", "start"]
+ +

保存并关闭文件

+

0xA.1.2 构建Docker镜像

在项目的目录中执行以下命令,以构建一个Docker镜像:

+
1
docker build -t realtimetext .
+ +

要确保的镜像已经正确构建,可以使用以下命令列出所有本地的Docker镜像:

+
1
docker images
+ +

0xA.1.3 运行Docker容器

确保Docker已安装并正在运行,然后运行以下命令以启动的Node.js应用:

+
1
docker run -p 4000:3000 realtime_text
+ +

0xA.2 导出容器上传并运行

0xA.2.1 导出Docker容器

首先,需要确定一个有效的路径来保存tar文件

+
1
mkdir docker-images
+ +

查看当前运行的docker ID

+
1
docker ps
+ +

将容器提交为新的Docker镜像

+
1
docker commit [CONTAINER_ID] realtime_text
+ +

这应该会将的realtime_text镜像保存为realtime_text.tar文件,位于docker-images/目录下

+
1
docker save -o docker-images/realtime_text.tar realtime_text
+ +

0xA.2.2 上传Docker容器

    +
  1. 需要确保在QNAP中的FileStation创建一个名为Container的目录

    +
  2. +
  3. 在App Center中搜索并安装Container Station,等待服务启动完成就可以打开了

    +
  4. +
  5. 打开映像页面,点击右上角的导入,从本地计算机选择刚导出的Docker镜像文件realtime_text,再点击导入

    +
  6. +
+

0xA.2.3 运行Docker容器

在容器页面启动名为realtime_text的容器,查看其详细信息及Web URL:http://192.168.50.145:32769,就是部署后的网页地址

+

0xA.3 维护容器占用空间

长期发送图片消息会导致upload目录占用较大空间,必要情况下可以在容器页面选择容器后的设置按钮,选择执行/bin/sh控制台

+
1
2
cd uploads
rm *
+ + + + +

0xB 常见问题与解决方案

0xB.1 构建失败

    +
  • 在构建Docker时出现任何问题请先确保你在realtime_text根目录下

    +
  • +
  • 构建docker的镜像名必须是小写

    +
  • +
  • 构建docker镜像必须指定导出目录

    +
  • +
  • 如果你在Linux下创建docker请确保相关文件有执行的权限

    +
    1
    2
    chmod +x script_name.sh
    ./script_name.sh
  • +
+

0xB.2 端口占用

在运行时报告了一个端口占用错误

+
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
“node:events:491
throw er; // Unhandled 'error' event
^
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (node:net:1485:16)
at listenInCluster (node:net:1533:12)
at Server.listen (node:net:1621:7)
at Object.<anonymous> (D:\code\win-code\NodeJS\realtime_text\server.js:60:8)
at Module._compile (node:internal/modules/cjs/loader:1159:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
at Module.load (node:internal/modules/cjs/loader:1037:32)
at Module._load (node:internal/modules/cjs/loader:878:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:23:47
Emitted 'error' event on Server instance at:
at emitErrorNT (node:net:1512:8)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'EADDRINUSE',
errno: -4091,
syscall: 'listen',
address: '::',
port: 3000
}

Node.js v18.12.1”
+ +

这个错误 Error: listen EADDRINUSE: address already in use :::3000 意味着端口 3000 已经被其他程序或者相同的程序的另一个实例使用了。你有几种方式来解决这个问题:

+
    +
  1. 杀掉使用该端口的进程

    +
      +
    1. 在 Windows 上:
        +
      1. 打开命令行窗口,然后运行 netstat -ano | findstr :3000
      2. +
      3. 这会显示使用端口 3000 的所有进程。找到进程 ID(PID)。
      4. +
      5. 执行 taskkill /PID <PID> /F 来杀掉进程。
      6. +
      +
    2. +
    3. 在 macOS 或 Linux 上:
        +
      1. 打开终端,然后运行 lsof -i :3000
      2. +
      3. 这会显示使用端口 3000 的所有进程。找到进程 ID(PID)。
      4. +
      5. 执行 kill -9 <PID> 来杀掉进程。
      6. +
      +
    4. +
    +
  2. +
  3. 更改应用程序使用的端口

    +

    在你的 server.js 文件中,找到下面这行代码:

    +
    1
    const port = process.env.PORT || 3000;
    + +

    你可以更改 3000 到其他未被使用的端口号,例如:

    +
    1
    const port = process.env.PORT || 4000;
    + +

    然后重新运行你的应用程序。

    +
  4. +
  5. 重启计算机

    +

    如果上面的方法没有解决问题或者你不确定如何进行,重启计算机通常能够解决端口冲突的问题。

    +

    希望这能帮到你!

    +
  6. +
+

0xB.3 数据库自增ID列

DataGrip 是一个流行的数据库管理工具,允许你通过其图形界面来进行各种数据库操作。要在 DataGrip 中修改表的 id 列为自增,可以按照以下步骤进行:

+
    +
  1. 打开 DataGrip 并连接到你的 SQLite 数据库。

    +
  2. +
  3. 在左侧的 数据库浏览器 中,找到你的表。

    +
  4. +
  5. 右键点击 你想要修改的表,然后选择 Modify Table

    +
  6. +
  7. 在打开的窗口中,找到 id 列。

    +
  8. +
  9. 选中 id 列,然后在右侧的属性面板中找到 IdentityAuto Increment 选项并勾选。

    +
  10. +
  11. 右键点击 该列名,并从上下文菜单中选择 Set Primary Key

    +
  12. +
  13. 最后,点击窗口下方的 提交应用 按钮来保存更改。

    +
  14. +
+

请注意,不同的数据库系统和不同的版本可能会有些微差异。确保在进行此类操作之前备份你的数据。

+

如果 DataGrip 不直接支持 SQLite 的自增特性或有任何其他问题,你可能需要使用 SQL 语句来手动修改表结构,或者像之前所描述的那样,通过创建一个新表、复制数据、删除旧表、然后重命名新表的方式来达到目的。

+

0xB.4 确保前端页面与数据库内容同步

前端消息的ID与数据库ID对应。若前端删除消息失败,可能是ID不匹配或Docker中的sqlite3库问题。若需手动删除消息,以下是相关sql语句。不使用Docker容器可避免该问题,并注意Docker会将24小时制改为12小时制

+

0xB.4.1 连接数据库

1
sqlite3 messages.db
+ +

0xB.4.2 查询表

1
SELECT * FROM messages;
+ +

0xB.4.3 查ID列

1
SELECT id FROM messages;
+ +

0xB.4.4 删除指定ID行

1
DELETE FROM messages WHERE id = 5;
+ +

0xB.4.5 删除指定多行ID

1
DELETE FROM messages WHERE id IN (3, 4, 5);
+ +

0xB.4.6 查询指定ID列的值

1
SELECT * FROM messages WHERE id = 5;
+ +

0xB.4.7 查询指定多行ID列的值

1
SELECT * FROM messages WHERE id IN (3, 4, 5);
+ +

0xB.4.8 修改指定ID

在这个语句中,18 替代了第一个 ?21 替代了第二个 ?,而 21 替代了第三个 ?。这样的话,当你执行这个SQL语句时,它会将 messages 表中 rowid 为 21,且 id 为 21 的行的 id 字段的值更新为 18。

+
1
UPDATE "messages" SET "id" = 18 WHERE "rowid" = 21 AND "id" = 21
+ +

0xB.4.9 SSH连接正在运行的docker

1
docker exec -it <container_id_or_name> /bin/bash
+ +

0xB.4.A docker中安装vim

1
2
apt-get update
apt-get install -y vim
+ +

0xB.4.B 重置id列自增值

查询当前最大值

+
1
SELECT MAX(id) FROM messages;
+ +

将自增ID的SEQ设置为最大ID值:使用以下SQL语句来设置自增ID列的序列(sequence)为当前最大ID值

+
1
UPDATE sqlite_sequence SET seq = 31 WHERE name = 'messages';
+ +

删除消息后需重启docker容器生效

+

0xC 运行效果

等一会,加载图片比较慢

+

0xC.1 大屏幕设备

+

+

0xC.2 小屏幕设备

+ + + +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/index.html b/2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/index.html new file mode 100644 index 0000000..e1c4f82 --- /dev/null +++ b/2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/index.html @@ -0,0 +1,776 @@ + + + + + + + + + + + + Hexo环境配置 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ Hexo环境配置 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

电脑有时会出现一些奇怪的问题要重装系统,导致很多环境要重新配置。Hexo是环境配置中最复杂的每次都要在百度查很久,为此今天重新配置环境就顺便写一篇文章来记录。

+ + +

0x1 暂停QSync双向同步

重新初始化Hexo会覆盖整个目录,所以得先提前把博客备份出来。然后删除本地的Hexo目录里的文件夹和NAS端的,然后再打开QSync等待同步完成。

+

注意:只保留一个空的Hexo目录

+
1
2
> dir Hexo
0 个文件 0 字节
+ +

0x2 安装Node.js

    +
  1. 删除之前安装的Node.js安装目录,否则会安装失败

    +
  2. +
  3. 安装推荐的LTS版本,LTS是长期支持的稳定版本

    +
  4. +
  5. 检查安装是否成功,显示版本号就算没问题了

    +
    1
    2
    > node -v
    v20.11.1
  6. +
+

0x3 初始化blog

    +
  1. 打开Clash的TUN模式,在打开系统{代}{理},TUN表示{代}{理}所有应用程序。

    +
  2. +
  3. 在Hexo目录下打开终端,依次执行

    +
    1
    2
    3
    4
    5
    > npm install hexo-cli -g
    > hexo init blog
    > cd blog
    > npm install
    > hexo server
  4. +
  5. 如果一切顺利会显示一个本地服务器地址http://localhost:4000/,复制在浏览器里打开能够看到一个Hello World默认文章,就表示已经成功了。

    +
  6. +
+

0x4 运行自己的blog

    +
  1. 把刚才备份博客目录解压缩在Hexo目录下
  2. +
  3. 在自己blog目录下打开终端执行hexo s
  4. +
  5. 如果能够显示http://localhost:4000,并且能够正常在浏览器中打开就算本地配置完成了
  6. +
+

0x5 Gitee SSH配置

    +
  1. 设置Gitee账户和用户名

    +
    1
    2
    > git config --global user.email "you@example.com"
    > git config --global user.name "Your Name"
  2. +
  3. 生成SSH密钥

    +
    1
    > ssh-keygen -t rsa -C "Your Name"
  4. +
  5. SSH密钥保存位置使用默认路径,不使用密码。只需要连按三次回车即可

    +
    1
    2
    3
    4
    > ssh-keygen -t rsa -C  "Your Name"
    Enter file in which to save the key (C:\Users\user/.ssh/id_rsa):
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
  6. +
  7. 查看密钥,密钥保存位置在上一步命令执行结果中有

    +
    1
    > notepad C:\Users\user/.ssh/id_rsa.pub
  8. +
  9. 打开你的Gitee设置页面,在安全设置下选择SSH公钥,把刚才打开的密钥文件内容全部复制进来,然后加上标题,点击确定就完成配置了。需要删除之前的公钥,注意不是打开blog的仓库设置,是你的账户设置

    +
  10. +
  11. 检查配置是否成功

    +
    1
    2
    3
    > ssh -T git@gitee.com
    # 在"?"后面输入yes,如果配置正确会显示你的Gitee账户名称
    Are you sure you want to continue connecting (yes/no/[fingerprint])?yes
  12. +
+

0x6 发布Hexo到Gitee

    +
  1. 在自己的blog目录下执行,删除之前生成的文件

    +
    1
    2
    3
    4
    > hexo clean
    INFO Validating config
    INFO Deleted database.
    INFO Deleted public folder.
  2. +
  3. 重新生成文件

    +
  4. +
+
1
2
3
4
5
> hexo g
INFO Validating config
INFO Start processing
...
INFO 108 files generated in 877 ms
+ +
    +
  1. 运行本地服务器,检查网站没问题就按Ctrl+C停止运行本地服务器
  2. +
+
1
2
3
4
> hexo s
INFO Validating config
INFO Start processing
INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.
+ +
    +
  1. 同步到Gitee仓库

    +
    1
    > hexo d
  2. +
+

0x7 Gitee部署Hexo

    +
  1. 打开Gitee同步blog的仓库
  2. +
  3. 点击右上角的管理,确保仓库处于开源状态,然后点击保存。有时候会因为没有签协议而被改为私有
  4. +
  5. 返回到仓库页面,点击服务中的Gitee Pages,点击更新
  6. +
  7. 等几分钟再查看blog
  8. +
+

至此,一切都已经配置完成。

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/index.html b/2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/index.html new file mode 100644 index 0000000..4e565d1 --- /dev/null +++ b/2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/index.html @@ -0,0 +1,772 @@ + + + + + + + + + + + + 北境之地资源加成模组 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+ +
+ + + +
+ +
+ 北境之地资源加成模组 +
+ + + +
+ +
+ +
+ +
+
+ WisdomEquan + + Lv2 + +
+
+ + + + +
+
+
+ + +
+ + +

记录一次Northgard(北境之地)模组编写的过程

+ + + + +

游戏介绍

+

AFTER YEARS OF TIRELESS EXPLORATIONS…

+

Brave Vikings have discovered a new land filled with mystery, danger and riches: Northgard.

+

The boldest Northmen have set sail to explore and conquer these new shores, bring fame to their Clan and write history through conquest, trading, or devotion to the Gods.

+
+

经过多年不懈的探索……

+

勇敢的维京人发现了一片充满神秘、危险和财富的新大陆:北加德。

+

最大胆的北方人已经起航去探索和征服这些新的海岸,通过征服、贸易或对神的忠诚来为他们的氏族带来名声,并写下历史。

+

准备工作

确保你已经阅读完了Northgard官方帮助文档,并且你已经知道了如何创建一个mod。确保我们的步骤一致你需要检查一下步骤:

+
    +
  1. 在Northgard的根目录下创建一个Northgard\mods目录
  2. +
  3. 打开Northgard——创意——创建地图
  4. +
  5. 地图大小:巨大
  6. +
  7. 玩家人数:8
  8. +
  9. 难度:普通
  10. +
  11. 游戏类型:随机
  12. +
  13. 点击创建地图
  14. +
  15. 然后保存地图并设置一个名称,比如cheatRes
  16. +
  17. 创意——启动北加尔编辑器——加载刚才创建的模组目录
  18. +
  19. 从游戏目录中复制Northgard\NGEditor\res\script.hxcheatRes目录下
  20. +
  21. 打开北加尔编辑器——Database——Diff——Create
  22. +
  23. 创建一个模组预览图片比如x512、x1024的正方形大小,命名为preview.jpg
  24. +
+

你的模组目录Northgard\mods\cheatRes下应该有以下四个文件:

+ + + + + + + + + + + + + + + + + + + +
cdb.diff修改游戏规则的数据库文件
map.dat在游戏中创建的自定义地图
preview.jpg上传创意工坊的模组预览图
script.hx修改游戏逻辑的
+

目标

每隔一段时间自动增加资源,比如:木材、食物、石材、铁锭等

+

script.hx

首先阅读一遍示例代码,可以发现主要用的只有三个方法:saveState()init()regularUpdate(dt : Float),其他不需要的方法都可以删掉了

+
    +
  • saveState():用于保存游戏的,保持默认就好
  • +
  • init():模组执行逻辑的主入口且只执行一次
  • +
  • regularUpdate(dt : Float):每0.5秒就会执行一次
  • +
+

从这三个方法中可以发现主要代码写在regularUpdate即可,通读官方提供的API文档可以发现有一个Player表示玩家点进去后发现有一个类似添加资源的函数addResource(kind:ResourceKind, amount:Float, produced:Bool = true):Void,这里第一个参数应该是指要添加的资源类型,但遗憾的是文档中并没有提及有哪些资源类型,第二个是指资源的数量,第三个可选。

+

那就只能参考别人的模组看看怎么写,打开创意工坊订阅最热门的模组。下载完成后打开SteamLibrary\steamapps\workshop\content目录下修改日期排序中最新的目录,只需要关注script.hx文件,在代码里搜索关键字addResource发现有一行类似的me().addResource(Resource.Fame, 100);,看到这里让我突然想起官方文档中对于玩家的修改使用的就是me()这样就不用判断是否是AI还是玩家了。

+

尝试编写模组:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 保存游戏(保持默认)
savedInt = 0;

function saveState() {
state.scriptProps = {
savedInt : savedInt,
};
}

// 开局执行一次
function init() {
if (state.time == 0){
// 声望
me().addResource(Resource.Fame, 100);
}
}
+ +

打开游戏选择单人游戏,添加模组,选择本地模组,进入游戏。

+

可以看到开局增加了100声望,同时增加了货币、食物、木材、知识。从这一点可以发现Resource.Fame是指声望。

+

继续查找其他资源类型,又看到一个资源类型Resource.Money,进入游戏发现增加的是货币。然后再次返回别人的代码中查找,遗憾的是没有发现其他资源类型。

+

到这里可以大胆的推测一下游戏中的其他资源类型,比如:

+
1
2
3
4
5
6
7
8
// 石头
Resource.Stone
// 铁矿
Resource.Iron
// 木头
Resource.Wood
// 食物
Resource.Food
+ +

然后代入参数类型再次尝试运行模组,令人高兴的是推测是正确的。

+

然后添加所有可能的资源类型,然后保存。

+
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
// 保存游戏(保持默认)
savedInt = 0;

function saveState() {
state.scriptProps = {
savedInt : savedInt,
};
}

// 开局执行一次
function init() {
if (state.time == 0){
// 声望
me().addResource(Resource.Fame, 100);
}
}

// 0.5秒执行一次
function regularUpdate(dt : Float) {
// 金钱
me().addResource(Resource.Money,1);
// 石头
me().addResource(Resource.Stone,1);
// 铁矿
me().addResource(Resource.Iron,1);
// 木头
me().addResource(Resource.Wood,1);
// 食物
me().addResource(Resource.Food,1);
}
+ +

到这里一个模组就编写完成了!

+

上传模组

确保你的模组目录中拥有一个名为preview的图片文件,这将用来在模组浏览页面展示你的模组。

+

然后打开游戏——创意——上传模组,上传成功后打开创意工坊中你发布的文件,完善模组信息比如标题和描述以及需要展示的图片或者视频。

+
+

注意:添加的模组标题和描述必须是符合当前语言的,你可以添加:日语、简体中文、英语,其他国家的人会看到不同的语言

+
+

订阅我

萌新资源大礼包(cheatRes)

+ +
+ + + +
+ + +
+ + + +
+
+ + + +
+
+ +
+
+ + + + +
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+ + +
+
+
+ +
+
+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + +
+
+
    + + +
  • + +
  • + + + + +
+
+ +
+ + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + +
  • + +
  • + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+
+
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/404/index.html b/404/index.html new file mode 100644 index 0000000..5ccac57 --- /dev/null +++ b/404/index.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + 404 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ +

404

+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..4a59347 --- /dev/null +++ b/about/index.html @@ -0,0 +1,445 @@ + + + + + + + + + + + + 关于我 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ +
    +
  • Modrinth: 我在这里创建了一些关于Minecraft的有趣模组
  • +
  • Steam: 我在Steam创意工坊中创建关于Sid Meier's Civilization VI的有趣模组
  • +
  • 吾爱破解: 这是我在吾爱破解中发布的唯一一个Minecraft光影包汉化的资源
  • +
+ + +
+ + + + + + + + + +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2020/11/index.html b/archives/2020/11/index.html new file mode 100644 index 0000000..dcfad27 --- /dev/null +++ b/archives/2020/11/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2020/11 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2020 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2020/index.html b/archives/2020/index.html new file mode 100644 index 0000000..641675b --- /dev/null +++ b/archives/2020/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2020 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2020 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2021/02/index.html b/archives/2021/02/index.html new file mode 100644 index 0000000..a3e4905 --- /dev/null +++ b/archives/2021/02/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2021/2 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2021 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2021/06/index.html b/archives/2021/06/index.html new file mode 100644 index 0000000..ecb232e --- /dev/null +++ b/archives/2021/06/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2021/6 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2021 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2021/index.html b/archives/2021/index.html new file mode 100644 index 0000000..faf5a79 --- /dev/null +++ b/archives/2021/index.html @@ -0,0 +1,458 @@ + + + + + + + + + + + + 归档: 2021 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2021 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2022/10/index.html b/archives/2022/10/index.html new file mode 100644 index 0000000..899ccc8 --- /dev/null +++ b/archives/2022/10/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2022/10 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2022 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2022/11/index.html b/archives/2022/11/index.html new file mode 100644 index 0000000..9da5478 --- /dev/null +++ b/archives/2022/11/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2022/11 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2022 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2022/index.html b/archives/2022/index.html new file mode 100644 index 0000000..f4a18c4 --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1,458 @@ + + + + + + + + + + + + 归档: 2022 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2022 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html new file mode 100644 index 0000000..2402884 --- /dev/null +++ b/archives/2023/06/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2023/6 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2023 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html new file mode 100644 index 0000000..5889347 --- /dev/null +++ b/archives/2023/07/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2023/7 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2023 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html new file mode 100644 index 0000000..3ee98ea --- /dev/null +++ b/archives/2023/08/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2023/8 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2023 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 0000000..3d3a41e --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,465 @@ + + + + + + + + + + + + 归档: 2023 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+ + + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2024/02/index.html b/archives/2024/02/index.html new file mode 100644 index 0000000..9ff5e8b --- /dev/null +++ b/archives/2024/02/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2024/2 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2024 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2024/05/index.html b/archives/2024/05/index.html new file mode 100644 index 0000000..50d195e --- /dev/null +++ b/archives/2024/05/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + 归档: 2024/5 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2024 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 0000000..c14855c --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1,458 @@ + + + + + + + + + + + + 归档: 2024 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2024 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..dc7e134 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,550 @@ + + + + + + + + + + + + 归档 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ 2024 +
+ +
+ +
+
+ 2023 +
+ +
+ +
+
+ 2022 +
+ +
+ +
+
+ 2021 +
+ +
+ +
+
+ 2020 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/atom.xml b/atom.xml new file mode 100644 index 0000000..92a0440 --- /dev/null +++ b/atom.xml @@ -0,0 +1,259 @@ + + + WisdomEquan + + 阳光下の少年 + + + + 2024-05-13T17:45:17.790Z + https://wisdomequan.github.io/ + + + WisdomEquan + + + + Hexo + + + 北境之地资源加成模组 + + https://wisdomequan.github.io/2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/ + 2024-05-13T16:20:38.000Z + 2024-05-13T17:45:17.790Z + + + <p>记录一次<a class="link" href="https://northgard.net/">Northgard(北境之地)<i class="fas fa-external-link-alt"></i></a>模组编写的过程</p> + + + + + + + + + + + + Hexo环境配置 + + https://wisdomequan.github.io/2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/ + 2024-02-24T19:19:21.000Z + 2024-02-24T19:40:09.679Z + + + <p>电脑有时会出现一些奇怪的问题要重装系统,导致很多环境要重新配置。Hexo是环境配置中最复杂的每次都要在百度查很久,为此今天重新配置环境就顺便写一篇文章来记录。</p> + + + + + + + + + + + + 基于Node.js跨平台局域网消息传输与QNAP部署 + + https://wisdomequan.github.io/2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/ + 2023-08-27T17:29:58.000Z + 2023-10-29T08:45:03.000Z + + + + + + + <h2 id="0x1-引言"><a href="#0x1-引言" class="headerlink" title="0x1 引言"></a>0x1 引言</h2><h3 id="0x1-1-目的与背景"><a href="#0x1-1-目的与背景" + + + + + + + + + + + + + + 基于MCreator+Fabric+Forge的自定义工作台 + + https://wisdomequan.github.io/2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/ + 2023-07-01T02:39:54.000Z + 2023-10-29T08:51:19.000Z + + + + + + + <h2 id="前言"><a href="#前言" class="headerlink" + + + + + + + + + + + + + + 植物大战僵尸阳光修改器 + + https://wisdomequan.github.io/2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/ + 2023-06-24T03:34:56.000Z + 2023-10-29T08:52:07.000Z + + + + + + + <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p><strong>一直都想搞一个植物大战僵尸的修改器,想通过学习自己制作一个</strong></p> +<p>打算使用Cheat + + + + + + + + + + + + + + Minecraft BSL 汉化包 + + https://wisdomequan.github.io/2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/ + 2022-11-26T04:53:53.000Z + 2024-02-24T20:48:43.329Z + + + + + + + <p><a class="link" href="https://bitslablab.com/bslshaders/">BSL<i class="fas fa-external-link-alt"></i></a> 是 Minecraft + + + + + + + + + + + + + + Absolute Pace 绝对和平的生存 + + https://wisdomequan.github.io/2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/ + 2022-10-01T10:36:38.000Z + 2023-10-29T08:53:26.000Z + + + + + + + <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 Minecraft + + + + + + + + + + + + + + 文明六自定义领袖特色建筑 + + https://wisdomequan.github.io/2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/ + 2021-05-31T16:00:00.000Z + 2024-01-12T15:24:36.000Z + + + + + + + <p>只允许主机、特定领袖、特定文明使用特定功能的模组</p> +<h2 id="背景"><a href="#背景" class="headerlink" + + + + + + + + + + + + + + 文明六自定义市中心区域建筑模组 + + https://wisdomequan.github.io/2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/ + 2021-02-03T16:00:00.000Z + 2023-10-29T08:54:10.000Z + + + + + + + <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在文明 6 + + + + + + + + + + + + + + 我的世界自定义配方脚本生成器 + + https://wisdomequan.github.io/2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/ + 2020-11-25T16:00:00.000Z + 2023-10-29T08:51:51.000Z + + + + + + + <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 Minecraft + + + + + + + + + + + + + diff --git a/categories/Civ6/index.html b/categories/Civ6/index.html new file mode 100644 index 0000000..5d7566e --- /dev/null +++ b/categories/Civ6/index.html @@ -0,0 +1,461 @@ + + + + + + + + + + + + 分类: Civ6 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+  Civ6 +
+
+ +
+ +
+
+ 2021 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/categories/Minecraft/index.html b/categories/Minecraft/index.html new file mode 100644 index 0000000..8632ddc --- /dev/null +++ b/categories/Minecraft/index.html @@ -0,0 +1,470 @@ + + + + + + + + + + + + 分类: Minecraft | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+  Minecraft +
+
+ +
+ +
+
+ 2023 +
+ +
+ +
+
+ 2022 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/categories/Mod/index.html b/categories/Mod/index.html new file mode 100644 index 0000000..747cf57 --- /dev/null +++ b/categories/Mod/index.html @@ -0,0 +1,470 @@ + + + + + + + + + + + + 分类: Mod | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+  Mod +
+
+ +
+ +
+
+ 2024 +
+ +
+ +
+
+ 2022 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/categories/Node-js/index.html b/categories/Node-js/index.html new file mode 100644 index 0000000..24ad2b3 --- /dev/null +++ b/categories/Node-js/index.html @@ -0,0 +1,454 @@ + + + + + + + + + + + + 分类: Node.js | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+  Node.js +
+
+ +
+ +
+
+ 2023 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 0000000..844ad43 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + 分类 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + + + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git "a/categories/\346\230\223/index.html" "b/categories/\346\230\223/index.html" new file mode 100644 index 0000000..5edbe60 --- /dev/null +++ "b/categories/\346\230\223/index.html" @@ -0,0 +1,470 @@ + + + + + + + + + + + + 分类: 易 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+  易 +
+
+ +
+ +
+
+ 2023 +
+ +
+ +
+
+ 2020 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git "a/categories/\347\216\257\345\242\203\351\205\215\347\275\256/index.html" "b/categories/\347\216\257\345\242\203\351\205\215\347\275\256/index.html" new file mode 100644 index 0000000..886b1ef --- /dev/null +++ "b/categories/\347\216\257\345\242\203\351\205\215\347\275\256/index.html" @@ -0,0 +1,454 @@ + + + + + + + + + + + + 分类: 环境配置 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
+
+  环境配置 +
+
+ +
+ +
+
+ 2024 +
+ +
+ +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/contact/index.html b/contact/index.html new file mode 100644 index 0000000..de9e59b --- /dev/null +++ b/contact/index.html @@ -0,0 +1,434 @@ + + + + + + + + + + + + contact | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ +

嗨,还在看嘛?

+

告诉你个小秘密:我的QQ无法被添加,因为设置了拒绝任何人添加!不信你试试

+ + +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/css/common/animated.css b/css/common/animated.css new file mode 100644 index 0000000..a1ac846 --- /dev/null +++ b/css/common/animated.css @@ -0,0 +1,180 @@ +.fade-in-down-animation { + animation-name: fade-in-down; + animation-duration: 1s; + animation-fill-mode: both; +} +.title-hover-animation { + position: relative; + display: inline-block; + color: var(--text-color-2); + line-height: 1.3; + vertical-align: top; + border-bottom: none; +} +.title-hover-animation::before { + position: absolute; + bottom: -4px; + left: 0; + width: 100%; + height: 2px; + background-color: var(--text-color-2); + transform: scaleX(0); + visibility: hidden; + content: ""; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease-in-out, ease-in-out; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, visibility, transform; +} +.title-hover-animation:hover::before { + transform: scaleX(1); + visibility: visible; +} +@-moz-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-webkit-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-o-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-moz-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-webkit-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-o-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-moz-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-webkit-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-o-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-moz-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@-webkit-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@-o-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} diff --git a/css/common/basic.css b/css/common/basic.css new file mode 100644 index 0000000..593fd23 --- /dev/null +++ b/css/common/basic.css @@ -0,0 +1,712 @@ +:root { + --base-font-size: 15.2px; + --base-line-height: 22px; + --base-font-weight: 400; + --base-font-family: Optima-Regular, Optima, PingFang SC, Microsoft YaHei, sans-serif; + --box-border-radius: 8px; + --header-height: 70px; + --header-shrink-height: calc(var(--header-height) * 0.72); + --first-screen-font-size: 2rem; + --first-screen-icon-size: 1.8rem; + --first-screen-font-color-light: #50505c; + --first-screen-font-color-dark: #adbac5; + --home-post-hover-scale: 1; + --post-author-avatar: block; + --post-create-datetime: flex; + --post-update-datetime: flex; + --post-img-align: 0 auto 0 0; +} +.fade-in-down-animation { + animation-name: fade-in-down; + animation-duration: 1s; + animation-fill-mode: both; +} +.title-hover-animation { + position: relative; + display: inline-block; + color: var(--text-color-2); + line-height: 1.3; + vertical-align: top; + border-bottom: none; +} +.title-hover-animation::before { + position: absolute; + bottom: -4px; + left: 0; + width: 100%; + height: 2px; + background-color: var(--text-color-2); + transform: scaleX(0); + visibility: hidden; + content: ""; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease-in-out, ease-in-out; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, visibility, transform; +} +.title-hover-animation:hover::before { + transform: scaleX(1); + visibility: visible; +} +@-moz-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-webkit-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-o-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-moz-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-webkit-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-o-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-moz-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-webkit-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-o-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-moz-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@-webkit-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@-o-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +:root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +@media (prefers-color-scheme: light) { + :root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); + } +} +@media (prefers-color-scheme: dark) { + :root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #22272e; + --background-color-1-transparent: rgba(34,39,46,0.6); + --background-color-2: #292f38; + --background-color-3: #313842; + --content-background-color: #21262d; + --text-color-1: #b7c2cc; + --text-color-2: #b2bec8; + --text-color-3: #adbac5; + --text-color-4: #6c8397; + --text-color-5: #4d5e6c; + --text-color-6: #2e3841; + --toc-text-color: #8296a6; + --badge-color: #343c47; + --badge-background-color: #ced6dc; + --border-color: #596678; + --selection-color: #005ebc; + --shadow-color: rgba(120,120,120,0.18); + --shadow-hover-color: rgba(120,120,120,0.24); + --scrollbar-color: #1f2329; + --scrollbar-background-color: #47515f; + --toc-scrollbar-color: rgba(173,186,197,0.1); + --copyright-icon-bg-color: rgba(173,186,197,0.12); + --avatar-background-color: #004a93; + --header-transparent-background-1: rgba(34,39,46,0.28); + --header-transparent-background-2: rgba(34,39,46,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #ea404a, #ea722f, #e9a71f, #67e559, #18ecec, #1b85f1, #ee1dee); + --post-h-bottom-border-color: rgba(173,186,197,0.15); + --article-aging-tips-color: $dark-article-aging-tips-color; + --article-aging-tips-background-color: $dark-article-aging-tips-background-color; + --article-aging-tips-border-color: $dark-article-aging-tips-border-color; + --note-default-color: #9999a2; + --note-default-background-color: rgba(153,153,162,0.1); + --note-default-border-color: rgba(153,153,162,0.5); + --note-primary-color: #268bef; + --note-primary-background-color: rgba(38,139,239,0.1); + --note-primary-border-color: rgba(38,139,239,0.6); + --note-warning-color: #ecc34d; + --note-warning-background-color: rgba(236,195,77,0.1); + --note-warning-border-color: rgba(236,195,77,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); + } +} +.light-mode { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +.dark-mode { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #22272e; + --background-color-1-transparent: rgba(34,39,46,0.6); + --background-color-2: #292f38; + --background-color-3: #313842; + --content-background-color: #21262d; + --text-color-1: #b7c2cc; + --text-color-2: #b2bec8; + --text-color-3: #adbac5; + --text-color-4: #6c8397; + --text-color-5: #4d5e6c; + --text-color-6: #2e3841; + --toc-text-color: #8296a6; + --badge-color: #343c47; + --badge-background-color: #ced6dc; + --border-color: #596678; + --selection-color: #005ebc; + --shadow-color: rgba(120,120,120,0.18); + --shadow-hover-color: rgba(120,120,120,0.24); + --scrollbar-color: #1f2329; + --scrollbar-background-color: #47515f; + --toc-scrollbar-color: rgba(173,186,197,0.1); + --copyright-icon-bg-color: rgba(173,186,197,0.12); + --avatar-background-color: #004a93; + --header-transparent-background-1: rgba(34,39,46,0.28); + --header-transparent-background-2: rgba(34,39,46,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #ea404a, #ea722f, #e9a71f, #67e559, #18ecec, #1b85f1, #ee1dee); + --post-h-bottom-border-color: rgba(173,186,197,0.15); + --article-aging-tips-color: $dark-article-aging-tips-color; + --article-aging-tips-background-color: $dark-article-aging-tips-background-color; + --article-aging-tips-border-color: $dark-article-aging-tips-border-color; + --note-default-color: #9999a2; + --note-default-background-color: rgba(153,153,162,0.1); + --note-default-border-color: rgba(153,153,162,0.5); + --note-primary-color: #268bef; + --note-primary-background-color: rgba(38,139,239,0.1); + --note-primary-border-color: rgba(38,139,239,0.6); + --note-warning-color: #ecc34d; + --note-warning-background-color: rgba(236,195,77,0.1); + --note-warning-border-color: rgba(236,195,77,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +* { + transition-delay: 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color; +} +*::-webkit-scrollbar { + width: 0.4rem; + height: 0.4rem; + transition: all 0.2s ease; +} +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-color); + border-radius: 0.1rem; +} +*::-webkit-scrollbar-track { + background: var(--scrollbar-background-color); +} +html, +body { + position: relative; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + color: var(--text-color-3); + font-weight: var(--base-font-weight); + font-size: var(--base-font-size); + font-family: var(--base-font-family); + line-height: var(--base-line-height); + letter-spacing: 0.2px; + background: var(--background-color-1); +} +html::-webkit-scrollbar, +body::-webkit-scrollbar { + width: 0.6rem; + height: 0.6rem; +} +@media (max-width: 800px) { + html::-webkit-scrollbar, + body::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; + } +} +@media (max-width: 500px) { + html::-webkit-scrollbar, + body::-webkit-scrollbar { + width: 0.4rem; + height: 0.4rem; + } +} +@media (max-width: 800px) { + html, + body { + font-size: calc(var(--base-font-size) * 0.95) !important; + line-height: calc(var(--base-line-height) * 0.95) !important; + } +} +@media (max-width: 500px) { + html, + body { + font-size: calc(var(--base-font-size) * 0.9) !important; + line-height: calc(var(--base-line-height) * 0.9) !important; + } +} +::selection { + color: #fff; + background: var(--selection-color); +} +ul, +ol, +li { + margin: 0; + padding: 0; + list-style: none; +} +a { + color: var(--text-color-3); + text-decoration: none; +} +a i, +a span { + color: var(--text-color-3); +} +a:hover, +a:active { + color: var(--primary-color); + text-decoration: none !important; +} +a:hover i, +a:active i, +a:hover span, +a:active span { + color: var(--primary-color); +} +.dark-mode img { + filter: brightness(0.9); +} +.dark-mode img:hover { + filter: brightness(1); +} +img[lazyload] { + position: relative; + box-sizing: border-box; + width: 8rem; + height: 8rem; + box-shadow: none !important; + cursor: not-allowed; + pointer-events: none; +} +img[lazyload]::before { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: 100%; + background: var(--background-color-1); + content: ''; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, background; +} +img[lazyload]::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: block; + width: 2rem; + height: 2rem; + margin: auto; + border: 2px solid var(--text-color-6); + border-top-color: var(--selection-color); + border-left-color: var(--selection-color); + border-radius: 50%; + animation: img-loading-animation 750ms infinite linear; + content: ''; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, border; +} +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} +.border-box { + position: relative; + box-sizing: border-box; +} +.text-ellipsis { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.clear::after { + display: block; + clear: both; + height: 0; + overflow: hidden; + visibility: hidden; + content: ''; +} +.tooltip { + position: relative; + box-sizing: border-box; +} +.tooltip:hover .tooltip-content { + display: inline-block; +} +.tooltip.show-img .tooltip-content { + display: none !important; +} +.tooltip .tooltip-content { + position: absolute; + top: -0.4rem; + left: 50%; + z-index: 1010; + display: none; + box-sizing: border-box; + padding: 0.2rem 0.6rem; + color: var(--text-color-6); + font-size: 0.8rem; + letter-spacing: 0.8px; + white-space: nowrap; + background: var(--text-color-1); + border-radius: 0.3rem; + transform: translateX(-50%) translateY(-100%); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, display; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.tooltip-img { + position: relative; + box-sizing: border-box; +} +.tooltip-img.show-img .tooltip-img-box { + display: flex; +} +.tooltip-img .tooltip-img-box { + position: absolute; + top: -0.4rem; + left: 50%; + z-index: 1011; + display: none; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-height: 6rem; + overflow: hidden; + background: var(--background-color-3); + border: 0.2rem solid var(--text-color-4); + border-radius: 0.3rem; + transform: translateX(-50%) translateY(-100%); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, display; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.tooltip-img .tooltip-img-box.has-tip { + flex-direction: column; + justify-content: space-between; +} +.tooltip-img .tooltip-img-box img { + display: block; + max-height: 10rem; +} +.tooltip-img .tooltip-img-box .tip { + position: relative; + box-sizing: border-box; + width: 100%; + padding: 0.3rem 0; + color: #555; + font-size: 0.9rem; + text-align: center; +} diff --git a/css/common/code-block/code-block.css b/css/common/code-block/code-block.css new file mode 100644 index 0000000..2eb9ff8 --- /dev/null +++ b/css/common/code-block/code-block.css @@ -0,0 +1,98 @@ +.highlight-container { + position: relative; + box-sizing: border-box; + margin: 1.4rem 0; +} +.highlight-container.mac { + margin: 1.4rem 0 1.8rem 0; + box-shadow: 0 0.8rem 2rem 0 rgba(0,0,0,0.4); +} +.highlight-container.mac:hover .code-tools-box .copy { + opacity: 1; +} +.highlight-container.mac .code-tools-box { + justify-content: flex-end; + padding: 0.4rem 0.6rem 0.7rem 0.4rem; + background: var(--mac-toolbar-background-color); +} +.highlight-container.mac .code-tools-box::before { + position: absolute; + left: 0.8rem; + width: 0.76rem; + height: 0.76rem; + background: #fc625d; + border-radius: 50%; + box-shadow: 1.3rem 0 #fdbc40, 2.6rem 0 #35cd4b; + content: ''; +} +.highlight-container.mac .code-tools-box.folded { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.highlight-container.mac .code-tools-box.folded .copy { + display: none; +} +.highlight-container.mac .code-tools-box .code-lang { + order: 1; + color: #bbb; +} +.highlight-container.mac .code-tools-box .fold { + order: 2; + padding: 0 0.1rem 0 0.6rem; +} +.highlight-container.mac .code-tools-box .fold i { + color: #ccc; +} +.highlight-container.mac .code-tools-box .copy { + position: absolute; + top: 3rem; + right: 0.5rem; + box-sizing: border-box; + padding: 0 0.1rem; + opacity: 0; +} +.highlight-container.mac .code-tools-box .copy i { + font-size: 1rem; +} +.highlight-container .code-tools-box { + position: relative; + z-index: $z-index-1; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + width: 100%; + padding: 0.3rem 0.4rem; + color: var(--toolbar-foreground); + background: var(--toolbar-background); + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; +} +.highlight-container .code-tools-box.folded { + border-bottom-right-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} +.highlight-container .code-tools-box .code-lang { + justify-content: flex-start; + margin-left: 0.2rem; + font-weight: 600; + font-size: 0.9rem; + font-family: "Source Code Pro", consolas, Menlo; +} +.highlight-container .code-tools-box .tool { + cursor: pointer; +} +.highlight-container .code-tools-box .tool i { + font-size: 0.8rem; +} +.highlight-container .code-tools-box .fold { + padding: 0 0.4rem 0 0.2rem; +} +.highlight-container figure.highlight { + position: relative; + box-sizing: border-box; + margin: 0; +} +.highlight-container figure.highlight.folded { + height: 0; +} diff --git a/css/common/code-block/code-theme.css b/css/common/code-block/code-theme.css new file mode 100644 index 0000000..a5f331b --- /dev/null +++ b/css/common/code-block/code-theme.css @@ -0,0 +1,99 @@ +:root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +@media (prefers-color-scheme: light) { + :root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; + } +} +@media (prefers-color-scheme: dark) { + :root { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; + } +} +.light-mode { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +.dark-mode { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; +} diff --git a/css/common/code-block/highlight.css b/css/common/code-block/highlight.css new file mode 100644 index 0000000..085592a --- /dev/null +++ b/css/common/code-block/highlight.css @@ -0,0 +1,273 @@ +:root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +@media (prefers-color-scheme: light) { + :root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; + } +} +@media (prefers-color-scheme: dark) { + :root { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; + } +} +.light-mode { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +.dark-mode { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; +} +.code-block, +pre, +.highlight { + margin: 1.5rem 0; + padding: 0; + overflow: auto; + color: var(--highlight-foreground); + font-size: 0.96rem; + line-height: 1.5rem; + background: var(--highlight-background); +} +pre, +code { + font-family: "Source Code Pro", consolas, Menlo; +} +code { + padding: 0.4rem; + color: var(--code-foreground); + font-size: 0.96rem; + word-wrap: break-word; + background: var(--code-background); + border-radius: 0.2rem; +} +pre { + padding: 0.6rem; +} +pre code { + padding: 0; + color: var(--highlight-foreground); + text-shadow: none; + background: none; +} +.highlight { + border-bottom-right-radius: 0.1rem; + border-bottom-left-radius: 0.1rem; +} +.highlight pre { + margin: 0; + padding: 0.6rem 0; + border: none; +} +.highlight table { + width: auto; + margin: 0; + border: none; + border-spacing: unset; +} +.highlight td { + padding: 0; + border: none; +} +.highlight figcaption { + box-sizing: border-box; + padding: 0.5rem; + color: var(--highlight-foreground); + font-size: 1rem; + line-height: 1rem; +} +.highlight figcaption a { + float: right; + color: var(--highlight-foreground); + font-size: 0.9rem; +} +.highlight figcaption a:hover { + border-bottom-color: var(--highlight-foreground); +} +.highlight .gutter pre { + padding-right: 0.6rem; + padding-left: 0.6rem; + color: var(--highlight-gutter-color); + text-align: center; + background-color: var(--highlight-gutter-bg-color); +} +.highlight .code pre { + width: 100%; + padding-right: 0.6rem; + padding-left: 0.6rem; + background-color: var(--highlight-background); +} +.highlight .line { + height: 1.5rem; + color: var(--highlight-foreground); +} +.highlight .line .language-javascript { + color: var(--highlight-foreground); +} +.highlight .line .attr { + color: var(--highlight-foreground); +} +.highlight .line .string { + color: var(--highlight-foreground); +} +.gutter { + color: var(--highlight-gutter-color); + background: var(--highlight-gutter-bg-color); +} +.gutter .line { + color: var(--highlight-gutter-color); +} +.gist table { + width: auto; +} +.gist table td { + border: none; +} +pre .deletion { + background: var(--highlight-deletion); +} +pre .addition { + background: var(--highlight-addition); +} +pre .meta { + color: var(--highlight-purple); +} +pre .comment { + color: var(--highlight-comment); +} +pre .variable, +pre .attribute, +pre .tag, +pre .regexp, +pre .ruby .constant, +pre .xml .tag .title, +pre .xml .pi, +pre .xml .doctype, +pre .html .doctype, +pre .css .id, +pre .css .class, +pre .css .pseudo { + color: var(--highlight-red); +} +pre .property { + color: var(--highlight-blue); +} +pre .number, +pre .preprocessor, +pre .built_in, +pre .literal, +pre .params, +pre .constant, +pre .command { + color: var(--highlight-orange); +} +pre .ruby .class .title, +pre .css .rules .attribute, +pre .string, +pre .value, +pre .inheritance, +pre .header, +pre .ruby .symbol, +pre .xml .cdata, +pre .special, +pre .number, +pre .formula { + color: var(--highlight-green); +} +pre .title, +pre .css .hexcolor { + color: var(--highlight-aqua); +} +pre .function, +pre .python .decorator, +pre .python .title, +pre .ruby .function .title, +pre .ruby .title .keyword, +pre .perl .sub, +pre .javascript .title, +pre .coffeescript .title { + color: var(--highlight-blue); +} +pre .keyword, +pre .javascript .function { + color: var(--highlight-purple); +} diff --git a/css/common/css-variables.css b/css/common/css-variables.css new file mode 100644 index 0000000..b0ee655 --- /dev/null +++ b/css/common/css-variables.css @@ -0,0 +1,18 @@ +:root { + --base-font-size: 15.2px; + --base-line-height: 22px; + --base-font-weight: 400; + --base-font-family: Optima-Regular, Optima, PingFang SC, Microsoft YaHei, sans-serif; + --box-border-radius: 8px; + --header-height: 70px; + --header-shrink-height: calc(var(--header-height) * 0.72); + --first-screen-font-size: 2rem; + --first-screen-icon-size: 1.8rem; + --first-screen-font-color-light: #50505c; + --first-screen-font-color-dark: #adbac5; + --home-post-hover-scale: 1; + --post-author-avatar: block; + --post-create-datetime: flex; + --post-update-datetime: flex; + --post-img-align: 0 auto 0 0; +} diff --git a/css/common/keep-style.css b/css/common/keep-style.css new file mode 100644 index 0000000..7f74b95 --- /dev/null +++ b/css/common/keep-style.css @@ -0,0 +1,264 @@ +:root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +@media (prefers-color-scheme: light) { + :root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); + } +} +@media (prefers-color-scheme: dark) { + :root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #22272e; + --background-color-1-transparent: rgba(34,39,46,0.6); + --background-color-2: #292f38; + --background-color-3: #313842; + --content-background-color: #21262d; + --text-color-1: #b7c2cc; + --text-color-2: #b2bec8; + --text-color-3: #adbac5; + --text-color-4: #6c8397; + --text-color-5: #4d5e6c; + --text-color-6: #2e3841; + --toc-text-color: #8296a6; + --badge-color: #343c47; + --badge-background-color: #ced6dc; + --border-color: #596678; + --selection-color: #005ebc; + --shadow-color: rgba(120,120,120,0.18); + --shadow-hover-color: rgba(120,120,120,0.24); + --scrollbar-color: #1f2329; + --scrollbar-background-color: #47515f; + --toc-scrollbar-color: rgba(173,186,197,0.1); + --copyright-icon-bg-color: rgba(173,186,197,0.12); + --avatar-background-color: #004a93; + --header-transparent-background-1: rgba(34,39,46,0.28); + --header-transparent-background-2: rgba(34,39,46,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #ea404a, #ea722f, #e9a71f, #67e559, #18ecec, #1b85f1, #ee1dee); + --post-h-bottom-border-color: rgba(173,186,197,0.15); + --article-aging-tips-color: $dark-article-aging-tips-color; + --article-aging-tips-background-color: $dark-article-aging-tips-background-color; + --article-aging-tips-border-color: $dark-article-aging-tips-border-color; + --note-default-color: #9999a2; + --note-default-background-color: rgba(153,153,162,0.1); + --note-default-border-color: rgba(153,153,162,0.5); + --note-primary-color: #268bef; + --note-primary-background-color: rgba(38,139,239,0.1); + --note-primary-border-color: rgba(38,139,239,0.6); + --note-warning-color: #ecc34d; + --note-warning-background-color: rgba(236,195,77,0.1); + --note-warning-border-color: rgba(236,195,77,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); + } +} +.light-mode { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +.dark-mode { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #22272e; + --background-color-1-transparent: rgba(34,39,46,0.6); + --background-color-2: #292f38; + --background-color-3: #313842; + --content-background-color: #21262d; + --text-color-1: #b7c2cc; + --text-color-2: #b2bec8; + --text-color-3: #adbac5; + --text-color-4: #6c8397; + --text-color-5: #4d5e6c; + --text-color-6: #2e3841; + --toc-text-color: #8296a6; + --badge-color: #343c47; + --badge-background-color: #ced6dc; + --border-color: #596678; + --selection-color: #005ebc; + --shadow-color: rgba(120,120,120,0.18); + --shadow-hover-color: rgba(120,120,120,0.24); + --scrollbar-color: #1f2329; + --scrollbar-background-color: #47515f; + --toc-scrollbar-color: rgba(173,186,197,0.1); + --copyright-icon-bg-color: rgba(173,186,197,0.12); + --avatar-background-color: #004a93; + --header-transparent-background-1: rgba(34,39,46,0.28); + --header-transparent-background-2: rgba(34,39,46,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #ea404a, #ea722f, #e9a71f, #67e559, #18ecec, #1b85f1, #ee1dee); + --post-h-bottom-border-color: rgba(173,186,197,0.15); + --article-aging-tips-color: $dark-article-aging-tips-color; + --article-aging-tips-background-color: $dark-article-aging-tips-background-color; + --article-aging-tips-border-color: $dark-article-aging-tips-border-color; + --note-default-color: #9999a2; + --note-default-background-color: rgba(153,153,162,0.1); + --note-default-border-color: rgba(153,153,162,0.5); + --note-primary-color: #268bef; + --note-primary-background-color: rgba(38,139,239,0.1); + --note-primary-border-color: rgba(38,139,239,0.6); + --note-warning-color: #ecc34d; + --note-warning-background-color: rgba(236,195,77,0.1); + --note-warning-border-color: rgba(236,195,77,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} diff --git a/css/common/markdown.css b/css/common/markdown.css new file mode 100644 index 0000000..e555236 --- /dev/null +++ b/css/common/markdown.css @@ -0,0 +1,233 @@ +.keep-markdown-body { + font-size: 1rem; +} +.keep-markdown-body blockquote { + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; + box-sizing: border-box; + margin: 1.4rem 0; + color: var(--text-color-3); + background: var(--background-color-2); + border-left: 0.4rem solid var(--text-color-4); + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} +.keep-markdown-body blockquote p, +.keep-markdown-body blockquote ul, +.keep-markdown-body blockquote ol, +.keep-markdown-body blockquote footer { + position: relative; + box-sizing: border-box; + padding: 0.4rem 0.4rem 0.4rem 0.8rem !important; +} +.keep-markdown-body blockquote footer { + margin-bottom: 0.6rem; + font-style: italic; +} +.keep-markdown-body blockquote cite { + position: relative; + box-sizing: border-box; + margin-left: 1rem; + color: var(--text-color-4); +} +.keep-markdown-body blockquote cite::before { + content: '— '; +} +.keep-markdown-body p { + margin: 0.6rem 0; + color: var(--text-color-3); + line-height: 2; +} +.keep-markdown-body a { + position: relative; + box-sizing: border-box; + padding-bottom: 0.2rem; + text-decoration: none; + overflow-wrap: break-word; + border-bottom: 0.1rem solid var(--text-color-4); + outline: 0; + cursor: pointer; +} +.keep-markdown-body a .fas, +.keep-markdown-body a .far, +.keep-markdown-body a .fab { + position: relative; + margin: 0 0.2rem 0 0.4rem; + color: var(--text-color-4); + font-size: 0.88rem; +} +.keep-markdown-body a:hover { + text-decoration: underline; +} +.keep-markdown-body a:hover::after { + background: var(--primary-color); +} +.keep-markdown-body strong { + color: var(--text-color-3); +} +.keep-markdown-body em { + color: var(--text-color-3); +} +.keep-markdown-body ul li, +.keep-markdown-body ol li { + margin-left: 1rem; + line-height: 2rem; +} +.keep-markdown-body ul li { + list-style: disc; +} +.keep-markdown-body ul li ul li { + list-style: circle; +} +.keep-markdown-body ul li ul li ul li { + list-style: square; +} +.keep-markdown-body ol li { + list-style: decimal; +} +.keep-markdown-body ol li ol li { + list-style: upper-alpha; +} +.keep-markdown-body ol li ol li ol li { + list-style: upper-roman; +} +.keep-markdown-body li { + color: var(--text-color-3); +} +.keep-markdown-body h1, +.keep-markdown-body h2, +.keep-markdown-body h3, +.keep-markdown-body h4, +.keep-markdown-body h5, +.keep-markdown-body h6 { + position: relative; + box-sizing: border-box; + padding-top: 0.4rem; + padding-bottom: 0.2rem; + overflow: hidden; + color: var(--text-color-2); + line-height: 1.5; + white-space: nowrap; + text-overflow: ellipsis; + border-bottom: 1px solid var(--post-h-bottom-border-color); +keep-tablet() +} +.keep-markdown-body h1:hover a.headerlink, +.keep-markdown-body h2:hover a.headerlink, +.keep-markdown-body h3:hover a.headerlink, +.keep-markdown-body h4:hover a.headerlink, +.keep-markdown-body h5:hover a.headerlink, +.keep-markdown-body h6:hover a.headerlink { + visibility: visible; +} +.keep-markdown-body h1 a.headerlink, +.keep-markdown-body h2 a.headerlink, +.keep-markdown-body h3 a.headerlink, +.keep-markdown-body h4 a.headerlink, +.keep-markdown-body h5 a.headerlink, +.keep-markdown-body h6 a.headerlink { + position: relative; + float: right; + box-sizing: border-box; + margin-left: 0.32rem; + padding-top: 0.25rem; + color: var(--text-color-4); + font-size: 1.2rem; + text-decoration: none; + border: none; + visibility: hidden; +} +.keep-markdown-body h1 a.headerlink:hover, +.keep-markdown-body h2 a.headerlink:hover, +.keep-markdown-body h3 a.headerlink:hover, +.keep-markdown-body h4 a.headerlink:hover, +.keep-markdown-body h5 a.headerlink:hover, +.keep-markdown-body h6 a.headerlink:hover { + color: var(--text-color-3); +} +.keep-markdown-body h1 a.headerlink::before, +.keep-markdown-body h2 a.headerlink::before, +.keep-markdown-body h3 a.headerlink::before, +.keep-markdown-body h4 a.headerlink::before, +.keep-markdown-body h5 a.headerlink::before, +.keep-markdown-body h6 a.headerlink::before { + font-weight: 600; + font-family: 'Font Awesome 6 Free'; + content: '\f0c1'; +} +.keep-markdown-body h1 { + font-weight: 600; + font-size: 1.8rem; +keep-tablet() +} +.keep-markdown-body h2 { + font-weight: 600; + font-size: 1.7rem; +keep-tablet() +} +.keep-markdown-body h3 { + font-weight: 550; + font-size: 1.6rem; +keep-tablet() +} +.keep-markdown-body h4 { + font-weight: 550; + font-size: 1.5rem; +keep-tablet() +} +.keep-markdown-body h5 { + font-weight: 500; + font-size: 1.28rem; +keep-tablet() +} +.keep-markdown-body h6 { + font-weight: 500; + font-size: 1.2rem; + line-height: 1.2; +keep-tablet() +} +.keep-markdown-body img { + position: relative; + display: block; + box-sizing: border-box; + max-width: 100%; + margin: var(--post-img-align); + margin-bottom: 2rem; + box-shadow: 0 0 0.2rem var(--shadow-color); + cursor: zoom-in; + opacity: 1; +} +.keep-markdown-body img.hide { + opacity: 0; +} +.keep-markdown-body img[lazyload] { + margin: 0.8rem auto 0.2rem; +} +.keep-markdown-body > table { + width: 100%; + overflow: auto; + border-collapse: collapse; + border-spacing: 0; +keep-mobile() +} +.keep-markdown-body > table td, +.keep-markdown-body > table th { + padding: 0; +} +.keep-markdown-body > table th { + font-weight: 600; +} +.keep-markdown-body > table td, +.keep-markdown-body > table th { + padding: 0.4rem 1rem; + border: 0.1rem solid var(--border-color); +} +.keep-markdown-body > table tr { + background-color: var(--background-color-1); + border: 0.1rem solid var(--text-color-6); +} +.keep-markdown-body > table tr:nth-child(2n) { + background-color: var(--background-color-2); +} diff --git a/placeholder b/css/common/stylus-variables.css similarity index 100% rename from placeholder rename to css/common/stylus-variables.css diff --git a/css/common/tags/keep-button.css b/css/common/tags/keep-button.css new file mode 100644 index 0000000..f847705 --- /dev/null +++ b/css/common/tags/keep-button.css @@ -0,0 +1,70 @@ +.keep-button { + position: relative; + box-sizing: border-box; + margin: 0.3rem 0.6rem; + padding: 0.6rem 1rem; + color: var(--text-color-3); + font-size: 1rem; + background: var(--background-color-1); + border: none; + border-radius: 0.4rem; + box-shadow: 0.1rem 0.2rem 0.4rem var(--shadow-color); + cursor: pointer; +} +.keep-button i { + color: var(--text-color-3); +} +.keep-button:hover { + color: var(--background-color-1); + background: var(--primary-color); +} +.keep-button:hover i { + color: var(--background-color-1); +} +.keep-button.size-small { + margin: 0.2rem 0.4rem; + padding: 0.5rem 0.8rem; + font-size: 0.8rem; + border-radius: 0.3rem; + box-shadow: 0.1rem 0.1rem 0.3rem var(--shadow-color); +} +.keep-button.size-large { + width: 100%; + margin: 0.6rem 0; + padding: 1rem; + font-size: 1.1rem; + border-radius: 0.5rem; + box-shadow: 0.1rem 0.2rem 0.5rem var(--shadow-color); +} +.keep-button.color-primary { + color: var(--note-primary-color); + background-color: var(--note-primary-background-color); +} +.keep-button.color-primary:hover { + color: #fff; + background-color: var(--note-primary-color); +} +.keep-button.color-success { + color: var(--note-success-color); + background-color: var(--note-success-background-color); +} +.keep-button.color-success:hover { + color: #fff; + background-color: var(--note-success-color); +} +.keep-button.color-warning { + color: var(--note-warning-color); + background-color: var(--note-warning-background-color); +} +.keep-button.color-warning:hover { + color: #fff; + background-color: var(--note-warning-color); +} +.keep-button.color-danger { + color: var(--note-danger-color); + background-color: var(--note-danger-background-color); +} +.keep-button.color-danger:hover { + color: #fff; + background-color: var(--note-danger-color); +} diff --git a/css/common/tags/keep-note.css b/css/common/tags/keep-note.css new file mode 100644 index 0000000..2d0d349 --- /dev/null +++ b/css/common/tags/keep-note.css @@ -0,0 +1,46 @@ +.keep-note { + position: relative; + box-sizing: border-box; + width: 100%; + margin-bottom: 2rem; + padding: 0.8rem 1rem; + font-size: 0.9rem; + border-style: solid; + border-width: 0.1rem; + border-radius: 0.4rem; +} +.keep-note.default { + color: var(--note-default-color); + background-color: var(--note-default-background-color); + border-color: var(--note-default-border-color); +} +.keep-note.primary { + color: var(--note-primary-color); + background-color: var(--note-primary-background-color); + border-color: var(--note-primary-border-color); +} +.keep-note.success { + color: var(--note-success-color); + background-color: var(--note-success-background-color); + border-color: var(--note-success-border-color); +} +.keep-note.warning { + color: var(--note-warning-color); + background-color: var(--note-warning-background-color); + border-color: var(--note-warning-border-color); +} +.keep-note.danger { + color: var(--note-danger-color); + background-color: var(--note-danger-background-color); + border-color: var(--note-danger-border-color); +} +.keep-note .keep-note-title { + box-sizing: border-box; + padding: 0.6rem 0 0 0; + color: inherit; + font-weight: 600; + font-size: 1rem; +} +.keep-note p { + color: inherit; +} diff --git a/css/common/tags/keep-tabs.css b/css/common/tags/keep-tabs.css new file mode 100644 index 0000000..52646e6 --- /dev/null +++ b/css/common/tags/keep-tabs.css @@ -0,0 +1,65 @@ +.keep-tabs { + position: relative; + box-sizing: border-box; + width: 100%; + height: auto; + background: var(--background-color); + border-radius: 0.4rem; + box-shadow: 0.1rem 0.1rem 0.5rem var(--shadow-color); +} +.keep-tabs .tabs-nav { + position: relative; + display: flex; + justify-content: flex-start; + box-sizing: border-box; + list-style: none; +} +.keep-tabs .tabs-nav::before { + position: absolute; + bottom: 0; + left: 0; + box-sizing: border-box; + width: 100%; + height: 2px; + background: var(--border-color); + content: ''; +} +.keep-tabs .tabs-nav .tab { + position: relative; + box-sizing: border-box; + margin-right: 0.8rem; + padding: 1rem 0.6rem; + overflow: hidden; + color: var(--text-color-3); + cursor: pointer; +} +.keep-tabs .tabs-nav .tab.active { + font-weight: 600; +} +.keep-tabs .tabs-nav .tab.active::before { + position: absolute; + bottom: 0; + left: 50%; + box-sizing: border-box; + width: 100%; + height: 2px; + background: var(--primary-color); + border-radius: 0.2rem; + transform: translateX(-50%); + content: ''; +} +.keep-tabs .tabs-content { + position: relative; + box-sizing: border-box; +} +.keep-tabs .tabs-content .tab-pane { + position: relative; + box-sizing: border-box; + width: 100%; + height: auto; + min-height: 12rem; + padding: 0.6rem 0.8rem; +} +.keep-tabs .tabs-content .tab-pane:not(.active) { + display: none; +} diff --git a/css/layout/archive-content.css b/css/layout/archive-content.css new file mode 100644 index 0000000..e69de29 diff --git a/css/layout/article-content.css b/css/layout/article-content.css new file mode 100644 index 0000000..8db79e8 --- /dev/null +++ b/css/layout/article-content.css @@ -0,0 +1,255 @@ +.post-page-container { + display: flex; + justify-content: space-between; + width: 100%; + height: 100%; +keep-tablet() +} +.post-page-container.show-toc .pc-post-toc { + display: block; +} +.post-page-container.show-toc .article-content-container { + width: calc(100% - 15rem); +} +.post-page-container .article-content-container { + order: 0; + width: 100%; + height: 100%; +keep-mobile() +} +.post-page-container .article-content-container .article-content-top { + display: flex; + align-items: flex-end; + width: 100%; + padding-top: 2rem; + padding-right: 2rem; + overflow: hidden; + background-color: var(--background-color-2); + border-top-left-radius: var(--box-border-radius); + border-top-right-radius: var(--box-border-radius); +keep-tablet() +keep-mobile() +} +.post-page-container .article-content-container .article-content-top .cover-article-title { + position: relative; + z-index: 1001; + box-sizing: border-box; + max-width: 100%; + max-height: 100%; + padding: 1rem 2rem; + overflow-y: auto; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.76rem; + line-height: 1.6; + background: var(--background-color-1-transparent); + border-top-right-radius: var(--box-border-radius); + backdrop-filter: blur(2px); +} +.post-page-container .article-content-container .article-content-top .post-cover { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + width: 100%; + height: 100%; + object-fit: cover; +} +.post-page-container .article-content-container .article-content-bottom { + padding: 2rem; +keep-mobile() +} +.post-page-container .article-content-container .article-content-bottom.has-cover { + padding-top: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-title { + color: var(--text-color-2); + font-weight: 600; + font-size: 1.6rem; + line-height: 1.6; +keep-tablet() +keep-mobile() +} +.post-page-container .article-content-container .article-content-bottom .article-header { + display: flex; + justify-content: flex-start; + width: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-header .avatar-box { + display: var(--post-author-avatar); + flex-shrink: 0; + width: 3.2rem; + height: 3.2rem; + margin-right: 0.8rem; + padding: 0.1rem; + border: 1px solid var(--border-color); + border-radius: 50%; +} +.post-page-container .article-content-container .article-content-bottom .article-header .avatar-box img { + width: 100%; + height: 100%; + background: var(--avatar-background-color); + border-radius: 50%; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box { + display: flex; + flex-direction: column; + justify-content: space-between; + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 0.2rem 0; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .author { + display: flex; + align-items: center; + font-weight: 600; + font-size: 1.18rem; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .author .name { + color: var(--text-color-3); +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .author .author-label { + margin-left: 0.8rem; + padding: 0 0.4rem; + color: #fff; + font-weight: 500; + font-size: 0.8rem; + background: var(--selection-color); + border-radius: 0.4rem; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .meta-info { + margin-top: 0.1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-header, +.post-page-container .article-content-container .article-content-bottom .article-header-meta-info { + margin-top: 1rem; +keep-tablet() +} +.post-page-container .article-content-container .article-content-bottom .article-content { + margin-top: 30.400000000000002px; + padding-bottom: 30.400000000000002px; + color: var(--text-color-3); + text-align: justify; + word-wrap: break-word; +keep-tablet() + border-bottom: 2px dashed var(--border-color); +} +.post-page-container .article-content-container .article-content-bottom .article-content .article-aging-tips { + position: relative; + display: none; + box-sizing: border-box; + margin-bottom: 1.8rem; + padding: 1rem; + color: var(--note-warning-color); + line-height: 1.6; + background: var(--note-warning-background-color); + border: 0.1rem solid var(--note-warning-border-color); + border-radius: 0.4rem; +} +.post-page-container .article-content-container .article-content-bottom .article-content .article-aging-tips i { + margin-right: 0.4rem; + color: var(--note-warning-color); +} +.post-page-container .article-content-container .article-content-bottom .article-content .article-aging-tips .days { + color: var(--note-warning-color); +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share { + display: flex; + justify-content: space-between; + width: 100%; + margin-top: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share .post-tags-box { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-start; + width: 100%; + font-size: 0.96rem; +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share .post-tags-box .tag-item { + margin-right: 0.4rem; + line-height: 2; +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share .post-tags-box .tag-item .icon { + font-size: 0.88rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav { + height: 2.8rem; + margin-top: 38px; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next { + box-sizing: border-box; + max-width: 14rem; + height: 100%; + border-radius: 0.32rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a { + position: relative; + display: block; + box-sizing: border-box; + width: 100%; + height: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a.prev, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a.prev { + padding-left: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a.next, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a.next { + padding-right: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .arrow-icon, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .arrow-icon { + position: absolute; + top: 0; + width: 1rem; + height: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .arrow-icon.left, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .arrow-icon.left { + left: 0; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .arrow-icon.right, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .arrow-icon.right { + right: 0; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .title, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .title { + width: 100%; + height: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev { + float: left; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next { + float: right; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .post-nav-item { + display: none; +keep-tablet() +} +.post-page-container .pc-post-toc { + position: sticky; + top: calc(var(--header-height) + 38px); + display: none; + flex-shrink: 0; + box-sizing: border-box; + width: 15rem; + max-height: calc(100vh - calc(var(--header-height) + 38px)); +} +.post-page-container .pc-post-toc.right-toc { + order: 1; + padding: 2rem 0 2rem 1.8rem; +} +.post-page-container .pc-post-toc.left-toc { + order: -1; + padding: 2rem 1.8rem 2rem 0; +} +.header-shrink .post-page-container .pc-post-toc { + top: calc(var(--header-shrink-height) + 38px); + max-height: calc(100vh - calc(var(--header-shrink-height) + 38px)); +} diff --git a/css/layout/category-content.css b/css/layout/category-content.css new file mode 100644 index 0000000..fb529b8 --- /dev/null +++ b/css/layout/category-content.css @@ -0,0 +1,11 @@ +.category-container .category-name { + margin-bottom: $component-spacing-value; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.5rem; +keep-tablet() +keep-mobile() +} +.category-container .category-name i { + color: var(--text-color-2); +} diff --git a/css/layout/category-list.css b/css/layout/category-list.css new file mode 100644 index 0000000..5cec3d2 --- /dev/null +++ b/css/layout/category-list.css @@ -0,0 +1,41 @@ +.category-list-container .category-list-content ul.site-all-category-list { + user-select: none; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item { + position: relative; + box-sizing: border-box; + margin-bottom: 0.8rem; + font-size: 1rem; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item:last-child { + margin-bottom: 0; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + padding: 0.5rem 0; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info .left { + position: relative; + box-sizing: border-box; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info .left .icon { + padding-right: 0.4rem; + color: var(--text-color-3); + font-size: 0.8rem; + cursor: pointer; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info .right .site-all-category-list-count { + color: var(--text-color-3); +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .site-all-category-list-child { + position: relative; + box-sizing: border-box; + height: 0; + margin-left: 1.5rem; + overflow: hidden; + visibility: hidden; +} diff --git a/css/layout/home-content.css b/css/layout/home-content.css new file mode 100644 index 0000000..79eed29 --- /dev/null +++ b/css/layout/home-content.css @@ -0,0 +1,73 @@ +.home-content-container { + background: var(--background-color-1); +} +.home-content-container .home-article-list .home-article-item { + position: relative; + overflow: hidden; +} +.home-content-container .home-article-list .home-article-item .post-sticky-box { + position: absolute; + z-index: $z-index-1; + display: flex; + align-items: center; + box-sizing: border-box; + padding: 0.1rem 0.4rem; + color: var(--text-color-4); + font-size: 12px; + letter-spacing: 1px; + background: var(--background-color-3); + border-radius: 0.3rem; + transform: scale(0.86); + cursor: default; +keep-mobile() +} +.home-content-container .home-article-list .home-article-item .post-sticky-box.cover { + top: 0.6rem; + right: 0.6rem; +} +.home-content-container .home-article-list .home-article-item .post-sticky-box.card { + top: 0.8rem; + right: 0.8rem; +} +.home-content-container .home-article-list .home-article-item .home-article-item-top { + width: 100%; + overflow: hidden; + background-color: var(--background-color-2); +keep-tablet() +keep-mobile() +} +.home-content-container .home-article-list .home-article-item .home-article-item-top .home-cover { + width: 100%; + height: 100%; + object-fit: cover; + transform-origin: center center; +} +.home-content-container .home-article-list .home-article-item .home-article-item-top .home-cover:hover { + transform: scale(1.03); +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom { + padding: 2rem; +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom .sticky-icon { + position: absolute; + top: 1.2rem; + right: 1.2rem; + color: var(--text-color-4); + font-size: 1.2rem; + transform: rotate(45deg); +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom .home-article-title { + margin: 0; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.4rem; + line-height: 1.5; +keep-tablet() +keep-mobile() +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom .home-article-content { + margin: 1.8rem 0; + color: var(--text-color-3); + text-align: justify; + word-wrap: break-word; +} diff --git a/css/layout/page.css b/css/layout/page.css new file mode 100644 index 0000000..50c6f9d --- /dev/null +++ b/css/layout/page.css @@ -0,0 +1,118 @@ +.page-container { + width: 100%; + height: auto; + background: var(--background-color-1); +} +.page-container .page-main-content { + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 100vh; + padding-top: var(--header-height); +keep-tablet() +keep-mobile() +} +.page-container .page-main-content.is-home .transparent-1 { + background: var(--header-transparent-background-1); + backdrop-filter: blur(4px); +} +.page-container .page-main-content.is-home .transparent-2 { + background: var(--header-transparent-background-2); + backdrop-filter: blur(5px); +} +.header-shrink .page-container .page-main-content { + padding-top: var(--header-shrink-height); +keep-tablet() +keep-mobile() +} +.page-container .page-main-content .page-main-content-top { + position: fixed; + top: 0; + right: 0; + z-index: $z-index-5; + box-sizing: border-box; + width: 100%; + height: var(--header-height); +keep-tablet() +keep-mobile() +} +.page-container .page-main-content .page-main-content-top.hide { + transform: translateY(-105%); +} +.header-shrink .page-container .page-main-content .page-main-content-top { + height: var(--header-shrink-height); +keep-tablet() +keep-mobile() +} +.page-container .page-main-content .page-main-content-middle { + display: flex; + justify-content: center; + width: 100%; + padding: $component-spacing-value 0; +keep-tablet() +keep-mobile() +} +.page-container .page-main-content .page-main-content-middle .main-content { + width: $main-content-width; + max-width: $content-max-width; + height: 100%; +keep-tablet() +keep-mobile() +} +.has-toc .page-container .page-main-content .page-main-content-middle .main-content { + max-width: $has-toc-content-max-width; +} +.page-container .page-main-content .page-main-content-bottom { + width: 100%; +} +.page-container .post-tools { + position: fixed; + top: calc(var(--header-height) + $component-spacing-value); + box-sizing: border-box; + opacity: 0; +keep-tablet() +} +.page-container .post-tools.right-toc { + left: calc((100vw - $content-max-width / 2) - 5rem); +} +.page-container .post-tools.left-toc { + right: calc((100vw - $content-max-width / 2) - 5rem); +} +.header-shrink .page-container .post-tools { + top: calc(var(--header-shrink-height) + $component-spacing-value); +} +.page-container .side-tools { + position: fixed; + right: 0; + bottom: 1.6rem; + box-sizing: border-box; + padding-right: 1rem; +keep-tablet() +} +.page-container .side-tools:hover .side-tools-container { + transform: translateX(0); + opacity: 1; +} +.page-container .tablet-post-toc-mask { + position: fixed; + top: 0; + right: 0; + z-index: $z-index-10; + box-sizing: border-box; + width: 100%; + height: 100%; + background: rgba(0,0,0,0); + visibility: hidden; +} +.page-container .tablet-post-toc-mask .tablet-post-toc { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + width: 22rem; + height: 100%; + padding: 1.2rem 1rem; + background: var(--background-color-1); + transform: translateX(-100%); +keep-mobile() +} diff --git a/css/layout/tag-content.css b/css/layout/tag-content.css new file mode 100644 index 0000000..16c553c --- /dev/null +++ b/css/layout/tag-content.css @@ -0,0 +1,11 @@ +.tag-container .tag-name { + margin-bottom: $component-spacing-value; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.5rem; +keep-tablet() +keep-mobile() +} +.tag-container .tag-name i { + color: var(--text-color-2); +} diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..e378667 --- /dev/null +++ b/css/style.css @@ -0,0 +1,4054 @@ +:root { + --base-font-size: 15.2px; + --base-line-height: 22px; + --base-font-weight: 400; + --base-font-family: Optima-Regular, Optima, PingFang SC, Microsoft YaHei, sans-serif; + --box-border-radius: 8px; + --header-height: 70px; + --header-shrink-height: calc(var(--header-height) * 0.72); + --first-screen-font-size: 2rem; + --first-screen-icon-size: 1.8rem; + --first-screen-font-color-light: #50505c; + --first-screen-font-color-dark: #adbac5; + --home-post-hover-scale: 1; + --post-author-avatar: block; + --post-create-datetime: flex; + --post-update-datetime: flex; + --post-img-align: 0 auto 0 0; +} +.fade-in-down-animation { + animation-name: fade-in-down; + animation-duration: 1s; + animation-fill-mode: both; +} +.title-hover-animation { + position: relative; + display: inline-block; + color: var(--text-color-2); + line-height: 1.3; + vertical-align: top; + border-bottom: none; +} +.title-hover-animation::before { + position: absolute; + bottom: -4px; + left: 0; + width: 100%; + height: 2px; + background-color: var(--text-color-2); + transform: scaleX(0); + visibility: hidden; + content: ""; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease-in-out, ease-in-out; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, visibility, transform; +} +.title-hover-animation:hover::before { + transform: scaleX(1); + visibility: visible; +} +@-moz-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-webkit-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-o-keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@keyframes fade-in-down { + 0% { + transform: translateY(-50px); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} +@-moz-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-webkit-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-o-keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@keyframes heartbeat-animate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.88); + } + 20%, 40%, 60%, 80% { + transform: scale(1.08); + } + 50%, 70% { + transform: scale(1.08); + } +} +@-moz-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-webkit-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-o-keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@keyframes img-loading-animation { + to { + transform: rotate(1turn); + } +} +@-moz-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@-webkit-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@-o-keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +@keyframes blink-caret { + from, to { + opacity: 0; + } + 50% { + opacity: 1; + } +} +:root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +@media (prefers-color-scheme: light) { + :root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); + } +} +@media (prefers-color-scheme: dark) { + :root { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #22272e; + --background-color-1-transparent: rgba(34,39,46,0.6); + --background-color-2: #292f38; + --background-color-3: #313842; + --content-background-color: #21262d; + --text-color-1: #b7c2cc; + --text-color-2: #b2bec8; + --text-color-3: #adbac5; + --text-color-4: #6c8397; + --text-color-5: #4d5e6c; + --text-color-6: #2e3841; + --toc-text-color: #8296a6; + --badge-color: #343c47; + --badge-background-color: #ced6dc; + --border-color: #596678; + --selection-color: #005ebc; + --shadow-color: rgba(120,120,120,0.18); + --shadow-hover-color: rgba(120,120,120,0.24); + --scrollbar-color: #1f2329; + --scrollbar-background-color: #47515f; + --toc-scrollbar-color: rgba(173,186,197,0.1); + --copyright-icon-bg-color: rgba(173,186,197,0.12); + --avatar-background-color: #004a93; + --header-transparent-background-1: rgba(34,39,46,0.28); + --header-transparent-background-2: rgba(34,39,46,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #ea404a, #ea722f, #e9a71f, #67e559, #18ecec, #1b85f1, #ee1dee); + --post-h-bottom-border-color: rgba(173,186,197,0.15); + --article-aging-tips-color: $dark-article-aging-tips-color; + --article-aging-tips-background-color: $dark-article-aging-tips-background-color; + --article-aging-tips-border-color: $dark-article-aging-tips-border-color; + --note-default-color: #9999a2; + --note-default-background-color: rgba(153,153,162,0.1); + --note-default-border-color: rgba(153,153,162,0.5); + --note-primary-color: #268bef; + --note-primary-background-color: rgba(38,139,239,0.1); + --note-primary-border-color: rgba(38,139,239,0.6); + --note-warning-color: #ecc34d; + --note-warning-background-color: rgba(236,195,77,0.1); + --note-warning-border-color: rgba(236,195,77,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); + } +} +.light-mode { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #fff; + --background-color-1-transparent: rgba(255,255,255,0.6); + --background-color-2: #f7f7f7; + --background-color-3: #f0f0f0; + --content-background-color: #fff; + --text-color-1: #484853; + --text-color-2: #4c4c57; + --text-color-3: #50505c; + --text-color-4: #808091; + --text-color-5: #b7b7c0; + --text-color-6: #ededef; + --toc-text-color: #6f6f80; + --badge-color: #f0f0f0; + --badge-background-color: #9393a1; + --border-color: #b3b3b3; + --selection-color: #0075eb; + --shadow-color: rgba(0,0,0,0.16); + --shadow-hover-color: rgba(0,0,0,0.22); + --scrollbar-color: #60606e; + --scrollbar-background-color: #e6e6e6; + --toc-scrollbar-color: rgba(80,80,92,0.1); + --copyright-icon-bg-color: rgba(80,80,92,0.12); + --avatar-background-color: #005cb8; + --header-transparent-background-1: rgba(255,255,255,0.28); + --header-transparent-background-2: rgba(255,255,255,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #f10006, #ef5b00, #e59c01, #19ca05, #00cab5, #0264c8, #c303c3); + --post-h-bottom-border-color: rgba(80,80,92,0.15); + --article-aging-tips-color: $article-aging-tips-color; + --article-aging-tips-background-color: $article-aging-tips-background-color; + --article-aging-tips-border-color: $article-aging-tips-border-color; + --note-default-color: rgba(117,117,122,0.8); + --note-default-background-color: rgba(117,117,122,0.1); + --note-default-border-color: rgba(117,117,122,0.6); + --note-primary-color: #0458ab; + --note-primary-background-color: rgba(4,88,171,0.1); + --note-primary-border-color: rgba(4,88,171,0.6); + --note-warning-color: #b78d0f; + --note-warning-background-color: rgba(183,141,15,0.1); + --note-warning-border-color: rgba(183,141,15,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +.dark-mode { + --primary-color: #06c; + --primary-color-light-1: #0075eb; + --primary-color-light-2: #0a85ff; + --primary-color-dark-1: #005cb8; + --primary-color-dark-2: #0052a3; + --background-color-1: #22272e; + --background-color-1-transparent: rgba(34,39,46,0.6); + --background-color-2: #292f38; + --background-color-3: #313842; + --content-background-color: #21262d; + --text-color-1: #b7c2cc; + --text-color-2: #b2bec8; + --text-color-3: #adbac5; + --text-color-4: #6c8397; + --text-color-5: #4d5e6c; + --text-color-6: #2e3841; + --toc-text-color: #8296a6; + --badge-color: #343c47; + --badge-background-color: #ced6dc; + --border-color: #596678; + --selection-color: #005ebc; + --shadow-color: rgba(120,120,120,0.18); + --shadow-hover-color: rgba(120,120,120,0.24); + --scrollbar-color: #1f2329; + --scrollbar-background-color: #47515f; + --toc-scrollbar-color: rgba(173,186,197,0.1); + --copyright-icon-bg-color: rgba(173,186,197,0.12); + --avatar-background-color: #004a93; + --header-transparent-background-1: rgba(34,39,46,0.28); + --header-transparent-background-2: rgba(34,39,46,0.4); + --pjax-progress-bar-color: linear-gradient(45deg, #ea404a, #ea722f, #e9a71f, #67e559, #18ecec, #1b85f1, #ee1dee); + --post-h-bottom-border-color: rgba(173,186,197,0.15); + --article-aging-tips-color: $dark-article-aging-tips-color; + --article-aging-tips-background-color: $dark-article-aging-tips-background-color; + --article-aging-tips-border-color: $dark-article-aging-tips-border-color; + --note-default-color: #9999a2; + --note-default-background-color: rgba(153,153,162,0.1); + --note-default-border-color: rgba(153,153,162,0.5); + --note-primary-color: #268bef; + --note-primary-background-color: rgba(38,139,239,0.1); + --note-primary-border-color: rgba(38,139,239,0.6); + --note-warning-color: #ecc34d; + --note-warning-background-color: rgba(236,195,77,0.1); + --note-warning-border-color: rgba(236,195,77,0.6); + --note-success-color: #10b981; + --note-success-background-color: rgba(16,185,129,0.1); + --note-success-border-color: rgba(16,185,129,0.6); + --note-danger-color: #f43f5e; + --note-danger-background-color: rgba(244,63,94,0.1); + --note-danger-border-color: rgba(244,63,94,0.6); +} +* { + transition-delay: 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color; +} +*::-webkit-scrollbar { + width: 0.4rem; + height: 0.4rem; + transition: all 0.2s ease; +} +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-color); + border-radius: 0.1rem; +} +*::-webkit-scrollbar-track { + background: var(--scrollbar-background-color); +} +html, +body { + position: relative; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + color: var(--text-color-3); + font-weight: var(--base-font-weight); + font-size: var(--base-font-size); + font-family: var(--base-font-family); + line-height: var(--base-line-height); + letter-spacing: 0.2px; + background: var(--background-color-1); +} +html::-webkit-scrollbar, +body::-webkit-scrollbar { + width: 0.6rem; + height: 0.6rem; +} +@media (max-width: 800px) { + html::-webkit-scrollbar, + body::-webkit-scrollbar { + width: 0.5rem; + height: 0.5rem; + } +} +@media (max-width: 500px) { + html::-webkit-scrollbar, + body::-webkit-scrollbar { + width: 0.4rem; + height: 0.4rem; + } +} +@media (max-width: 800px) { + html, + body { + font-size: calc(var(--base-font-size) * 0.95) !important; + line-height: calc(var(--base-line-height) * 0.95) !important; + } +} +@media (max-width: 500px) { + html, + body { + font-size: calc(var(--base-font-size) * 0.9) !important; + line-height: calc(var(--base-line-height) * 0.9) !important; + } +} +::selection { + color: #fff; + background: var(--selection-color); +} +ul, +ol, +li { + margin: 0; + padding: 0; + list-style: none; +} +a { + color: var(--text-color-3); + text-decoration: none; +} +a i, +a span { + color: var(--text-color-3); +} +a:hover, +a:active { + color: var(--primary-color); + text-decoration: none !important; +} +a:hover i, +a:active i, +a:hover span, +a:active span { + color: var(--primary-color); +} +.dark-mode img { + filter: brightness(0.9); +} +.dark-mode img:hover { + filter: brightness(1); +} +img[lazyload] { + position: relative; + box-sizing: border-box; + width: 8rem; + height: 8rem; + box-shadow: none !important; + cursor: not-allowed; + pointer-events: none; +} +img[lazyload]::before { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: 100%; + background: var(--background-color-1); + content: ''; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, background; +} +img[lazyload]::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: block; + width: 2rem; + height: 2rem; + margin: auto; + border: 2px solid var(--text-color-6); + border-top-color: var(--selection-color); + border-left-color: var(--selection-color); + border-radius: 50%; + animation: img-loading-animation 750ms infinite linear; + content: ''; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, border; +} +.flex-center { + display: flex; + align-items: center; + justify-content: center; +} +.border-box { + position: relative; + box-sizing: border-box; +} +.text-ellipsis { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.clear::after { + display: block; + clear: both; + height: 0; + overflow: hidden; + visibility: hidden; + content: ''; +} +.tooltip { + position: relative; + box-sizing: border-box; +} +.tooltip:hover .tooltip-content { + display: inline-block; +} +.tooltip.show-img .tooltip-content { + display: none !important; +} +.tooltip .tooltip-content { + position: absolute; + top: -0.4rem; + left: 50%; + z-index: 1010; + display: none; + box-sizing: border-box; + padding: 0.2rem 0.6rem; + color: var(--text-color-6); + font-size: 0.8rem; + letter-spacing: 0.8px; + white-space: nowrap; + background: var(--text-color-1); + border-radius: 0.3rem; + transform: translateX(-50%) translateY(-100%); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, display; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.tooltip-img { + position: relative; + box-sizing: border-box; +} +.tooltip-img.show-img .tooltip-img-box { + display: flex; +} +.tooltip-img .tooltip-img-box { + position: absolute; + top: -0.4rem; + left: 50%; + z-index: 1011; + display: none; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-height: 6rem; + overflow: hidden; + background: var(--background-color-3); + border: 0.2rem solid var(--text-color-4); + border-radius: 0.3rem; + transform: translateX(-50%) translateY(-100%); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, display; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.tooltip-img .tooltip-img-box.has-tip { + flex-direction: column; + justify-content: space-between; +} +.tooltip-img .tooltip-img-box img { + display: block; + max-height: 10rem; +} +.tooltip-img .tooltip-img-box .tip { + position: relative; + box-sizing: border-box; + width: 100%; + padding: 0.3rem 0; + color: #555; + font-size: 0.9rem; + text-align: center; +} +.keep-markdown-body { + font-size: 1rem; +} +.keep-markdown-body blockquote { + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; + box-sizing: border-box; + margin: 1.4rem 0; + color: var(--text-color-3); + background: var(--background-color-2); + border-left: 0.4rem solid var(--text-color-4); + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} +.keep-markdown-body blockquote p, +.keep-markdown-body blockquote ul, +.keep-markdown-body blockquote ol, +.keep-markdown-body blockquote footer { + position: relative; + box-sizing: border-box; + padding: 0.4rem 0.4rem 0.4rem 0.8rem !important; +} +.keep-markdown-body blockquote footer { + margin-bottom: 0.6rem; + font-style: italic; +} +.keep-markdown-body blockquote cite { + position: relative; + box-sizing: border-box; + margin-left: 1rem; + color: var(--text-color-4); +} +.keep-markdown-body blockquote cite::before { + content: '— '; +} +.keep-markdown-body p { + margin: 0.6rem 0; + color: var(--text-color-3); + line-height: 2; +} +.keep-markdown-body a { + position: relative; + box-sizing: border-box; + padding-bottom: 0.2rem; + text-decoration: none; + overflow-wrap: break-word; + border-bottom: 0.1rem solid var(--text-color-4); + outline: 0; + cursor: pointer; +} +.keep-markdown-body a .fas, +.keep-markdown-body a .far, +.keep-markdown-body a .fab { + position: relative; + margin: 0 0.2rem 0 0.4rem; + color: var(--text-color-4); + font-size: 0.88rem; +} +.keep-markdown-body a:hover { + text-decoration: underline; +} +.keep-markdown-body a:hover::after { + background: var(--primary-color); +} +.keep-markdown-body strong { + color: var(--text-color-3); +} +.keep-markdown-body em { + color: var(--text-color-3); +} +.keep-markdown-body ul li, +.keep-markdown-body ol li { + margin-left: 1rem; + line-height: 2rem; +} +.keep-markdown-body ul li { + list-style: disc; +} +.keep-markdown-body ul li ul li { + list-style: circle; +} +.keep-markdown-body ul li ul li ul li { + list-style: square; +} +.keep-markdown-body ol li { + list-style: decimal; +} +.keep-markdown-body ol li ol li { + list-style: upper-alpha; +} +.keep-markdown-body ol li ol li ol li { + list-style: upper-roman; +} +.keep-markdown-body li { + color: var(--text-color-3); +} +.keep-markdown-body h1, +.keep-markdown-body h2, +.keep-markdown-body h3, +.keep-markdown-body h4, +.keep-markdown-body h5, +.keep-markdown-body h6 { + position: relative; + box-sizing: border-box; + padding-top: 0.4rem; + padding-bottom: 0.2rem; + overflow: hidden; + color: var(--text-color-2); + line-height: 1.5; + white-space: nowrap; + text-overflow: ellipsis; + border-bottom: 1px solid var(--post-h-bottom-border-color); +} +@media (max-width: 800px) { + .keep-markdown-body h1, + .keep-markdown-body h2, + .keep-markdown-body h3, + .keep-markdown-body h4, + .keep-markdown-body h5, + .keep-markdown-body h6 { + line-height: 1.25; + } +} +.keep-markdown-body h1:hover a.headerlink, +.keep-markdown-body h2:hover a.headerlink, +.keep-markdown-body h3:hover a.headerlink, +.keep-markdown-body h4:hover a.headerlink, +.keep-markdown-body h5:hover a.headerlink, +.keep-markdown-body h6:hover a.headerlink { + visibility: visible; +} +.keep-markdown-body h1 a.headerlink, +.keep-markdown-body h2 a.headerlink, +.keep-markdown-body h3 a.headerlink, +.keep-markdown-body h4 a.headerlink, +.keep-markdown-body h5 a.headerlink, +.keep-markdown-body h6 a.headerlink { + position: relative; + float: right; + box-sizing: border-box; + margin-left: 0.32rem; + padding-top: 0.25rem; + color: var(--text-color-4); + font-size: 1.2rem; + text-decoration: none; + border: none; + visibility: hidden; +} +.keep-markdown-body h1 a.headerlink:hover, +.keep-markdown-body h2 a.headerlink:hover, +.keep-markdown-body h3 a.headerlink:hover, +.keep-markdown-body h4 a.headerlink:hover, +.keep-markdown-body h5 a.headerlink:hover, +.keep-markdown-body h6 a.headerlink:hover { + color: var(--text-color-3); +} +.keep-markdown-body h1 a.headerlink::before, +.keep-markdown-body h2 a.headerlink::before, +.keep-markdown-body h3 a.headerlink::before, +.keep-markdown-body h4 a.headerlink::before, +.keep-markdown-body h5 a.headerlink::before, +.keep-markdown-body h6 a.headerlink::before { + font-weight: 600; + font-family: 'Font Awesome 6 Free'; + content: '\f0c1'; +} +.keep-markdown-body h1 { + font-weight: 600; + font-size: 1.8rem; +} +@media (max-width: 800px) { + .keep-markdown-body h1 { + font-size: 1.7rem; + } +} +.keep-markdown-body h2 { + font-weight: 600; + font-size: 1.7rem; +} +@media (max-width: 800px) { + .keep-markdown-body h2 { + font-size: 1.6rem; + } +} +.keep-markdown-body h3 { + font-weight: 550; + font-size: 1.6rem; +} +@media (max-width: 800px) { + .keep-markdown-body h3 { + font-size: 1.5rem; + } +} +.keep-markdown-body h4 { + font-weight: 550; + font-size: 1.5rem; +} +@media (max-width: 800px) { + .keep-markdown-body h4 { + font-size: 1.4rem; + } +} +.keep-markdown-body h5 { + font-weight: 500; + font-size: 1.28rem; +} +@media (max-width: 800px) { + .keep-markdown-body h5 { + font-size: 1.18rem; + } +} +.keep-markdown-body h6 { + font-weight: 500; + font-size: 1.2rem; + line-height: 1.2; +} +@media (max-width: 800px) { + .keep-markdown-body h6 { + font-size: 1.1rem; + line-height: 1.1; + } +} +.keep-markdown-body img { + position: relative; + display: block; + box-sizing: border-box; + max-width: 100%; + margin: var(--post-img-align); + margin-bottom: 2rem; + box-shadow: 0 0 0.2rem var(--shadow-color); + cursor: zoom-in; + opacity: 1; +} +.keep-markdown-body img.hide { + opacity: 0; +} +.keep-markdown-body img[lazyload] { + margin: 0.8rem auto 0.2rem; +} +.keep-markdown-body > table { + width: 100%; + overflow: auto; + border-collapse: collapse; + border-spacing: 0; +} +@media (max-width: 500px) { + .keep-markdown-body > table { + table-layout: fixed; + } +} +.keep-markdown-body > table td, +.keep-markdown-body > table th { + padding: 0; +} +.keep-markdown-body > table th { + font-weight: 600; +} +.keep-markdown-body > table td, +.keep-markdown-body > table th { + padding: 0.4rem 1rem; + border: 0.1rem solid var(--border-color); +} +.keep-markdown-body > table tr { + background-color: var(--background-color-1); + border: 0.1rem solid var(--text-color-6); +} +.keep-markdown-body > table tr:nth-child(2n) { + background-color: var(--background-color-2); +} +:root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +@media (prefers-color-scheme: light) { + :root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; + } +} +@media (prefers-color-scheme: dark) { + :root { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; + } +} +.light-mode { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +.dark-mode { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; +} +.code-block, +pre, +.highlight { + margin: 1.5rem 0; + padding: 0; + overflow: auto; + color: var(--highlight-foreground); + font-size: 0.96rem; + line-height: 1.5rem; + background: var(--highlight-background); + -ms-text-size-adjust: none; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; +} +pre, +code { + font-family: "Source Code Pro", consolas, Menlo; +} +code { + padding: 0.4rem; + color: var(--code-foreground); + font-size: 0.96rem; + word-wrap: break-word; + background: var(--code-background); + border-radius: 0.2rem; +} +pre { + padding: 0.6rem; +} +pre code { + padding: 0; + color: var(--highlight-foreground); + text-shadow: none; + background: none; +} +.highlight { + border-bottom-right-radius: 0.1rem; + border-bottom-left-radius: 0.1rem; +} +.highlight pre { + margin: 0; + padding: 0.6rem 0; + border: none; +} +.highlight table { + width: auto; + margin: 0; + border: none; + border-spacing: unset; +} +.highlight td { + padding: 0; + border: none; +} +.highlight figcaption { + box-sizing: border-box; + padding: 0.5rem; + color: var(--highlight-foreground); + font-size: 1rem; + line-height: 1rem; +} +.highlight figcaption a { + float: right; + color: var(--highlight-foreground); + font-size: 0.9rem; +} +.highlight figcaption a:hover { + border-bottom-color: var(--highlight-foreground); +} +.highlight .gutter pre { + padding-right: 0.6rem; + padding-left: 0.6rem; + color: var(--highlight-gutter-color); + text-align: center; + background-color: var(--highlight-gutter-bg-color); +} +.highlight .code pre { + width: 100%; + padding-right: 0.6rem; + padding-left: 0.6rem; + background-color: var(--highlight-background); +} +.highlight .line { + height: 1.5rem; + color: var(--highlight-foreground); +} +.highlight .line .language-javascript { + color: var(--highlight-foreground); +} +.highlight .line .attr { + color: var(--highlight-foreground); +} +.highlight .line .string { + color: var(--highlight-foreground); +} +.gutter { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; + color: var(--highlight-gutter-color); + background: var(--highlight-gutter-bg-color); +} +.gutter .line { + color: var(--highlight-gutter-color); +} +.gist table { + width: auto; +} +.gist table td { + border: none; +} +pre .deletion { + background: var(--highlight-deletion); +} +pre .addition { + background: var(--highlight-addition); +} +pre .meta { + color: var(--highlight-purple); +} +pre .comment { + color: var(--highlight-comment); +} +pre .variable, +pre .attribute, +pre .tag, +pre .regexp, +pre .ruby .constant, +pre .xml .tag .title, +pre .xml .pi, +pre .xml .doctype, +pre .html .doctype, +pre .css .id, +pre .css .class, +pre .css .pseudo { + color: var(--highlight-red); +} +pre .property { + color: var(--highlight-blue); +} +pre .number, +pre .preprocessor, +pre .built_in, +pre .literal, +pre .params, +pre .constant, +pre .command { + color: var(--highlight-orange); +} +pre .ruby .class .title, +pre .css .rules .attribute, +pre .string, +pre .value, +pre .inheritance, +pre .header, +pre .ruby .symbol, +pre .xml .cdata, +pre .special, +pre .number, +pre .formula { + color: var(--highlight-green); +} +pre .title, +pre .css .hexcolor { + color: var(--highlight-aqua); +} +pre .function, +pre .python .decorator, +pre .python .title, +pre .ruby .function .title, +pre .ruby .title .keyword, +pre .perl .sub, +pre .javascript .title, +pre .coffeescript .title { + color: var(--highlight-blue); +} +pre .keyword, +pre .javascript .function { + color: var(--highlight-purple); +} +.highlight-container { + position: relative; + box-sizing: border-box; + margin: 1.4rem 0; +} +.highlight-container.mac { + margin: 1.4rem 0 1.8rem 0; + box-shadow: 0 0.8rem 2rem 0 rgba(0,0,0,0.4); +} +.highlight-container.mac:hover .code-tools-box .copy { + opacity: 1; +} +.highlight-container.mac .code-tools-box { + justify-content: flex-end; + padding: 0.4rem 0.6rem 0.7rem 0.4rem; + background: var(--mac-toolbar-background-color); +} +.highlight-container.mac .code-tools-box::before { + position: absolute; + left: 0.8rem; + width: 0.76rem; + height: 0.76rem; + background: #fc625d; + border-radius: 50%; + box-shadow: 1.3rem 0 #fdbc40, 2.6rem 0 #35cd4b; + content: ''; +} +.highlight-container.mac .code-tools-box.folded { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.highlight-container.mac .code-tools-box.folded .copy { + display: none; +} +.highlight-container.mac .code-tools-box .code-lang { + order: 1; + color: #bbb; +} +.highlight-container.mac .code-tools-box .fold { + order: 2; + padding: 0 0.1rem 0 0.6rem; +} +.highlight-container.mac .code-tools-box .fold i { + color: #ccc; +} +.highlight-container.mac .code-tools-box .copy { + position: absolute; + top: 3rem; + right: 0.5rem; + box-sizing: border-box; + padding: 0 0.1rem; + opacity: 0; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease-in-out; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, opacity; +} +.highlight-container.mac .code-tools-box .copy i { + font-size: 1rem; +} +.highlight-container .code-tools-box { + position: relative; + z-index: 1001; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + width: 100%; + padding: 0.3rem 0.4rem; + color: var(--toolbar-foreground); + background: var(--toolbar-background); + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; +} +.highlight-container .code-tools-box.folded { + border-bottom-right-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} +.highlight-container .code-tools-box .code-lang { + justify-content: flex-start; + margin-left: 0.2rem; + font-weight: 600; + font-size: 0.9rem; + font-family: "Source Code Pro", consolas, Menlo; +} +.highlight-container .code-tools-box .tool { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; + cursor: pointer; +} +.highlight-container .code-tools-box .tool i { + font-size: 0.8rem; +} +.highlight-container .code-tools-box .fold { + padding: 0 0.4rem 0 0.2rem; +} +.highlight-container figure.highlight { + position: relative; + box-sizing: border-box; + margin: 0; +} +.highlight-container figure.highlight.folded { + height: 0; +} +:root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +@media (prefers-color-scheme: light) { + :root { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; + } +} +@media (prefers-color-scheme: dark) { + :root { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; + } +} +.light-mode { + --toolbar-foreground: #b5b5b5; + --toolbar-background: #3a3f40; + --code-foreground: #535360; + --code-background: #f2f2f2; + --highlight-background: #273036; + --highlight-foreground: #faf5d6; + --highlight-comment: #76888e; + --highlight-red: #ee5758; + --highlight-orange: #ec7600; + --highlight-yellow: #ffcd22; + --highlight-green: #93c763; + --highlight-aqua: #458383; + --highlight-blue: #6e95bd; + --highlight-purple: #b18bb7; + --highlight-gutter-color: #7a979a; + --highlight-gutter-bg-color: #273130; + --mac-toolbar-background-color: #21252b; +} +.dark-mode { + --toolbar-foreground: #b1b1b1; + --toolbar-background: #393e3f; + --code-foreground: #afbbc6; + --code-background: #1f2329; + --highlight-background: #262f35; + --highlight-foreground: #f5ebad; + --highlight-comment: #76888e; + --highlight-red: #ed5152; + --highlight-orange: #e77400; + --highlight-yellow: #ffcc1c; + --highlight-green: #90c55f; + --highlight-aqua: #448080; + --highlight-blue: #6a92bb; + --highlight-purple: #ae87b5; + --highlight-gutter-color: #7b989b; + --highlight-gutter-bg-color: #26302f; + --mac-toolbar-background-color: #1f2021; +} +.keep-note { + position: relative; + box-sizing: border-box; + width: 100%; + margin-bottom: 2rem; + padding: 0.8rem 1rem; + font-size: 0.9rem; + border-style: solid; + border-width: 0.1rem; + border-radius: 0.4rem; +} +.keep-note.default { + color: var(--note-default-color); + background-color: var(--note-default-background-color); + border-color: var(--note-default-border-color); +} +.keep-note.primary { + color: var(--note-primary-color); + background-color: var(--note-primary-background-color); + border-color: var(--note-primary-border-color); +} +.keep-note.success { + color: var(--note-success-color); + background-color: var(--note-success-background-color); + border-color: var(--note-success-border-color); +} +.keep-note.warning { + color: var(--note-warning-color); + background-color: var(--note-warning-background-color); + border-color: var(--note-warning-border-color); +} +.keep-note.danger { + color: var(--note-danger-color); + background-color: var(--note-danger-background-color); + border-color: var(--note-danger-border-color); +} +.keep-note .keep-note-title { + box-sizing: border-box; + padding: 0.6rem 0 0 0; + color: inherit; + font-weight: 600; + font-size: 1rem; +} +.keep-note p { + color: inherit; +} +.keep-button { + position: relative; + box-sizing: border-box; + margin: 0.3rem 0.6rem; + padding: 0.6rem 1rem; + color: var(--text-color-3); + font-size: 1rem; + background: var(--background-color-1); + border: none; + border-radius: 0.4rem; + box-shadow: 0.1rem 0.2rem 0.4rem var(--shadow-color); + cursor: pointer; +} +.keep-button i { + color: var(--text-color-3); +} +.keep-button:hover { + color: var(--background-color-1); + background: var(--primary-color); +} +.keep-button:hover i { + color: var(--background-color-1); +} +.keep-button.size-small { + margin: 0.2rem 0.4rem; + padding: 0.5rem 0.8rem; + font-size: 0.8rem; + border-radius: 0.3rem; + box-shadow: 0.1rem 0.1rem 0.3rem var(--shadow-color); +} +.keep-button.size-large { + width: 100%; + margin: 0.6rem 0; + padding: 1rem; + font-size: 1.1rem; + border-radius: 0.5rem; + box-shadow: 0.1rem 0.2rem 0.5rem var(--shadow-color); +} +.keep-button.color-primary { + color: var(--note-primary-color); + background-color: var(--note-primary-background-color); +} +.keep-button.color-primary:hover { + color: #fff; + background-color: var(--note-primary-color); +} +.keep-button.color-success { + color: var(--note-success-color); + background-color: var(--note-success-background-color); +} +.keep-button.color-success:hover { + color: #fff; + background-color: var(--note-success-color); +} +.keep-button.color-warning { + color: var(--note-warning-color); + background-color: var(--note-warning-background-color); +} +.keep-button.color-warning:hover { + color: #fff; + background-color: var(--note-warning-color); +} +.keep-button.color-danger { + color: var(--note-danger-color); + background-color: var(--note-danger-background-color); +} +.keep-button.color-danger:hover { + color: #fff; + background-color: var(--note-danger-color); +} +.keep-tabs { + position: relative; + box-sizing: border-box; + width: 100%; + height: auto; + background: var(--background-color); + border-radius: 0.4rem; + box-shadow: 0.1rem 0.1rem 0.5rem var(--shadow-color); +} +.keep-tabs .tabs-nav { + position: relative; + display: flex; + justify-content: flex-start; + box-sizing: border-box; + list-style: none; +} +.keep-tabs .tabs-nav::before { + position: absolute; + bottom: 0; + left: 0; + box-sizing: border-box; + width: 100%; + height: 2px; + background: var(--border-color); + content: ''; +} +.keep-tabs .tabs-nav .tab { + position: relative; + box-sizing: border-box; + margin-right: 0.8rem; + padding: 1rem 0.6rem; + overflow: hidden; + color: var(--text-color-3); + cursor: pointer; +} +.keep-tabs .tabs-nav .tab.active { + font-weight: 600; +} +.keep-tabs .tabs-nav .tab.active::before { + position: absolute; + bottom: 0; + left: 50%; + box-sizing: border-box; + width: 100%; + height: 2px; + background: var(--primary-color); + border-radius: 0.2rem; + transform: translateX(-50%); + content: ''; +} +.keep-tabs .tabs-content { + position: relative; + box-sizing: border-box; +} +.keep-tabs .tabs-content .tab-pane { + position: relative; + box-sizing: border-box; + width: 100%; + height: auto; + min-height: 12rem; + padding: 0.6rem 0.8rem; +} +.keep-tabs .tabs-content .tab-pane:not(.active) { + display: none; +} +.page-container { + width: 100%; + height: auto; + background: var(--background-color-1); +} +.page-container .page-main-content { + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 100vh; + padding-top: var(--header-height); +} +.page-container .page-main-content.is-home .transparent-1 { + background: var(--header-transparent-background-1); + backdrop-filter: blur(4px); +} +.page-container .page-main-content.is-home .transparent-2 { + background: var(--header-transparent-background-2); + backdrop-filter: blur(5px); +} +.header-shrink .page-container .page-main-content { + padding-top: var(--header-shrink-height); +} +@media (max-width: 800px) { + .header-shrink .page-container .page-main-content { + padding-top: calc(var(--header-shrink-height) * 0.9); + } +} +@media (max-width: 500px) { + .header-shrink .page-container .page-main-content { + padding-top: calc(var(--header-shrink-height) * 0.8); + } +} +@media (max-width: 800px) { + .page-container .page-main-content { + padding-top: calc(var(--header-height) * 0.9); + } +} +@media (max-width: 500px) { + .page-container .page-main-content { + padding-top: calc(var(--header-height) * 0.8); + } +} +.page-container .page-main-content .page-main-content-top { + position: fixed; + top: 0; + right: 0; + z-index: 1005; + box-sizing: border-box; + width: 100%; + height: var(--header-height); + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease-out, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.3s, 0.2s; + transition-property: color, background, box-shadow, border-color, transform, height; +} +.page-container .page-main-content .page-main-content-top.hide { + transform: translateY(-105%); +} +.header-shrink .page-container .page-main-content .page-main-content-top { + height: var(--header-shrink-height); +} +@media (max-width: 800px) { + .header-shrink .page-container .page-main-content .page-main-content-top { + height: calc(var(--header-shrink-height) * 0.9); + } +} +@media (max-width: 500px) { + .header-shrink .page-container .page-main-content .page-main-content-top { + height: calc(var(--header-shrink-height) * 0.8); + } +} +@media (max-width: 800px) { + .page-container .page-main-content .page-main-content-top { + height: calc(var(--header-height) * 0.9); + } +} +@media (max-width: 500px) { + .page-container .page-main-content .page-main-content-top { + height: calc(var(--header-height) * 0.8); + } +} +.page-container .page-main-content .page-main-content-middle { + display: flex; + justify-content: center; + width: 100%; + padding: 38px 0; +} +@media (max-width: 800px) { + .page-container .page-main-content .page-main-content-middle { + padding: 30.400000000000002px 0; + } +} +@media (max-width: 500px) { + .page-container .page-main-content .page-main-content-middle { + padding: 22.8px 0; + } +} +.page-container .page-main-content .page-main-content-middle .main-content { + width: 80%; + max-width: 928px; + height: 100%; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.1s, 0.1s; + transition-property: color, background, box-shadow, border-color, max-width, width; +} +.has-toc .page-container .page-main-content .page-main-content-middle .main-content { + max-width: 1113.6px; +} +@media (max-width: 800px) { + .page-container .page-main-content .page-main-content-middle .main-content { + width: 88%; + } +} +@media (max-width: 500px) { + .page-container .page-main-content .page-main-content-middle .main-content { + width: 90%; + } +} +.page-container .page-main-content .page-main-content-bottom { + width: 100%; +} +.page-container .post-tools { + position: fixed; + top: calc(var(--header-height) + 38px); + box-sizing: border-box; + opacity: 0; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s, 0.2s, 0s; + transition-timing-function: ease, ease, ease, ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, top, transform, opacity, left; +} +.page-container .post-tools.right-toc { + left: calc((100vw - 928px / 2) - 5rem); +} +.page-container .post-tools.left-toc { + right: calc((100vw - 928px / 2) - 5rem); +} +.header-shrink .page-container .post-tools { + top: calc(var(--header-shrink-height) + 38px); +} +@media (max-width: 800px) { + .page-container .post-tools { + display: none !important; + } +} +.page-container .side-tools { + position: fixed; + right: 0; + bottom: 1.6rem; + box-sizing: border-box; + padding-right: 1rem; +} +@media (max-width: 800px) { + .page-container .side-tools { + padding-right: 0; + } +} +.page-container .side-tools:hover .side-tools-container { + transform: translateX(0); + opacity: 1; +} +.page-container .tablet-post-toc-mask { + position: fixed; + top: 0; + right: 0; + z-index: 1010; + box-sizing: border-box; + width: 100%; + height: 100%; + background: rgba(0,0,0,0); + visibility: hidden; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, visibility; +} +.page-container .tablet-post-toc-mask .tablet-post-toc { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + width: 22rem; + height: 100%; + padding: 1.2rem 1rem; + background: var(--background-color-1); + transform: translateX(-100%); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.4s; + transition-property: color, background, box-shadow, border-color, transform; +} +@media (max-width: 500px) { + .page-container .tablet-post-toc-mask .tablet-post-toc { + width: 70%; + } +} +.search-pop-overlay { + position: fixed; + top: 0; + left: 0; + z-index: 1008; + display: flex; + width: 100%; + height: 100%; + background: rgba(0,0,0,0); + visibility: hidden; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.3s, 0.3s; + transition-property: color, background, box-shadow, border-color, visibility, background; +} +.search-pop-overlay.active { + background: rgba(0,0,0,0.35); + visibility: visible; +} +.search-pop-overlay.active .search-popup { + transform: scale(1); +} +.search-pop-overlay .search-popup { + z-index: 1006; + width: 70%; + height: 80%; + margin: auto; + background: var(--background-color-1); + border-radius: 0.4rem; + transform: scale(0); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.3s; + transition-property: color, background, box-shadow, border-color, transform; +} +@media (max-width: 800px) { + .search-pop-overlay .search-popup { + width: 80%; + } +} +@media (max-width: 500px) { + .search-pop-overlay .search-popup { + width: 90%; + } +} +.search-pop-overlay .search-popup .search-header { + display: flex; + align-items: center; + height: 3rem; + padding: 0 1rem; + background: var(--text-color-6); + border-top-left-radius: 0.2rem; + border-top-right-radius: 0.2rem; +} +.search-pop-overlay .search-popup .search-header .search-input-field-pre { + margin-right: 0.2rem; + color: var(--text-color-3); + font-size: 1.3rem; + cursor: pointer; +} +.search-pop-overlay .search-popup .search-header .search-input-container { + flex-grow: 1; + padding: 0.2rem; +} +.search-pop-overlay .search-popup .search-header .search-input-container .search-input { + width: 100%; + color: var(--text-color-3); + font-size: 1.2rem; + background: transparent; + border: 0; + outline: 0; +} +.search-pop-overlay .search-popup .search-header .search-input-container .search-input::-webkit-search-cancel-button { + display: none; +} +.search-pop-overlay .search-popup .search-header .search-input-container .search-input::-webkit-input-placeholder { + color: var(--text-color-4); + font-size: 1rem; +} +.search-pop-overlay .search-popup .search-header .close-popup-btn { + color: var(--text-color-3); + font-size: 1.2rem; + cursor: pointer; +} +.search-pop-overlay .search-popup .search-header .close-popup-btn:hover { + color: var(--text-color-1); +} +.search-pop-overlay .search-popup #search-result { + position: relative; + display: flex; + box-sizing: border-box; + height: calc(100% - 3rem); + padding: 0.3rem 1.5rem; + overflow: auto; +} +.search-pop-overlay .search-popup #search-result .search-result-list { + width: 100%; + height: 100%; + font-size: 1rem; +} +.search-pop-overlay .search-popup #search-result .search-result-list li { + box-sizing: border-box; + margin: 0.8rem 0; + padding: 0.8rem 0; + border-bottom: 0.1rem dashed var(--border-color); +} +.search-pop-overlay .search-popup #search-result .search-result-list li:last-child { + border-bottom: none; +} +.search-pop-overlay .search-popup #search-result .search-result-list li .search-result-title { + position: relative; + display: flex; + align-items: center; + margin-bottom: 0.8rem; + padding-left: 1rem; + font-weight: bold; +} +.search-pop-overlay .search-popup #search-result .search-result-list li .search-result-title::after { + position: absolute; + top: 50%; + left: 0; + width: 0.4rem; + height: 0.4rem; + background: var(--text-color-3); + border-radius: 50%; + transform: translateY(-50%); + content: ''; +} +.search-pop-overlay .search-popup #search-result .search-result-list li .search-result { + margin: 0; + padding-left: 1rem; + line-height: 2rem; + word-wrap: break-word; +} +.search-pop-overlay .search-popup #search-result .search-result-list li a:hover { + color: var(--text-color-3); +} +.search-pop-overlay .search-popup #search-result .search-result-list li .search-keyword { + color: var(--primary-color); + font-weight: bold; + border-bottom: 0.1rem dashed var(--primary-color); +} +.search-pop-overlay .search-popup #search-result #no-result { + margin: auto; + color: var(--text-color-4); +} +.post-toc-wrap { + width: 100%; + height: 100%; + overflow-y: auto; + font-size: 0.92rem; +} +.post-toc-wrap::-webkit-scrollbar-thumb { + background: var(--toc-scrollbar-color); + border-radius: 0.3rem; +} +.post-toc-wrap::-webkit-scrollbar-track { + background: transparent; +} +.post-toc-wrap .post-toc ol { + position: relative; + box-sizing: border-box; + margin: 0; + text-align: left; + list-style: none; +} +.post-toc-wrap .post-toc ol a { + transition-property: all; +} +.post-toc-wrap .post-toc .nav .nav-item { + position: relative; + box-sizing: border-box; + overflow: hidden; + line-height: 2; + white-space: nowrap; + text-overflow: ellipsis; +} +.post-toc-wrap .post-toc .nav .nav-item.active > .nav-child { + display: block; +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link { + position: relative; + box-sizing: border-box; + padding-left: 0.8rem; +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link:hover .nav-number, +.post-toc-wrap .post-toc .nav .nav-item .nav-link:hover .nav-text { + color: var(--primary-color); +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link.active-current > .nav-child { + display: block; +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link.active-current > .nav-child > .nav-item { + display: block; +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link.active-current::before { + position: absolute; + top: 50%; + left: 0; + width: 0.2rem; + height: 1.2rem; + background: var(--primary-color); + border-radius: 0.2rem; + transform: translateY(-50%); + content: ''; +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link.active-current .nav-number, +.post-toc-wrap .post-toc .nav .nav-item .nav-link.active-current .nav-text { + color: var(--primary-color); +} +.post-toc-wrap .post-toc .nav .nav-item .nav-link .nav-number, +.post-toc-wrap .post-toc .nav .nav-item .nav-link .nav-text { + color: var(--toc-text-color); +} +.post-toc-wrap .post-toc .nav .nav-item .nav-child { + padding-left: 1rem; + display: none; +} +.comments-container { + display: inline-block; + width: 100%; + margin-top: 38px; +} +.comments-container .comment-area-title { + width: 100%; + color: var(--text-color-3); + font-size: 1.38rem; + line-height: 2; +} +.comments-container .comment-area-title i { + color: var(--text-color-3); +} +@media (max-width: 800px) { + .comments-container .comment-area-title { + font-size: 1.2rem; + } +} +.comments-container .error-tips { + margin-top: 1rem; + color: var(--text-color-3); + font-size: 1rem; +} +.progress-bar-container { + position: fixed; + top: 0; + left: 0; + z-index: 1009; + width: 100%; +} +.progress-bar-container .pjax-progress-bar { + position: absolute; + top: 0; + left: 0; + z-index: 1008; + width: 0; + height: 2px; + background: var(--pjax-progress-bar-color); + visibility: hidden; + opacity: 0; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.1s, 0.1s; + transition-property: color, background, box-shadow, border-color, width, opacity; +} +.progress-bar-container .pjax-progress-bar.show { + visibility: visible; + opacity: 1; +} +.progress-bar-container .pjax-progress-icon { + position: absolute; + top: 0.4rem; + right: 0.3rem; + z-index: 1008; + color: var(--text-color-3); + font-size: 1.1rem; + visibility: hidden; +} +@media (max-width: 800px) { + .progress-bar-container .pjax-progress-icon { + top: 0.3rem; + right: 0.2rem; + font-size: 1rem; + } +} +.progress-bar-container .pjax-progress-icon.show { + visibility: visible; +} +.progress-bar-container .scroll-progress-bar { + position: absolute; + top: 0; + left: 0; + z-index: 1007; + width: 0; + height: 2px; + background: var(--primary-color); + visibility: hidden; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.1s; + transition-property: color, background, box-shadow, border-color, width; +} +.progress-bar-container .scroll-progress-bar.hide { + display: none !important; +} +.header-wrapper { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + width: 100%; + height: 100%; + padding-top: 2px; + background: var(--background-color-1); + box-shadow: 1px 2px 6px var(--shadow-color); +} +.header-wrapper:hover { + box-shadow: 1px 2px 10px var(--shadow-hover-color); +} +.header-wrapper .header-content { + z-index: 1005; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 80%; + max-width: 928px; + height: 100%; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.1s, 0.1s; + transition-property: color, background, box-shadow, border-color, max-width, width; +} +.header-wrapper .header-content.has-first-screen { + max-width: 1113.6px; +} +.has-toc .header-wrapper .header-content { + max-width: 1113.6px; +} +@media (max-width: 800px) { + .header-wrapper .header-content { + width: 88%; + } +} +@media (max-width: 500px) { + .header-wrapper .header-content { + width: 90%; + } +} +.header-wrapper .header-content .left { + display: flex; + align-items: center; + justify-content: flex-start; + height: 100%; + font-size: 2rem; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, transform; +} +.header-shrink .header-wrapper .header-content .left { + transform: scale(0.72); + transform-origin: left; +} +.header-wrapper .header-content .left .logo-image { + flex-shrink: 0; + width: 2.8rem; + height: 2.8rem; + margin-right: 0.5rem; +} +@media (max-width: 800px) { + .header-wrapper .header-content .left .logo-image { + width: 2.52rem; + height: 2.52rem; + } +} +@media (max-width: 500px) { + .header-wrapper .header-content .left .logo-image { + width: 2.24rem; + height: 2.24rem; + } +} +.header-wrapper .header-content .left .logo-image img { + width: 100%; + border-radius: 0.4rem; +} +.header-wrapper .header-content .left .site-name { + color: var(--text-color-1); + font-weight: 600; + font-size: 1.88rem; + line-height: 1; + letter-spacing: 1px; +} +@media (max-width: 800px) { + .header-wrapper .header-content .left .site-name { + font-size: 1.692rem; + } +} +@media (max-width: 500px) { + .header-wrapper .header-content .left .site-name { + font-size: 1.504rem; + } +} +.header-wrapper .header-content .right .pc .menu-list { + display: flex; + align-items: center; +} +@media (max-width: 800px) { + .header-wrapper .header-content .right .pc .menu-list { + display: none; + } +} +.header-wrapper .header-content .right .pc .menu-list .menu-item { + position: relative; + float: left; + box-sizing: border-box; + margin-left: 2rem; + color: var(--text-color-3); + font-size: 1rem; + cursor: pointer; +} +.header-wrapper .header-content .right .pc .menu-list .menu-item:first-child { + margin-left: 0; +} +.header-wrapper .header-content .right .pc .menu-list .menu-item a:hover::after, +.header-wrapper .header-content .right .pc .menu-list .menu-item .active::after { + position: absolute; + bottom: -10px; + left: 50%; + box-sizing: border-box; + width: 100%; + height: 2px; + background: var(--primary-color); + transform: translateX(-50%); + content: ''; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, transform, bottom; +} +.header-shrink .header-wrapper .header-content .right .pc .menu-list .menu-item a:hover::after, +.header-shrink .header-wrapper .header-content .right .pc .menu-list .menu-item .active::after { + bottom: calc(-1 * calc(var(--header-shrink-height) * 0.5 - 12px)); +} +.header-wrapper .header-content .right .pc .menu-list .menu-item.search { + margin-left: 26px; + font-size: 1.5rem; +} +.header-wrapper .header-content .right .pc .menu-list .menu-item.search i { + color: var(--text-color-3); +} +.header-wrapper .header-content .right .mobile { + display: flex; + align-items: center; + justify-content: space-between; +} +.header-wrapper .header-content .right .mobile .icon-item { + position: relative; + display: none; + width: 20px; + height: 20px; + margin-left: 12px; + color: var(--text-color-3); + font-size: 18px; + cursor: pointer; +} +.header-wrapper .header-content .right .mobile .icon-item i { + color: var(--text-color-3); +} +.header-wrapper .header-content .right .mobile .icon-item:first-child { + margin-left: 0; +} +@media (max-width: 800px) { + .header-wrapper .header-content .right .mobile .icon-item { + display: flex; + align-items: center; + justify-content: center; + } +} +.header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle { + position: relative; + width: 18px; + height: 2.5px; + background: var(--text-color-3); +} +.header-drawer-show .header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle { + background: transparent; +} +.header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle::before, +.header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle::after { + position: absolute; + left: 0; + width: 100%; + height: 2.5px; + background: var(--text-color-3); + content: ''; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.38s; + transition-property: color, background, box-shadow, border-color, transform; +} +.header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle::before { + top: -6px; +} +.header-drawer-show .header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle::before { + transform: translateY(6px) rotate(45deg); +} +.header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle::after { + bottom: -6px; +} +.header-drawer-show .header-wrapper .header-content .right .mobile .menu-bar .menu-bar-middle::after { + transform: translateY(-6px) rotate(-45deg); +} +.header-wrapper .header-drawer { + position: absolute; + top: 0; + left: 0; + z-index: 1002; + width: 100%; + padding: var(--header-height) 0 20px 0; + background: var(--background-color-1); + transform: scaleY(0); + transform-origin: top; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.38s; + transition-property: color, background, box-shadow, border-color, transform; +} +.header-drawer-show .header-wrapper .header-drawer { + transform: scaleY(1); +} +.header-wrapper .header-drawer .drawer-menu-list { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; +} +.header-wrapper .header-drawer .drawer-menu-list .drawer-menu-item { + height: 38px; + margin: 6px 0; + font-size: 1rem; +} +.header-wrapper .header-drawer .drawer-menu-list .drawer-menu-item a { + padding: 6px 20px; + color: var(--text-color-3); + border-radius: 20px; +} +.header-wrapper .header-drawer .drawer-menu-list .drawer-menu-item a:hover { + color: var(--text-color-2); + border: 1px solid var(--text-color-3); +} +.header-wrapper .header-drawer .drawer-menu-list .drawer-menu-item a.active { + color: var(--text-color-2); + border: 1px solid var(--text-color-3); +} +.header-wrapper .window-mask { + position: absolute; + top: 0; + z-index: 1001; + width: 100%; + height: 100vh; + background: rgba(0,0,0,0.4); + visibility: hidden; + opacity: 0; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.38s, 0.38s; + transition-property: color, background, box-shadow, border-color, transform, opacity; +} +.header-drawer-show .header-wrapper .window-mask { + visibility: visible; + opacity: 1; +} +.header-drawer-show { + overflow: hidden; +} +.post-tools-container { + padding-top: 38px; +} +.post-tools-container .tools-list li { + position: relative; + box-sizing: border-box; + width: 2.5rem; + height: 2.5rem; + margin-bottom: 0.8rem; + color: var(--text-color-3); + font-size: 1.2rem; + background: var(--background-color-1); + border-radius: 50%; + box-shadow: 2px 2px 5px var(--shadow-color); + cursor: pointer; +} +.post-tools-container .tools-list li:hover { + box-shadow: 2px 2px 8px var(--shadow-hover-color); +} +.post-tools-container .tools-list li i { + color: var(--text-color-3); +} +.post-tools-container .tools-list li:hover { + color: var(--background-color-1); + background: var(--primary-color); +} +.post-tools-container .tools-list li:hover i { + color: var(--background-color-1); +} +.post-tools-container .tools-list li:last-child { + margin-bottom: 0; +} +.post-tools-container .tools-list li.toggle-show-toc { + display: none; +} +.post-tools-container .tools-list li.go-to-comments .post-comments-count { + position: absolute; + top: 0; + right: -1rem; + display: none; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-width: 1.1rem; + height: 1.1rem; + padding: 0 0.2rem; + color: var(--badge-color); + font-size: 12px; + background: var(--badge-background-color); + border-radius: 0.4rem; +} +@media (max-width: 800px) { + .post-tools-container .tools-list li.go-to-comments .post-comments-count { + display: none !important; + } +} +.side-tools-show-handle { + transform: translateX(100%); + opacity: 0; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.26s, 0.26s; + transition-property: color, background, box-shadow, border-color, transform, opacity; +} +.side-tools-show-handle.show { + transform: translateX(0); + opacity: 1; +} +.side-tools-container .tools-item { + width: 2.2rem; + height: 2.2rem; + margin-bottom: 0.25rem; + color: var(--text-color-3); + font-size: 1.1rem; + background: var(--background-color-1); + border-right: none; + border-radius: 0.2rem; + box-shadow: 0.1rem 0.1rem 0.2rem var(--shadow-color); + cursor: pointer; +} +.side-tools-container .tools-item i { + color: var(--text-color-3); +} +.side-tools-container .tools-item:hover { + color: var(--background-color-1); + background: var(--primary-color); + box-shadow: 0.2rem 0.2rem 0.4rem var(--shadow-color); +} +.side-tools-container .tools-item:hover i { + color: var(--background-color-1); +} +@media (max-width: 800px) { + .side-tools-container .tools-item { + width: 1.98rem; + height: 1.98rem; + font-size: 0.99rem; + } + .side-tools-container .tools-item.toggle-show-toc-tablet, + .side-tools-container .tools-item.go-to-comments-tablet { + display: flex !important; + } +} +.side-tools-container .tools-item.rss a { + width: 100%; + height: 100%; + border-radius: 0.2rem; +} +.side-tools-container .tools-item.rss a:hover { + color: var(--background-color-1); + background: var(--primary-color); + box-shadow: 0.2rem 0.2rem 0.4rem var(--shadow-color); +} +.side-tools-container .tools-item.toggle-show-toc-tablet, +.side-tools-container .tools-item.go-to-comments-tablet { + display: none; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top { + display: none; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top.show { + display: flex; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top.show-arrow .percent { + display: none; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top.show-arrow .arrow { + display: flex; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top.show-percent .percent { + display: flex; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top.show-percent .arrow { + display: none; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top:hover .percent { + display: none; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top:hover .arrow { + display: flex; +} +.side-tools-container .exposed-tools-list .tool-scroll-to-top .percent { + font-size: 1rem; +} +.archive-list-container .archive-item { + margin-bottom: 38px; +} +.archive-list-container .archive-item:last-child { + margin-bottom: 0; +} +.archive-list-container .archive-item .archive-item-header { + margin-bottom: 0.8rem; +} +.archive-list-container .archive-item .archive-item-header .archive-year { + margin-right: 6px; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.6rem; +} +@media (max-width: 800px) { + .archive-list-container .archive-item .archive-item-header .archive-year { + font-size: 1.44rem; + } +} +.archive-list-container .archive-item .article-list { + padding-left: 1.2rem; +} +.archive-list-container .archive-item .article-list .article-item { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 0.816rem 0 0.816rem 1.2rem; + font-size: 1rem; + border-left: 1px dashed var(--border-color); +} +.archive-list-container .archive-item .article-list .article-item::before { + position: absolute; + top: 50%; + left: -0.3rem; + z-index: 1001; + box-sizing: border-box; + width: 0.6rem; + height: 0.6rem; + background: var(--text-color-5); + border-radius: 50%; + transform: translateY(-50%); + content: ''; +} +.archive-list-container .archive-item .article-list .article-item:hover::before { + background: var(--text-color-4); +} +.archive-list-container .archive-item .article-list .article-item:hover a.article-title { + color: var(--primary-color); +} +.archive-list-container .archive-item .article-list .article-item .article-date { + width: 3.6rem; + margin-right: 1.2rem; + color: var(--text-color-3); + font-size: 1rem; +} +.archive-list-container .archive-item .article-list .article-item a.article-title { + width: calc(100% - 3.6rem); + color: var(--text-color-3); +} +.footer { + padding: 1rem 0; + color: var(--text-color-4); + font-size: 1rem; +} +.footer a { + color: var(--text-color-4); +} +.footer a:hover { + color: var(--primary-color); +} +.footer a.no-pointer { + pointer-events: none; +} +.footer .website-info-box { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} +.footer .website-info-box.shields { + padding: 0 1rem; +} +.footer .website-info-box.shields img { + height: 22px !important; +} +.footer .website-info-box.shields a { + position: relative; + display: inline-flex; + box-sizing: border-box; + text-decoration: none; +} +.footer .website-info-box.shields .shields-item, +.footer .website-info-box.shields .count-box { + margin: 0.3rem 0.2rem; + user-select: none; +} +@media (max-width: 500px) { + .footer .website-info-box.shields .shields-item, + .footer .website-info-box.shields .count-box { + margin: 0.12rem -0.3rem; + transform: scale(0.9); + transform-origin: center center; + } +} +.footer .website-info-box .info-item { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + box-sizing: border-box; + color: var(--text-color-4); +} +.footer .website-info-box .info-item.shields .count-box { + display: flex; + justify-content: flex-start; + height: 22px; + color: #f2f2f2; + font-size: 12.6px; +} +.footer .website-info-box .info-item.shields .count-box .item-type, +.footer .website-info-box .info-item.shields .count-box .item-value { + display: flex; + align-items: center; + height: 100%; + padding: 0 6px; +} +.footer .website-info-box .info-item.shields .count-box .item-type { + margin-right: 0; + background: #555; +} +.footer .website-info-box .info-item.shields .count-box .item-value.uv { + background: #ff7f50; +} +.footer .website-info-box .info-item.shields .count-box .item-value.pv { + background: #ffa500; +} +.footer .website-info-box .info-item.shields .count-box .item-value.word { + background: #cd5c5c; +} +.footer .website-info-box .info-item.default { + margin: 0.2rem 0; +} +.footer .website-info-box .info-item.default .count-box { + margin-right: 15px; +} +.footer .website-info-box .info-item.default .count-box::before { + position: absolute; + top: 50%; + left: -10px; + box-sizing: border-box; + transform: translateY(-55%); + content: '|'; +} +.footer .website-info-box .info-item.default .count-box:first-child::before { + display: none; +} +.footer .website-info-box .info-item.default .count-box:last-child { + margin-right: 0; +} +.footer .website-info-box .info-item.default .count-box .item-type { + margin-right: 4px; +} +.footer .website-info-box .info-item.count-item { + display: flex; +} +.footer .website-info-box .info-item.count-item .count-box.uv, +.footer .website-info-box .info-item.count-item .count-box.pv { + display: none; +} +.footer .website-info-box .info-item.deploy-info { + display: flex; +} +.footer .website-info-box .info-item.deploy-info a, +.footer .website-info-box .info-item.deploy-info .tooltip { + display: flex; + align-items: center; +} +.footer .website-info-box .info-item.deploy-info img { + height: 1.08rem; + margin: 0 0.4rem; +} +.footer .icon-animate { + animation: heartbeat-animate 1.2s ease-in-out infinite; +} +.paginator { + margin-top: 2rem; + padding-top: 0.2rem; + user-select: none; +} +.paginator .btn-wrap { + cursor: not-allowed; +} +.paginator .btn-wrap.allowed { + cursor: pointer; +} +.paginator .btn-wrap.allowed a.jump-btn { + pointer-events: auto; +} +.paginator .btn-wrap.allowed a.jump-btn i { + color: var(--text-color-3); +} +.paginator .btn-wrap a.jump-btn { + padding: 0.2rem; + pointer-events: none; +} +.paginator .btn-wrap a.jump-btn i { + color: var(--text-color-4); + font-size: 1rem; +} +.paginator .base-color-size { + color: var(--text-color-3); + font-size: 1rem; +} +.paginator .page-number-box { + margin: 0 1rem; +} +.paginator .page-number-box .page-number-input { + width: 2.8rem; + height: 1.8rem; + margin: 0; + padding: 0; + font-weight: 400 !important; + text-align: center; + background: none; + border: 0.1rem solid var(--border-color); + border-radius: 0.3rem; + outline: none; + -webkit-appearance: none; +} +.paginator .page-number-box .page-number-input::-webkit-outer-spin-button, +.paginator .page-number-box .page-number-input::-webkit-inner-spin-button { + -webkit-appearance: none !important; +} +.paginator .page-number-box .page-number-input:hover, +.paginator .page-number-box .page-number-input:focus { + border-color: var(--primary-color); +} +.paginator .page-number-box .delimiter { + margin: 0 0.6rem; +} +.page-template-container { + box-sizing: border-box; + margin-bottom: 2rem; + padding: 2rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.page-template-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .page-template-container { + margin-bottom: 1.6rem; + padding: 1.6rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .page-template-container { + margin-bottom: 1.2rem; + padding: 1.2rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.page-template-container .page-template-content { + color: var(--text-color-3); +} +.page-template-container .page-template-content h1:first-child, +.page-template-container .page-template-content h2:first-child, +.page-template-container .page-template-content h3:first-child, +.page-template-container .page-template-content h4:first-child, +.page-template-container .page-template-content h5:first-child, +.page-template-container .page-template-content h6:first-child { + margin-top: 0; +} +.page-template-container .friends-link-list { + display: grid; + grid-gap: 1.2rem; + grid-template-columns: repeat(2, 1fr); +} +.page-template-container .friends-link-list .link-type-title { + grid-column: span 2; + margin-top: 0.5rem; + padding-left: 0.7rem; + color: var(--text-color-3); + font-weight: bold; + font-size: 1.4rem; +} +.page-template-container .friends-link-list .link-type-title::before { + position: absolute; + top: 50%; + left: 0; + width: 0.3rem; + height: 100%; + background: var(--primary-color); + border-radius: 0.2rem; + transform: translateY(-50%); + content: ''; +} +.page-template-container .friends-link-list .link-type-title i.fa-solid { + color: var(--text-color-3); +} +@media (max-width: 800px) { + .page-template-container .friends-link-list { + grid-gap: 1.1rem; + } + .page-template-container .friends-link-list .link-type-title { + margin-top: 0.4rem !important; + font-size: 1.1rem !important; + } + .page-template-container .friends-link-list .friends-link-item { + height: 4.2rem !important; + } + .page-template-container .friends-link-list .friends-link-item .a-wrap .avatar { + width: 4.2rem !important; + } + .page-template-container .friends-link-list .friends-link-item .a-wrap .avatar i.icon { + font-size: 1.6rem !important; + } + .page-template-container .friends-link-list .friends-link-item .a-wrap .details { + padding: 0.6rem 0.8rem !important; + } + .page-template-container .friends-link-list .friends-link-item .a-wrap .details .name { + font-size: 1.1rem !important; + } + .page-template-container .friends-link-list .friends-link-item .a-wrap .details .desc { + font-size: 0.9rem !important; + } +} +@media (max-width: 500px) { + .page-template-container .friends-link-list { + grid-gap: 1rem !important; + grid-template-columns: repeat(1, 1fr) !important; + } + .page-template-container .friends-link-list .link-type-title { + grid-column: span 1 !important; + } +} +.page-template-container .friends-link-list .friends-link-item { + width: 100%; + height: 5.2rem; + overflow: hidden; + border-radius: 0.6rem; + box-shadow: 1px 1px 3px var(--shadow-color); + cursor: pointer; +} +.page-template-container .friends-link-list .friends-link-item:hover { + box-shadow: 1px 1px 6px var(--shadow-color); +} +.page-template-container .friends-link-list .friends-link-item:hover .avatar img { + transform: scale(1.12); +} +.page-template-container .friends-link-list .friends-link-item .a-wrap { + display: flex; + justify-content: space-between; + width: 100%; + height: 100%; +} +.page-template-container .friends-link-list .friends-link-item .a-wrap .avatar { + flex-shrink: 0; + width: 5.2rem; + height: 100%; + overflow: hidden; + background-color: var(--background-color-2); +} +.page-template-container .friends-link-list .friends-link-item .a-wrap .avatar img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + background-color: var(--background-color-2); + transform-origin: center center; + transition-delay: 0s, 0s, 0s, 0s, 0.1s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.8s; + transition-property: color, background, box-shadow, border-color, transform; +} +.page-template-container .friends-link-list .friends-link-item .a-wrap .avatar i.icon { + color: var(--text-color-4); + font-size: 1.8rem; +} +.page-template-container .friends-link-list .friends-link-item .a-wrap .details { + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + height: 100%; + padding: 0.8rem 1rem; +} +.page-template-container .friends-link-list .friends-link-item .a-wrap .details .name { + width: 100%; + color: var(--text-color-3); + font-size: 1.2rem; +} +.page-template-container .friends-link-list .friends-link-item .a-wrap .details .desc { + width: 100%; + margin-top: 0.4rem; + color: var(--text-color-4); + font-size: 1rem; +} +.first-screen-container { + width: 100%; + height: 100vh; + overflow: hidden; + background: url("/images/bg.svg") center center/cover no-repeat; +} +.first-screen-container .first-screen-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + width: 80%; + height: 90%; + padding-top: calc(var(--header-height) * 0.5); +} +@media (max-width: 800px) { + .first-screen-container .first-screen-content { + padding-top: calc(var(--header-height) * 0.5 * 0.9); + } +} +@media (max-width: 500px) { + .first-screen-container .first-screen-content { + padding-top: calc(var(--header-height) * 0.5 * 0.8); + } +} +.first-screen-container .first-screen-content .description { + color: var(--first-screen-font-color-light); + font-weight: bold; + font-size: var(--first-screen-font-size); + line-height: 1.8; + text-align: center; + opacity: 0; +} +.dark-mode .first-screen-container .first-screen-content .description { + color: var(--first-screen-font-color-dark); +} +.dark-mode .first-screen-container .first-screen-content .description .desc-item .desc, +.dark-mode .first-screen-container .first-screen-content .description .desc-item .cursor { + color: var(--first-screen-font-color-dark); +} +.first-screen-container .first-screen-content .description .desc-item .desc { + color: var(--first-screen-font-color-light); +} +.first-screen-container .first-screen-content .description .desc-item .cursor { + color: var(--first-screen-font-color-light); + animation: blink-caret 0.8s step-end infinite; +} +@media (max-width: 800px) { + .first-screen-container .first-screen-content .description { + font-size: calc(var(--first-screen-font-size) * 0.9); + } +} +.first-screen-container .first-screen-content .bottom-placeholder { + width: 100%; +} +.first-screen-container .first-screen-content .bottom-placeholder .sc-icon-list { + display: flex; + justify-content: center; + box-sizing: border-box; + font-size: var(--first-screen-icon-size); +} +@media (max-width: 800px) { + .first-screen-container .first-screen-content .bottom-placeholder .sc-icon-list { + font-size: calc(var(--first-screen-icon-size) * 0.9); + } +} +.first-screen-container .first-screen-content .bottom-placeholder .sc-icon-list .sc-icon-item { + margin: 0 1rem; + cursor: pointer; +} +.first-screen-container .first-screen-content .bottom-placeholder .sc-icon-list .sc-icon-item i { + color: var(--text-color-3); +} +.zoom-in-image-mask { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1008; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + background: rgba(0,0,0,0); + visibility: hidden; + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.3s, 0.3s; + transition-property: color, background, box-shadow, border-color, visibility, background; +} +.zoom-in-image-mask.show { + background: rgba(0,0,0,0.5); + visibility: visible; +} +.zoom-in-image-mask.show .zoom-in-image { + cursor: zoom-out; +} +.zoom-in-image-mask .zoom-in-image { + position: absolute; + z-index: 1009; + transform-origin: center center; + will-change: transform; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.3s; + transition-property: color, background, box-shadow, border-color, transform; +} +.category-list-container { + box-sizing: border-box; + margin-bottom: 0; + padding: 2rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.category-list-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .category-list-container { + margin-bottom: 0; + padding: 1.6rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .category-list-container { + margin-bottom: 0; + padding: 1.2rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.category-list-container .category-list-content ul.site-all-category-list { + user-select: none; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item { + position: relative; + box-sizing: border-box; + margin-bottom: 0.8rem; + font-size: 1rem; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item:last-child { + margin-bottom: 0; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + padding: 0.5rem 0; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info .left { + position: relative; + box-sizing: border-box; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info .left .icon { + padding-right: 0.4rem; + color: var(--text-color-3); + font-size: 0.8rem; + cursor: pointer; +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .self-category-info .right .site-all-category-list-count { + color: var(--text-color-3); +} +.category-list-container .category-list-content ul.site-all-category-list > li.site-all-category-list-item .site-all-category-list-child { + position: relative; + box-sizing: border-box; + height: 0; + margin-left: 1.5rem; + overflow: hidden; + visibility: hidden; +} +.article-meta-info-container { + color: var(--text-color-4); +} +.article-meta-info-container.post { + font-size: 0.86rem; +} +.article-meta-info-container.post i.icon, +.article-meta-info-container.post i.fas { + font-size: 0.86rem; +} +.article-meta-info-container.home { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.92rem; +} +.article-meta-info-container.home i.icon, +.article-meta-info-container.home i.fas { + font-size: 0.92rem; +} +.article-meta-info-container a { + color: var(--text-color-4); +} +.article-meta-info-container a:hover { + color: var(--primary-color); +} +.article-meta-info-container .article-meta-info { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + letter-spacing: 0.6px; +} +.article-meta-info-container .article-meta-info .meta-info-item { + display: flex; + align-items: center; + justify-content: flex-start; + box-sizing: border-box; + margin-right: 0.6rem; +} +.article-meta-info-container .article-meta-info .meta-info-item:last-child { + margin-right: 0; +} +.article-meta-info-container .article-meta-info .meta-info-item ul, +.article-meta-info-container .article-meta-info .meta-info-item li { + display: inline; +} +.article-meta-info-container .article-meta-info .article-create-date { + display: var(--post-create-datetime); +} +.article-meta-info-container .article-meta-info .article-create-date .mobile { + display: none; +} +@media (max-width: 800px) { + .article-meta-info-container .article-meta-info .article-create-date .pc { + display: none; + } + .article-meta-info-container .article-meta-info .article-create-date .mobile { + display: inline-block; + } +} +.article-meta-info-container .article-meta-info .article-update-date { + display: var(--post-update-datetime); +} +@media (max-width: 800px) { + .article-meta-info-container .article-meta-info .article-update-date { + display: none; + } +} +.article-meta-info-container .article-meta-info .article-category { + word-spacing: 0; +} +.article-meta-info-container .article-meta-info .article-category .category-item i.icon { + font-size: 0.644rem; +} +@media (max-width: 500px) { + .article-meta-info-container .article-meta-info .article-category { + display: none !important; + } +} +.article-meta-info-container .article-meta-info .article-tag { + word-spacing: 0; +} +.article-meta-info-container .article-meta-info .article-tag .tag-item { + margin-right: 0.4rem; +} +.article-meta-info-container .article-meta-info .article-tag .tag-item:last-child { + margin-right: 0; +} +.article-meta-info-container .article-meta-info .article-tag .tag-item .tag-separator { + margin-right: 0.2rem; +} +.article-meta-info-container .article-meta-info .article-tag .tag-item .tag-separator i.icon { + font-size: 0.644rem; +} +@media (max-width: 800px) { + .article-meta-info-container .article-meta-info .article-tag { + display: none !important; + } +} +.article-meta-info-container .article-meta-info .article-pv { + display: none; +} +.article-meta-info-container .home-read-more { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.92rem; +} +.article-meta-info-container .home-read-more i.fas { + color: var(--text-color-4); + font-size: 1.012rem; +} +.article-copyright-info-container { + width: 100%; + margin-top: 1rem; + background: var(--background-color-2); + border-radius: calc(var(--box-border-radius) * 0.6); +} +.article-copyright-info-container .copyright-info-content { + width: 100%; + padding: 1.3rem; + overflow: hidden; + font-size: 1rem; +} +.article-copyright-info-container .copyright-info-content .copyright-info-top { + width: 100%; +} +.article-copyright-info-container .copyright-info-content .copyright-info-top .post-title { + width: 100%; + color: var(--text-color-3); + font-size: 1.1rem; +} +.article-copyright-info-container .copyright-info-content .copyright-info-top .post-link { + width: 100%; + margin-top: 0.2rem; + color: var(--text-color-4); +} +.article-copyright-info-container .copyright-info-content .copyright-info-bottom { + display: flex; + justify-content: flex-start; + margin-top: 1rem; +} +.article-copyright-info-container .copyright-info-content .copyright-info-bottom .bottom-item { + z-index: 1010; + margin-right: 1.2rem; +} +.article-copyright-info-container .copyright-info-content .copyright-info-bottom .bottom-item .type { + color: var(--text-color-4); +} +.article-copyright-info-container .copyright-info-content .copyright-info-bottom .bottom-item .content { + margin-top: 0.2rem; + color: var(--text-color-3); +} +.article-copyright-info-container .copyright-info-content .copyright-info-bottom .post-license .content i { + font-size: 1.1rem; +} +.article-copyright-info-container .copyright-info-content .copyright-bg { + position: absolute; + top: 50%; + right: 2rem; + color: var(--copyright-icon-bg-color); + font-size: 14rem; + transform: translateY(-50%); +} +@media (max-width: 500px) { + .article-copyright-info-container .copyright-info-content .copyright-bg { + font-size: 12rem; + } +} +.article-copyright-info-container .copy-copyright-info { + position: absolute; + top: 0.5rem; + right: 0.5rem; + box-sizing: border-box; + padding: 0.3rem; + cursor: pointer; +} +.article-copyright-info-container .copy-copyright-info i { + color: var(--text-color-3); +} +.home-content-container { + background: var(--background-color-1); +} +.home-content-container .home-article-list .home-article-item { + position: relative; + overflow: hidden; + box-sizing: border-box; + margin-bottom: 2.8rem; + padding: 0; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s; + transition-property: color, background, box-shadow, border-color, transform; +} +.home-content-container .home-article-list .home-article-item:hover { + transform: scale(var(--home-post-hover-scale)); + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .home-content-container .home-article-list .home-article-item { + margin-bottom: 2.24rem; + padding: 0; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .home-content-container .home-article-list .home-article-item { + margin-bottom: 1.68rem; + padding: 0; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.home-content-container .home-article-list .home-article-item .post-sticky-box { + position: absolute; + z-index: 1001; + display: flex; + align-items: center; + box-sizing: border-box; + padding: 0.1rem 0.4rem; + color: var(--text-color-4); + font-size: 12px; + letter-spacing: 1px; + background: var(--background-color-3); + border-radius: 0.3rem; + transform: scale(0.86); + cursor: default; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.home-content-container .home-article-list .home-article-item .post-sticky-box.cover { + top: 0.6rem; + right: 0.6rem; +} +.home-content-container .home-article-list .home-article-item .post-sticky-box.card { + top: 0.8rem; + right: 0.8rem; +} +@media (max-width: 500px) { + .home-content-container .home-article-list .home-article-item .post-sticky-box { + padding: 0; + background: none; + transform: rotate(45deg); + } + .home-content-container .home-article-list .home-article-item .post-sticky-box .sticky-name { + display: none; + } +} +.home-content-container .home-article-list .home-article-item .home-article-item-top { + width: 100%; + overflow: hidden; + background-color: var(--background-color-2); +} +@media (max-width: 800px) { + .home-content-container .home-article-list .home-article-item .home-article-item-top { + height: 7rem; + } +} +@media (max-width: 500px) { + .home-content-container .home-article-list .home-article-item .home-article-item-top { + height: 6rem; + } +} +.home-content-container .home-article-list .home-article-item .home-article-item-top .home-cover { + width: 100%; + height: 100%; + object-fit: cover; + transform-origin: center center; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, linear; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 1s; + transition-property: color, background, box-shadow, border-color, transform; +} +.home-content-container .home-article-list .home-article-item .home-article-item-top .home-cover:hover { + transform: scale(1.03); +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom { + padding: 2rem; +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom .sticky-icon { + position: absolute; + top: 1.2rem; + right: 1.2rem; + color: var(--text-color-4); + font-size: 1.2rem; + transform: rotate(45deg); +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom .home-article-title { + margin: 0; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.4rem; + line-height: 1.5; +} +@media (max-width: 800px) { + .home-content-container .home-article-list .home-article-item .home-article-item-bottom .home-article-title { + font-size: 1.3rem; + } +} +@media (max-width: 500px) { + .home-content-container .home-article-list .home-article-item .home-article-item-bottom .home-article-title { + font-size: 1.2rem; + } +} +.home-content-container .home-article-list .home-article-item .home-article-item-bottom .home-article-content { + margin: 1.8rem 0; + color: var(--text-color-3); + text-align: justify; + word-wrap: break-word; +} +.archives-container { + box-sizing: border-box; + margin-bottom: 2rem; + padding: 2rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.archives-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .archives-container { + margin-bottom: 1.6rem; + padding: 1.6rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .archives-container { + margin-bottom: 1.2rem; + padding: 1.2rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.post-page-container { + display: flex; + justify-content: space-between; + width: 100%; + height: 100%; +} +.post-page-container.show-toc .pc-post-toc { + display: block; +} +.post-page-container.show-toc .article-content-container { + width: calc(100% - 15rem); +} +@media (max-width: 800px) { + .post-page-container .pc-post-toc { + display: none !important; + } + .post-page-container .article-content-container { + width: 100% !important; + } +} +.post-page-container .article-content-container { + order: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + margin-bottom: 2rem; + padding: 0; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.post-page-container .article-content-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .post-page-container .article-content-container { + margin-bottom: 1.6rem; + padding: 0; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .post-page-container .article-content-container { + margin-bottom: 1.2rem; + padding: 0; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +@media (max-width: 500px) { + .post-page-container .article-content-container { + padding: 0 0.2rem !important; + box-shadow: none !important; + } + .post-page-container .article-content-container:hover { + box-shadow: none !important; + } +} +.post-page-container .article-content-container .article-content-top { + display: flex; + align-items: flex-end; + width: 100%; + padding-top: 2rem; + padding-right: 2rem; + overflow: hidden; + background-color: var(--background-color-2); + border-top-left-radius: var(--box-border-radius); + border-top-right-radius: var(--box-border-radius); +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-top { + height: 12.8rem; + padding-top: 1.6rem; + padding-right: 1.6rem; + } + .post-page-container .article-content-container .article-content-top .cover-article-title { + padding: 0.8rem 1.6rem !important; + font-size: 1.44rem !important; + line-height: 1.5 !important; + } +} +@media (max-width: 500px) { + .post-page-container .article-content-container .article-content-top { + height: 11.8rem; + padding-top: 1.2rem; + padding-right: 1.2rem; + } + .post-page-container .article-content-container .article-content-top .cover-article-title { + padding: 0.6rem 1.2rem !important; + font-size: 1.28rem !important; + line-height: 1.4 !important; + } +} +.post-page-container .article-content-container .article-content-top .cover-article-title { + position: relative; + z-index: 1001; + box-sizing: border-box; + max-width: 100%; + max-height: 100%; + padding: 1rem 2rem; + overflow-y: auto; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.76rem; + line-height: 1.6; + background: var(--background-color-1-transparent); + border-top-right-radius: var(--box-border-radius); + backdrop-filter: blur(2px); +} +.post-page-container .article-content-container .article-content-top .post-cover { + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + width: 100%; + height: 100%; + object-fit: cover; +} +.post-page-container .article-content-container .article-content-bottom { + padding: 2rem; +} +.post-page-container .article-content-container .article-content-bottom.has-cover { + padding-top: 1rem; +} +@media (max-width: 500px) { + .post-page-container .article-content-container .article-content-bottom { + padding: 1rem 0 !important; + } +} +.post-page-container .article-content-container .article-content-bottom .article-title { + color: var(--text-color-2); + font-weight: 600; + font-size: 1.6rem; + line-height: 1.6; +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-bottom .article-title { + font-size: 1.44rem; + } +} +@media (max-width: 500px) { + .post-page-container .article-content-container .article-content-bottom .article-title { + font-size: 1.28rem; + } +} +.post-page-container .article-content-container .article-content-bottom .article-header { + display: flex; + justify-content: flex-start; + width: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-header .avatar-box { + display: var(--post-author-avatar); + flex-shrink: 0; + width: 3.2rem; + height: 3.2rem; + margin-right: 0.8rem; + padding: 0.1rem; + border: 1px solid var(--border-color); + border-radius: 50%; +} +.post-page-container .article-content-container .article-content-bottom .article-header .avatar-box img { + width: 100%; + height: 100%; + background: var(--avatar-background-color); + border-radius: 50%; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box { + display: flex; + flex-direction: column; + justify-content: space-between; + box-sizing: border-box; + width: 100%; + height: 100%; + padding: 0.2rem 0; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .author { + display: flex; + align-items: center; + font-weight: 600; + font-size: 1.18rem; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .author .name { + color: var(--text-color-3); +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .author .author-label { + margin-left: 0.8rem; + padding: 0 0.4rem; + color: #fff; + font-weight: 500; + font-size: 0.8rem; + background: var(--selection-color); + border-radius: 0.4rem; +} +.post-page-container .article-content-container .article-content-bottom .article-header .info-box .meta-info { + margin-top: 0.1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-header, +.post-page-container .article-content-container .article-content-bottom .article-header-meta-info { + margin-top: 1rem; +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-bottom .article-header, + .post-page-container .article-content-container .article-content-bottom .article-header-meta-info { + transform: scale(0.9); + transform-origin: left top; + } +} +.post-page-container .article-content-container .article-content-bottom .article-content { + margin-top: 30.400000000000002px; + padding-bottom: 30.400000000000002px; + color: var(--text-color-3); + text-align: justify; + word-wrap: break-word; + border-bottom: 2px dashed var(--border-color); +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-bottom .article-content { + margin-top: 22.8px; + padding-bottom: 22.8px; + } +} +.post-page-container .article-content-container .article-content-bottom .article-content .article-aging-tips { + position: relative; + display: none; + box-sizing: border-box; + margin-bottom: 1.8rem; + padding: 1rem; + color: var(--note-warning-color); + line-height: 1.6; + background: var(--note-warning-background-color); + border: 0.1rem solid var(--note-warning-border-color); + border-radius: 0.4rem; +} +.post-page-container .article-content-container .article-content-bottom .article-content .article-aging-tips i { + margin-right: 0.4rem; + color: var(--note-warning-color); +} +.post-page-container .article-content-container .article-content-bottom .article-content .article-aging-tips .days { + color: var(--note-warning-color); +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share { + display: flex; + justify-content: space-between; + width: 100%; + margin-top: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share .post-tags-box { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-start; + width: 100%; + font-size: 0.96rem; +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share .post-tags-box .tag-item { + margin-right: 0.4rem; + line-height: 2; +} +.post-page-container .article-content-container .article-content-bottom .post-bottom-tags-and-share .post-tags-box .tag-item .icon { + font-size: 0.88rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav { + height: 2.8rem; + margin-top: 38px; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next { + box-sizing: border-box; + max-width: 14rem; + height: 100%; + box-sizing: border-box; + margin-bottom: 0; + padding: 0.8rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); + border-radius: 0.32rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev:hover, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-bottom .article-nav .article-prev, + .post-page-container .article-content-container .article-content-bottom .article-nav .article-next { + margin-bottom: 0; + padding: 0.64rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .post-page-container .article-content-container .article-content-bottom .article-nav .article-prev, + .post-page-container .article-content-container .article-content-bottom .article-nav .article-next { + margin-bottom: 0; + padding: 0.48rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a { + position: relative; + display: block; + box-sizing: border-box; + width: 100%; + height: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a.prev, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a.prev { + padding-left: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a.next, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a.next { + padding-right: 1rem; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .arrow-icon, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .arrow-icon { + position: absolute; + top: 0; + width: 1rem; + height: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .arrow-icon.left, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .arrow-icon.left { + left: 0; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .arrow-icon.right, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .arrow-icon.right { + right: 0; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev a .title, +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next a .title { + width: 100%; + height: 100%; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-prev { + float: left; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .article-next { + float: right; +} +.post-page-container .article-content-container .article-content-bottom .article-nav .post-nav-item { + display: none; +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-bottom .article-nav .post-nav-item { + display: inline-block; + } +} +@media (max-width: 800px) { + .post-page-container .article-content-container .article-content-bottom .article-nav .post-nav-title-item { + display: none; + } +} +.post-page-container .pc-post-toc { + position: sticky; + top: calc(var(--header-height) + 38px); + display: none; + flex-shrink: 0; + box-sizing: border-box; + width: 15rem; + max-height: calc(100vh - calc(var(--header-height) + 38px)); + transition-delay: 0s, 0s, 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.2s, 0.1s, 0.2s; + transition-property: color, background, box-shadow, border-color, top, display, max-height; +} +.post-page-container .pc-post-toc.right-toc { + order: 1; + padding: 2rem 0 2rem 1.8rem; +} +.post-page-container .pc-post-toc.left-toc { + order: -1; + padding: 2rem 1.8rem 2rem 0; +} +.header-shrink .post-page-container .pc-post-toc { + top: calc(var(--header-shrink-height) + 38px); + max-height: calc(100vh - calc(var(--header-shrink-height) + 38px)); +} +.category-container { + box-sizing: border-box; + margin-bottom: 2rem; + padding: 2rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.category-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .category-container { + margin-bottom: 1.6rem; + padding: 1.6rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .category-container { + margin-bottom: 1.2rem; + padding: 1.2rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.category-container .category-name { + margin-bottom: 38px; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.5rem; +} +.category-container .category-name i { + color: var(--text-color-2); +} +@media (max-width: 800px) { + .category-container .category-name { + font-size: 1.35rem; + } +} +@media (max-width: 500px) { + .category-container .category-name { + font-size: 1.2rem; + } +} +.tag-container { + box-sizing: border-box; + margin-bottom: 2rem; + padding: 2rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.tag-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .tag-container { + margin-bottom: 1.6rem; + padding: 1.6rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .tag-container { + margin-bottom: 1.2rem; + padding: 1.2rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.tag-container .tag-name { + margin-bottom: 38px; + color: var(--text-color-2); + font-weight: 600; + font-size: 1.5rem; +} +.tag-container .tag-name i { + color: var(--text-color-2); +} +@media (max-width: 800px) { + .tag-container .tag-name { + font-size: 1.35rem; + } +} +@media (max-width: 500px) { + .tag-container .tag-name { + font-size: 1.2rem; + } +} +.tagcloud-container { + box-sizing: border-box; + margin-bottom: 0; + padding: 2rem; + background: var(--content-background-color); + border-radius: var(--box-border-radius); + box-shadow: 0 0 8px var(--shadow-color); +} +.tagcloud-container:hover { + box-shadow: 0 0 12px var(--shadow-hover-color); +} +@media (max-width: 800px) { + .tagcloud-container { + margin-bottom: 0; + padding: 1.6rem; + border-radius: calc(var(--box-border-radius) * 0.8); + } +} +@media (max-width: 500px) { + .tagcloud-container { + margin-bottom: 0; + padding: 1.2rem; + border-radius: calc(var(--box-border-radius) * 0.6); + } +} +.tagcloud-container .tagcloud-content { + text-align: justify; +} +.tagcloud-container .tagcloud-content a { + display: inline-block; + box-sizing: border-box; + padding: 0.68rem 0.5rem; +} +.empty-content-box { + display: flex; + justify-content: center; + width: 100%; + padding: 4rem 0; +} +.empty-content-box i.fa-solid { + color: var(--text-color-4); +} +.post-share-container { + flex-shrink: 0; +} +.post-share-container .share-list-wrap { + display: flex; + justify-content: flex-end; +} +.post-share-container .share-list-wrap .share-item { + width: 1.8rem; + height: 1.8rem; + margin-left: 0.5rem; + padding: 0.4rem; + border-style: solid; + border-width: 0.1rem; + border-radius: 50%; + cursor: pointer; + transition-delay: 0s, 0s, 0s, 0s, 0s; + transition-timing-function: ease, ease, ease, ease, ease; + transition-duration: 0.2s, 0.2s, 0.2s, 0.2s, 0.3s; + transition-property: color, background, box-shadow, border-color, background; +} +.post-share-container .share-list-wrap .share-item i { + color: inherit; + font-size: 1rem; +} +.post-share-container .share-list-wrap .share-item.qq { + color: var(--note-primary-color); + border-color: var(--note-primary-color); +} +.post-share-container .share-list-wrap .share-item.qq:hover { + color: var(--background-color-1); + background: var(--note-primary-color); +} +.post-share-container .share-list-wrap .share-item.wechat { + color: var(--note-success-color); + border-color: var(--note-success-color); +} +.post-share-container .share-list-wrap .share-item.wechat:hover { + color: var(--background-color-1); + background: var(--note-success-color); +} +.post-share-container .share-list-wrap .share-item.weibo { + color: var(--note-danger-color); + border-color: var(--note-danger-color); +} +.post-share-container .share-list-wrap .share-item.weibo:hover { + color: var(--background-color-1); + background: var(--note-danger-color); +} +.reward-author-container { + margin-top: 2rem; +} +.reward-author-container .tooltip-img .tooltip-img-box img { + max-height: 12rem; +} +.reward-author-container .reward-btn { + width: 3rem; + height: 3rem; + border-radius: 50%; +} +.reward-author-container .reward-btn i { + font-size: 1.2rem; +} diff --git a/font/css/brands.min.css b/font/css/brands.min.css new file mode 100644 index 0000000..3c911a9 --- /dev/null +++ b/font/css/brands.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-threads:before{content:"\e618"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-debian:before{content:"\e60b"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-square-threads:before{content:"\e619"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-x-twitter:before{content:"\e61b"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-stubber:before{content:"\e5c7"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-odysee:before{content:"\e5c6"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-square-x-twitter:before{content:"\e61a"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"} \ No newline at end of file diff --git a/font/css/fontawesome.min.css b/font/css/fontawesome.min.css new file mode 100644 index 0000000..9a90619 --- /dev/null +++ b/font/css/fontawesome.min.css @@ -0,0 +1,9 @@ +/*! + * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{-webkit-animation-name:fa-beat;animation-name:fa-beat;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{-webkit-animation-name:fa-bounce;animation-name:fa-bounce;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{-webkit-animation-name:fa-fade;animation-name:fa-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{-webkit-animation-name:fa-beat-fade;animation-name:fa-beat-fade;-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1));animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{-webkit-animation-name:fa-flip;animation-name:fa-flip;-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,ease-in-out);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{-webkit-animation-name:fa-shake;animation-name:fa-shake;-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{-webkit-animation-delay:var(--fa-animation-delay,0s);animation-delay:var(--fa-animation-delay,0s);-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-duration:var(--fa-animation-duration,2s);animation-duration:var(--fa-animation-duration,2s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,linear);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{-webkit-animation-name:fa-spin;animation-name:fa-spin;-webkit-animation-direction:var(--fa-animation-direction,normal);animation-direction:var(--fa-animation-direction,normal);-webkit-animation-duration:var(--fa-animation-duration,1s);animation-duration:var(--fa-animation-duration,1s);-webkit-animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-iteration-count:var(--fa-animation-iteration-count,infinite);-webkit-animation-timing-function:var(--fa-animation-timing,steps(8));animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{-webkit-animation-delay:-1ms;animation-delay:-1ms;-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1;-webkit-transition-delay:0s;transition-delay:0s;-webkit-transition-duration:0s;transition-duration:0s}}@-webkit-keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-beat{0%,90%{-webkit-transform:scale(1);transform:scale(1)}45%{-webkit-transform:scale(var(--fa-beat-scale,1.25));transform:scale(var(--fa-beat-scale,1.25))}}@-webkit-keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@keyframes fa-bounce{0%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}10%{-webkit-transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0);transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{-webkit-transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em));transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{-webkit-transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0);transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{-webkit-transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em));transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}to{-webkit-transform:scale(1) translateY(0);transform:scale(1) translateY(0)}}@-webkit-keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@-webkit-keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);-webkit-transform:scale(1);transform:scale(1)}50%{opacity:1;-webkit-transform:scale(var(--fa-beat-fade-scale,1.125));transform:scale(var(--fa-beat-fade-scale,1.125))}}@-webkit-keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-flip{50%{-webkit-transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg));transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@-webkit-keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes fa-shake{0%{-webkit-transform:rotate(-15deg);transform:rotate(-15deg)}4%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}8%,24%{-webkit-transform:rotate(-18deg);transform:rotate(-18deg)}12%,28%{-webkit-transform:rotate(18deg);transform:rotate(18deg)}16%{-webkit-transform:rotate(-22deg);transform:rotate(-22deg)}20%{-webkit-transform:rotate(22deg);transform:rotate(22deg)}32%{-webkit-transform:rotate(-12deg);transform:rotate(-12deg)}36%{-webkit-transform:rotate(12deg);transform:rotate(12deg)}40%,to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}.fa-rotate-by{-webkit-transform:rotate(var(--fa-rotate-angle,none));transform:rotate(var(--fa-rotate-angle,none))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} + +.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-arrow-turn-right:before,.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"} +.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0} \ No newline at end of file diff --git a/font/css/regular.min.css b/font/css/regular.min.css new file mode 100644 index 0000000..1feae61 --- /dev/null +++ b/font/css/regular.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400} \ No newline at end of file diff --git a/font/css/solid.min.css b/font/css/solid.min.css new file mode 100644 index 0000000..770d98f --- /dev/null +++ b/font/css/solid.min.css @@ -0,0 +1,6 @@ +/*! + * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900} \ No newline at end of file diff --git a/font/webfonts/fa-brands-400.ttf b/font/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000..30f55b7 Binary files /dev/null and b/font/webfonts/fa-brands-400.ttf differ diff --git a/font/webfonts/fa-brands-400.woff2 b/font/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000..8a480d9 Binary files /dev/null and b/font/webfonts/fa-brands-400.woff2 differ diff --git a/font/webfonts/fa-regular-400.ttf b/font/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000..c79589d Binary files /dev/null and b/font/webfonts/fa-regular-400.ttf differ diff --git a/font/webfonts/fa-regular-400.woff2 b/font/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000..059a94e Binary files /dev/null and b/font/webfonts/fa-regular-400.woff2 differ diff --git a/font/webfonts/fa-solid-900.ttf b/font/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000..e479fb2 Binary files /dev/null and b/font/webfonts/fa-solid-900.ttf differ diff --git a/font/webfonts/fa-solid-900.woff2 b/font/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000..88b0367 Binary files /dev/null and b/font/webfonts/fa-solid-900.woff2 differ diff --git a/font/webfonts/fa-v4compatibility.ttf b/font/webfonts/fa-v4compatibility.ttf new file mode 100644 index 0000000..ba6cb25 Binary files /dev/null and b/font/webfonts/fa-v4compatibility.ttf differ diff --git a/font/webfonts/fa-v4compatibility.woff2 b/font/webfonts/fa-v4compatibility.woff2 new file mode 100644 index 0000000..23b1c47 Binary files /dev/null and b/font/webfonts/fa-v4compatibility.woff2 differ diff --git a/friends/index.html b/friends/index.html new file mode 100644 index 0000000..75524be --- /dev/null +++ b/friends/index.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + friends | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ +

friends

+ +
+ +
+
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/images/avatar.svg b/images/avatar.svg new file mode 100644 index 0000000..cfe6832 --- /dev/null +++ b/images/avatar.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/bg.svg b/images/bg.svg new file mode 100644 index 0000000..f576b34 --- /dev/null +++ b/images/bg.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/deploy-provider/aliyun.png b/images/deploy-provider/aliyun.png new file mode 100644 index 0000000..c4e9c7f Binary files /dev/null and b/images/deploy-provider/aliyun.png differ diff --git a/images/deploy-provider/gitee.png b/images/deploy-provider/gitee.png new file mode 100644 index 0000000..4701bbf Binary files /dev/null and b/images/deploy-provider/gitee.png differ diff --git a/images/deploy-provider/github.png b/images/deploy-provider/github.png new file mode 100644 index 0000000..0bd612d Binary files /dev/null and b/images/deploy-provider/github.png differ diff --git a/images/deploy-provider/netlify.png b/images/deploy-provider/netlify.png new file mode 100644 index 0000000..42e7e42 Binary files /dev/null and b/images/deploy-provider/netlify.png differ diff --git a/images/deploy-provider/tencent_cloud.png b/images/deploy-provider/tencent_cloud.png new file mode 100644 index 0000000..9abe109 Binary files /dev/null and b/images/deploy-provider/tencent_cloud.png differ diff --git a/images/deploy-provider/upyun.png b/images/deploy-provider/upyun.png new file mode 100644 index 0000000..98feb4d Binary files /dev/null and b/images/deploy-provider/upyun.png differ diff --git a/images/deploy-provider/vercel.png b/images/deploy-provider/vercel.png new file mode 100644 index 0000000..fc66aff Binary files /dev/null and b/images/deploy-provider/vercel.png differ diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 0000000..9933ffc --- /dev/null +++ b/images/logo.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/mod.svg b/images/mod.svg new file mode 100644 index 0000000..1ea01ae --- /dev/null +++ b/images/mod.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..b399f39 --- /dev/null +++ b/index.html @@ -0,0 +1,945 @@ + + + + + + + + + + + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + +
+ + + + +
+
+
+ +
+ +
+ + + +
随缘应变觉无界,因果循环见自然
+ + + +
+ + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ + + + + +
+ + + + +
+ + + + + + + +
+ + + + +
+ +
+
+
+ + + + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + + +
+
    + +
  • + + + +
    + +

    + + 北境之地资源加成模组 + +

    + +
    + +

    记录一次Northgard(北境之地)模组编写的过程

    + +
    + + + + + +
    +
  • + +
  • + + + +
    + +

    + + Hexo环境配置 + +

    + +
    + +

    电脑有时会出现一些奇怪的问题要重装系统,导致很多环境要重新配置。Hexo是环境配置中最复杂的每次都要在百度查很久,为此今天重新配置环境就顺便写一篇文章来记录。

    + +
    + + + + + +
    +
  • + +
  • + + + +
    + +

    + + 基于Node.js跨平台局域网消息传输与QNAP部署 + +

    + +
    + + 0x1 引言0x1.1 目的与背景当我刚进入大学时,我得到了我的第一台游戏本。当时,我经常需要在手机和电脑之间传输文件和文档。最初,我使用QQ和手机热点来进行传输,这显得相当繁琐。后来,我尝试了蓝牙传输,但速度过慢。尽管我使用了小米的热点文件传输功能... + +
    + + + + + +
    +
  • + +
  • + + + +
    + +

    + + 基于MCreator+Fabric+Forge的自定义工作台 + +

    + +
    + + 前言玩Minecraft已经有9年了一直都想给Minecraft写Mod,但苦于不会写代码并且几年前就连搭建开发环境都一直没有成功。现在MCreator可以解决这些问题,只需要几分钟就能设计出属于你的Mod +名词释义模组 Modification 缩... + +
    + + + + + +
    +
  • + +
  • + + + +
    + +

    + + 植物大战僵尸阳光修改器 + +

    + +
    + + 前言一直都想搞一个植物大战僵尸的修改器,想通过学习自己制作一个 +打算使用Cheat Engine修改器找到阳光的真正地址,然后再用易语言制作一个针对植物大战僵尸的阳光修改器 +名词释义植物大战僵尸:可怕的僵尸即将入侵你的家,唯一的防御方式就是你栽种的植... + +
    + + + + + +
    +
  • + +
  • + + + +
    + +

    + + Minecraft BSL 汉化包 + +

    + +
    + + BSL 是 Minecraft 的光影包,前两天在官网下载了最新版发现没有中文支持,然后自己就花了两天时间汉化完了。 +需要注意的 +只有一两处还是英文,这两个还有英文的地方我在原英文语言文件中没找到,应该是其他我不知道文件中的,不过总体上不影响使用。 +... + +
    + + + + + +
    +
  • + +
+ +
+
+ +
+ + +
+ /2 +
+ + +
+ +
+
+ + + + +
+ + + +
+ +
+ +
+ +
+
+ + + +
+ 由 Hexo 驱动 & 主题 Keep +
+ + + + +
+ + 本站由 提供部署服务 + +
+ + + +
+ + + + + +
+
+
+ +
+
+ + + + + +
+
+
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + + + + +
  • + +
  • +
+ +
    + + + + +
  • + +
  • + +
  • + + +
  • +
+
+ +
+ + +
+ +
+ + + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + + + + diff --git a/js/back2top.js b/js/back2top.js new file mode 100644 index 0000000..9edffbe --- /dev/null +++ b/js/back2top.js @@ -0,0 +1,42 @@ +/* global KEEP */ + +KEEP.initBack2Top = () => { + KEEP.utils.back2Top = { + back2BottomBtn: document.querySelector('.tool-scroll-to-bottom'), + back2TopBtn: document.querySelector('.tool-scroll-to-top'), + + back2top() { + window.anime({ + targets: document.scrollingElement, + easing: 'easeOutExpo', + scrollTop: 0 + }) + }, + + back2Bottom() { + const scrollHeight = document.body.scrollHeight || document.documentElement.scrollHeight + window.anime({ + targets: document.scrollingElement, + easing: 'easeInExpo', + scrollTop: scrollHeight + }) + }, + + initBack2Top() { + this.back2TopBtn && + this.back2TopBtn.addEventListener('click', () => { + this.back2top() + }) + }, + + initBack2Bottom() { + this.back2BottomBtn && + this.back2BottomBtn.addEventListener('click', () => { + this.back2Bottom() + }) + } + } + + KEEP.utils.back2Top.initBack2Top() + KEEP.utils.back2Top.initBack2Bottom() +} diff --git a/js/category-page.js b/js/category-page.js new file mode 100644 index 0000000..0d1bc90 --- /dev/null +++ b/js/category-page.js @@ -0,0 +1,66 @@ +/* global KEEP */ + +function resetCategoriesPage() { + const resetCategoryDom = (domList) => { + domList.forEach((dom) => { + const categoryNameDom = dom.querySelector('.site-all-category-list-link') + const categoryCountDom = dom.querySelector('.site-all-category-list-count') + const childCategoryInfo = dom.querySelector('.site-all-category-list-child') + + let domTemplate = ` +
+
${ + childCategoryInfo ? ' ' : '' + }${categoryNameDom.outerHTML}
+
${categoryCountDom.outerHTML}
+
+ ` + + if (childCategoryInfo) { + resetCategoryDom(childCategoryInfo.querySelectorAll('.site-all-category-list-item')) + domTemplate += childCategoryInfo.outerHTML + } + + dom.innerHTML = domTemplate + }) + } + + const expandHandle = () => { + const selfCategoryInfoDom = document.querySelectorAll('.site-all-category-list-item') + selfCategoryInfoDom.forEach((dom) => { + let isExpand = false + const iconDom = dom.querySelector('.self-category-info .left .icon') + const childDom = dom.querySelector('.site-all-category-list-child') + + iconDom && + iconDom.addEventListener('click', () => { + isExpand = !isExpand + if (childDom) { + if (isExpand) { + childDom.style.height = 'auto' + childDom.style.visibility = 'visible' + iconDom.classList.add('fa-chevron-down') + iconDom.classList.remove('fa-chevron-right') + } else { + childDom.style.height = '0' + childDom.style.visibility = 'hidden' + iconDom.classList.add('fa-chevron-right') + iconDom.classList.remove('fa-chevron-down') + } + } + }) + }) + } + + resetCategoryDom( + document.querySelectorAll('.site-all-category-list .site-all-category-list-item') + ) + + expandHandle() +} + +if (KEEP.theme_config.pjax.enable === true && KEEP.utils) { + resetCategoriesPage() +} else { + window.addEventListener('DOMContentLoaded', resetCategoriesPage) +} diff --git a/js/code-block.js b/js/code-block.js new file mode 100644 index 0000000..0f3b69c --- /dev/null +++ b/js/code-block.js @@ -0,0 +1,113 @@ +/* global KEEP */ + +KEEP.initCodeBlockTools = () => { + HTMLElement.prototype.wrap = function (wrapper) { + this.parentNode.insertBefore(wrapper, this) + this.parentNode.removeChild(this) + wrapper.appendChild(this) + } + + const { style: codeBlockStyle } = KEEP.theme_config?.code_block || {} + const { style: codeBlockToolsStyle } = KEEP.theme_config?.code_block?.tools || {} + + const isMac = (codeBlockStyle || codeBlockToolsStyle || 'default') === 'mac' + const foldedIconClassName = isMac ? 'fas fa-chevron-left' : 'fas fa-chevron-right' + const { + copy: copyLang, + copied: copiedLang, + fold: foldLang, + folded: foldedLang + } = KEEP.language_code_block + const foldDom = `` + + document.querySelectorAll('figure.highlight').forEach((element) => { + let codeLang = element.classList.length ? element.classList[1].toUpperCase() : '' + if (codeLang === 'PLAINTEXT') { + codeLang = '' + } + const highlightContainer = document.createElement('div') + highlightContainer.classList.add('highlight-container') + if (isMac) { + highlightContainer.classList.add('mac') + } + element.wrap(highlightContainer) + + const codeLangDom = `${codeLang ? '' + codeLang + '' : ''}` + + highlightContainer.insertAdjacentHTML( + 'afterbegin', + `
+ ${isMac ? foldDom + codeLangDom : '' + foldDom + codeLangDom + ''} + +
` + ) + const codeToolsBox = element.parentNode.querySelector('.code-tools-box') + const copyDom = codeToolsBox.querySelector('.copy') + const targetFoldDom = codeToolsBox.querySelector('.fold') + + copyDom.addEventListener('click', (event) => { + const target = event.currentTarget + const code = [...element.querySelectorAll('.code .line')] + .map((line) => line.innerText) + .join('\n') + const tta = document.createElement('textarea') + tta.style.top = window.scrollY + 'px' + tta.style.position = 'absolute' + tta.style.opacity = '0' + tta.readOnly = true + tta.value = code + document.body.append(tta) + const selection = document.getSelection() + const selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false + tta.select() + tta.setSelectionRange(0, code.length) + tta.readOnly = false + const result = document.execCommand('copy') + + const copyIconDom = target.querySelector('i') + const copyTooltipDom = codeToolsBox.querySelector('.copy .tooltip-content') + + if (result) { + copyIconDom.className = 'fas fa-check' + copyTooltipDom && (copyTooltipDom.innerHTML = copiedLang) + } else { + copyIconDom.className = 'fas fa-times' + } + + tta.blur() + target.blur() + if (selected) { + selection.removeAllRanges() + selection.addRange(selected) + } + document.body.removeChild(tta) + }) + + copyDom.addEventListener('mouseleave', (event) => { + setTimeout(() => { + event.target.querySelector('i').className = 'fas fa-copy' + const copyTooltipDom = codeToolsBox.querySelector('.copy .tooltip-content') + copyTooltipDom && (copyTooltipDom.innerHTML = copyLang) + }, 500) + }) + + let isFold = false + targetFoldDom.addEventListener('click', (event) => { + const target = event.currentTarget + const icon = target.querySelector('i') + const foldTooltipDom = codeToolsBox.querySelector('.fold .tooltip-content') + isFold = !isFold + if (isFold) { + icon.className = foldedIconClassName + element.classList.add('folded') + codeToolsBox.classList.add('folded') + foldTooltipDom && (foldTooltipDom.innerHTML = foldedLang) + } else { + icon.className = 'fas fa-chevron-down' + element.classList.remove('folded') + codeToolsBox.classList.remove('folded') + foldTooltipDom && (foldTooltipDom.innerHTML = foldLang) + } + }) + }) +} diff --git a/js/dark-light-toggle.js b/js/dark-light-toggle.js new file mode 100644 index 0000000..08d853d --- /dev/null +++ b/js/dark-light-toggle.js @@ -0,0 +1,56 @@ +/* global KEEP */ + +KEEP.initModeToggle = () => { + KEEP.utils.modeToggle = { + themeModeToggleBtn: document.querySelector('.tool-dark-light-toggle'), + iconDom: document.querySelector('.tool-dark-light-toggle i'), + + enableLightMode() { + document.body.classList.remove('dark-mode') + document.body.classList.add('light-mode') + this.iconDom.className = 'fas fa-moon' + KEEP.styleStatus.isDark = false + KEEP.setStyleStatus() + }, + + enableDarkMode() { + document.body.classList.add('dark-mode') + document.body.classList.remove('light-mode') + this.iconDom.className = 'fas fa-sun' + KEEP.styleStatus.isDark = true + KEEP.setStyleStatus() + }, + + isDarkPrefersColorScheme() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)') + }, + + initModeStatus() { + const styleStatus = KEEP.getStyleStatus() + + if (styleStatus) { + styleStatus.isDark ? this.enableDarkMode() : this.enableLightMode() + } else { + this.isDarkPrefersColorScheme().matches ? this.enableDarkMode() : this.enableLightMode() + } + }, + + initModeToggleButton() { + this.themeModeToggleBtn.addEventListener('click', () => { + const isDark = document.body.classList.contains('dark-mode') + isDark ? this.enableLightMode() : this.enableDarkMode() + }) + }, + + initModeAutoTrigger() { + const isDarkMode = this.isDarkPrefersColorScheme() + isDarkMode.addEventListener('change', (e) => { + e.matches ? this.enableDarkMode() : this.enableLightMode() + }) + } + } + + KEEP.utils.modeToggle.initModeStatus() + KEEP.utils.modeToggle.initModeToggleButton() + KEEP.utils.modeToggle.initModeAutoTrigger() +} diff --git a/js/header-shrink.js b/js/header-shrink.js new file mode 100644 index 0000000..fe372b3 --- /dev/null +++ b/js/header-shrink.js @@ -0,0 +1,68 @@ +/* global KEEP */ + +KEEP.initHeaderShrink = () => { + KEEP.utils.headerShrink = { + headerWrapperDom: null, + isHeaderShrink: false, + headerHeight: 70, + + init() { + this.headerWrapperDom = document.querySelector('.header-wrapper') + if (this.headerWrapperDom) { + this.headerHeight = this.headerWrapperDom.getBoundingClientRect().height + } + }, + + headerShrink() { + const scrollTop = document.body.scrollTop || document.documentElement.scrollTop + const { enable, header_transparent } = KEEP.theme_config?.style?.first_screen || {} + const isHeaderTransparent = + enable === true && + header_transparent === true && + !window.location.pathname.includes('/page/') + + if (!this.isHeaderShrink && scrollTop > this.headerHeight) { + this.isHeaderShrink = true + document.body.classList.add('header-shrink') + if (isHeaderTransparent) { + this.headerWrapperDom.classList.add('transparent-2') + } + } else if (this.isHeaderShrink && scrollTop <= this.headerHeight) { + this.isHeaderShrink = false + document.body.classList.remove('header-shrink') + if (isHeaderTransparent) { + this.headerWrapperDom.classList.remove('transparent-2') + } + } + }, + + sideToolsBarShowHandle() { + const scrollTop = document.body.scrollTop || document.documentElement.scrollTop + const sideToolsDom = document.querySelector('.side-tools .side-tools-container') + if (scrollTop > this.headerHeight / 2) { + sideToolsDom.classList.add('show') + } else { + sideToolsDom.classList.remove('show') + } + }, + + toggleHeaderDrawerShow() { + const domList = [document.querySelector('.window-mask'), document.querySelector('.menu-bar')] + + if (KEEP.theme_config?.pjax?.enable === true) { + domList.push( + ...document.querySelectorAll('.header-drawer .drawer-menu-list .drawer-menu-item') + ) + } + + domList.forEach((v) => { + v.addEventListener('click', () => { + document.body.classList.toggle('header-drawer-show') + }) + }) + } + } + KEEP.utils.headerShrink.init() + KEEP.utils.headerShrink.headerShrink() + KEEP.utils.headerShrink.toggleHeaderDrawerShow() +} diff --git a/js/lazyload.js b/js/lazyload.js new file mode 100644 index 0000000..8b7cddf --- /dev/null +++ b/js/lazyload.js @@ -0,0 +1,42 @@ +/* global KEEP */ + +KEEP.initLazyLoad = () => { + const imgs = document.querySelectorAll('img') + let now = Date.now() + let needLoad = true + + function lazyload(imgs) { + now = Date.now() + needLoad = Array.from(imgs).some((i) => i.hasAttribute('lazyload')) + + const h = window.innerHeight + const s = document.documentElement.scrollTop || document.body.scrollTop + + imgs.forEach((img) => { + if (img.hasAttribute('lazyload') && !img.hasAttribute('loading')) { + if (h + s > img.offsetTop) { + img.setAttribute('loading', true) + const loadImageTimeout = setTimeout(() => { + const temp = new Image() + const src = img.getAttribute('data-src') + temp.src = src + temp.onload = () => { + img.src = src + img.removeAttribute('lazyload') + img.removeAttribute('loading') + clearTimeout(loadImageTimeout) + } + }, 500) + } + } + }) + } + + lazyload(imgs) + + window.onscroll = () => { + if (Date.now() - now > 50 && needLoad) { + lazyload(imgs) + } + } +} diff --git a/js/libs/anime.min.js b/js/libs/anime.min.js new file mode 100644 index 0000000..99b263a --- /dev/null +++ b/js/libs/anime.min.js @@ -0,0 +1,8 @@ +/* + * anime.js v3.1.0 + * (c) 2019 Julian Garnier + * Released under the MIT license + * animejs.com + */ + +!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):n.anime=e()}(this,function(){"use strict";var n={update:null,begin:null,loopBegin:null,changeBegin:null,change:null,changeComplete:null,loopComplete:null,complete:null,loop:1,direction:"normal",autoplay:!0,timelineOffset:0},e={duration:1e3,delay:0,endDelay:0,easing:"easeOutElastic(1, .5)",round:0},r=["translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","scale","scaleX","scaleY","scaleZ","skew","skewX","skewY","perspective"],t={CSS:{},springs:{}};function a(n,e,r){return Math.min(Math.max(n,e),r)}function o(n,e){return n.indexOf(e)>-1}function u(n,e){return n.apply(null,e)}var i={arr:function(n){return Array.isArray(n)},obj:function(n){return o(Object.prototype.toString.call(n),"Object")},pth:function(n){return i.obj(n)&&n.hasOwnProperty("totalLength")},svg:function(n){return n instanceof SVGElement},inp:function(n){return n instanceof HTMLInputElement},dom:function(n){return n.nodeType||i.svg(n)},str:function(n){return"string"==typeof n},fnc:function(n){return"function"==typeof n},und:function(n){return void 0===n},hex:function(n){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(n)},rgb:function(n){return/^rgb/.test(n)},hsl:function(n){return/^hsl/.test(n)},col:function(n){return i.hex(n)||i.rgb(n)||i.hsl(n)},key:function(r){return!n.hasOwnProperty(r)&&!e.hasOwnProperty(r)&&"targets"!==r&&"keyframes"!==r}};function c(n){var e=/\(([^)]+)\)/.exec(n);return e?e[1].split(",").map(function(n){return parseFloat(n)}):[]}function s(n,e){var r=c(n),o=a(i.und(r[0])?1:r[0],.1,100),u=a(i.und(r[1])?100:r[1],.1,100),s=a(i.und(r[2])?10:r[2],.1,100),f=a(i.und(r[3])?0:r[3],.1,100),l=Math.sqrt(u/o),d=s/(2*Math.sqrt(u*o)),p=d<1?l*Math.sqrt(1-d*d):0,h=1,v=d<1?(d*l-f)/p:-f+l;function g(n){var r=e?e*n/1e3:n;return r=d<1?Math.exp(-r*d*l)*(h*Math.cos(p*r)+v*Math.sin(p*r)):(h+v*r)*Math.exp(-r*l),0===n||1===n?n:1-r}return e?g:function(){var e=t.springs[n];if(e)return e;for(var r=0,a=0;;)if(1===g(r+=1/6)){if(++a>=16)break}else a=0;var o=r*(1/6)*1e3;return t.springs[n]=o,o}}function f(n){return void 0===n&&(n=10),function(e){return Math.round(e*n)*(1/n)}}var l,d,p=function(){var n=11,e=1/(n-1);function r(n,e){return 1-3*e+3*n}function t(n,e){return 3*e-6*n}function a(n){return 3*n}function o(n,e,o){return((r(e,o)*n+t(e,o))*n+a(e))*n}function u(n,e,o){return 3*r(e,o)*n*n+2*t(e,o)*n+a(e)}return function(r,t,a,i){if(0<=r&&r<=1&&0<=a&&a<=1){var c=new Float32Array(n);if(r!==t||a!==i)for(var s=0;s=.001?function(n,e,r,t){for(var a=0;a<4;++a){var i=u(e,r,t);if(0===i)return e;e-=(o(e,r,t)-n)/i}return e}(t,l,r,a):0===d?l:function(n,e,r,t,a){for(var u,i,c=0;(u=o(i=e+(r-e)/2,t,a)-n)>0?r=i:e=i,Math.abs(u)>1e-7&&++c<10;);return i}(t,i,i+e,r,a)}}}(),h=(l={linear:function(){return function(n){return n}}},d={Sine:function(){return function(n){return 1-Math.cos(n*Math.PI/2)}},Circ:function(){return function(n){return 1-Math.sqrt(1-n*n)}},Back:function(){return function(n){return n*n*(3*n-2)}},Bounce:function(){return function(n){for(var e,r=4;n<((e=Math.pow(2,--r))-1)/11;);return 1/Math.pow(4,3-r)-7.5625*Math.pow((3*e-2)/22-n,2)}},Elastic:function(n,e){void 0===n&&(n=1),void 0===e&&(e=.5);var r=a(n,1,10),t=a(e,.1,2);return function(n){return 0===n||1===n?n:-r*Math.pow(2,10*(n-1))*Math.sin((n-1-t/(2*Math.PI)*Math.asin(1/r))*(2*Math.PI)/t)}}},["Quad","Cubic","Quart","Quint","Expo"].forEach(function(n,e){d[n]=function(){return function(n){return Math.pow(n,e+2)}}}),Object.keys(d).forEach(function(n){var e=d[n];l["easeIn"+n]=e,l["easeOut"+n]=function(n,r){return function(t){return 1-e(n,r)(1-t)}},l["easeInOut"+n]=function(n,r){return function(t){return t<.5?e(n,r)(2*t)/2:1-e(n,r)(-2*t+2)/2}}}),l);function v(n,e){if(i.fnc(n))return n;var r=n.split("(")[0],t=h[r],a=c(n);switch(r){case"spring":return s(n,e);case"cubicBezier":return u(p,a);case"steps":return u(f,a);default:return u(t,a)}}function g(n){try{return document.querySelectorAll(n)}catch(n){return}}function m(n,e){for(var r=n.length,t=arguments.length>=2?arguments[1]:void 0,a=[],o=0;o1&&(r-=1),r<1/6?n+6*(e-n)*r:r<.5?e:r<2/3?n+(e-n)*(2/3-r)*6:n}if(0==u)e=r=t=i;else{var f=i<.5?i*(1+u):i+u-i*u,l=2*i-f;e=s(l,f,o+1/3),r=s(l,f,o),t=s(l,f,o-1/3)}return"rgba("+255*e+","+255*r+","+255*t+","+c+")"}(n):void 0;var e,r,t,a}function C(n){var e=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(n);if(e)return e[1]}function B(n,e){return i.fnc(n)?n(e.target,e.id,e.total):n}function P(n,e){return n.getAttribute(e)}function I(n,e,r){if(M([r,"deg","rad","turn"],C(e)))return e;var a=t.CSS[e+r];if(!i.und(a))return a;var o=document.createElement(n.tagName),u=n.parentNode&&n.parentNode!==document?n.parentNode:document.body;u.appendChild(o),o.style.position="absolute",o.style.width=100+r;var c=100/o.offsetWidth;u.removeChild(o);var s=c*parseFloat(e);return t.CSS[e+r]=s,s}function T(n,e,r){if(e in n.style){var t=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),a=n.style[e]||getComputedStyle(n).getPropertyValue(t)||"0";return r?I(n,a,r):a}}function D(n,e){return i.dom(n)&&!i.inp(n)&&(P(n,e)||i.svg(n)&&n[e])?"attribute":i.dom(n)&&M(r,e)?"transform":i.dom(n)&&"transform"!==e&&T(n,e)?"css":null!=n[e]?"object":void 0}function E(n){if(i.dom(n)){for(var e,r=n.style.transform||"",t=/(\w+)\(([^)]*)\)/g,a=new Map;e=t.exec(r);)a.set(e[1],e[2]);return a}}function F(n,e,r,t){var a,u=o(e,"scale")?1:0+(o(a=e,"translate")||"perspective"===a?"px":o(a,"rotate")||o(a,"skew")?"deg":void 0),i=E(n).get(e)||u;return r&&(r.transforms.list.set(e,i),r.transforms.last=e),t?I(n,i,t):i}function N(n,e,r,t){switch(D(n,e)){case"transform":return F(n,e,t,r);case"css":return T(n,e,r);case"attribute":return P(n,e);default:return n[e]||0}}function A(n,e){var r=/^(\*=|\+=|-=)/.exec(n);if(!r)return n;var t=C(n)||0,a=parseFloat(e),o=parseFloat(n.replace(r[0],""));switch(r[0][0]){case"+":return a+o+t;case"-":return a-o+t;case"*":return a*o+t}}function L(n,e){if(i.col(n))return O(n);if(/\s/g.test(n))return n;var r=C(n),t=r?n.substr(0,n.length-r.length):n;return e?t+e:t}function j(n,e){return Math.sqrt(Math.pow(e.x-n.x,2)+Math.pow(e.y-n.y,2))}function S(n){for(var e,r=n.points,t=0,a=0;a0&&(t+=j(e,o)),e=o}return t}function q(n){if(n.getTotalLength)return n.getTotalLength();switch(n.tagName.toLowerCase()){case"circle":return o=n,2*Math.PI*P(o,"r");case"rect":return 2*P(a=n,"width")+2*P(a,"height");case"line":return j({x:P(t=n,"x1"),y:P(t,"y1")},{x:P(t,"x2"),y:P(t,"y2")});case"polyline":return S(n);case"polygon":return r=(e=n).points,S(e)+j(r.getItem(r.numberOfItems-1),r.getItem(0))}var e,r,t,a,o}function $(n,e){var r=e||{},t=r.el||function(n){for(var e=n.parentNode;i.svg(e)&&i.svg(e.parentNode);)e=e.parentNode;return e}(n),a=t.getBoundingClientRect(),o=P(t,"viewBox"),u=a.width,c=a.height,s=r.viewBox||(o?o.split(" "):[0,0,u,c]);return{el:t,viewBox:s,x:s[0]/1,y:s[1]/1,w:u/s[2],h:c/s[3]}}function X(n,e){function r(r){void 0===r&&(r=0);var t=e+r>=1?e+r:0;return n.el.getPointAtLength(t)}var t=$(n.el,n.svg),a=r(),o=r(-1),u=r(1);switch(n.property){case"x":return(a.x-t.x)*t.w;case"y":return(a.y-t.y)*t.h;case"angle":return 180*Math.atan2(u.y-o.y,u.x-o.x)/Math.PI}}function Y(n,e){var r=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g,t=L(i.pth(n)?n.totalLength:n,e)+"";return{original:t,numbers:t.match(r)?t.match(r).map(Number):[0],strings:i.str(n)||e?t.split(r):[]}}function Z(n){return m(n?y(i.arr(n)?n.map(b):b(n)):[],function(n,e,r){return r.indexOf(n)===e})}function Q(n){var e=Z(n);return e.map(function(n,r){return{target:n,id:r,total:e.length,transforms:{list:E(n)}}})}function V(n,e){var r=x(e);if(/^spring/.test(r.easing)&&(r.duration=s(r.easing)),i.arr(n)){var t=n.length;2===t&&!i.obj(n[0])?n={value:n}:i.fnc(e.duration)||(r.duration=e.duration/t)}var a=i.arr(n)?n:[n];return a.map(function(n,r){var t=i.obj(n)&&!i.pth(n)?n:{value:n};return i.und(t.delay)&&(t.delay=r?0:e.delay),i.und(t.endDelay)&&(t.endDelay=r===a.length-1?e.endDelay:0),t}).map(function(n){return k(n,r)})}function z(n,e){var r=[],t=e.keyframes;for(var a in t&&(e=k(function(n){for(var e=m(y(n.map(function(n){return Object.keys(n)})),function(n){return i.key(n)}).reduce(function(n,e){return n.indexOf(e)<0&&n.push(e),n},[]),r={},t=function(t){var a=e[t];r[a]=n.map(function(n){var e={};for(var r in n)i.key(r)?r==a&&(e.value=n[r]):e[r]=n[r];return e})},a=0;a-1&&(_.splice(o,1),r=_.length)}else a.tick(e);t++}n()}else U=cancelAnimationFrame(U)}return n}();function rn(r){void 0===r&&(r={});var t,o=0,u=0,i=0,c=0,s=null;function f(n){var e=window.Promise&&new Promise(function(n){return s=n});return n.finished=e,e}var l,d,p,h,v,g,y,b,M=(d=w(n,l=r),p=w(e,l),h=z(p,l),v=Q(l.targets),g=W(v,h),y=J(g,p),b=K,K++,k(d,{id:b,children:[],animatables:v,animations:g,duration:y.duration,delay:y.delay,endDelay:y.endDelay}));f(M);function x(){var n=M.direction;"alternate"!==n&&(M.direction="normal"!==n?"normal":"reverse"),M.reversed=!M.reversed,t.forEach(function(n){return n.reversed=M.reversed})}function O(n){return M.reversed?M.duration-n:n}function C(){o=0,u=O(M.currentTime)*(1/rn.speed)}function B(n,e){e&&e.seek(n-e.timelineOffset)}function P(n){for(var e=0,r=M.animations,t=r.length;e2||(b=Math.round(b*p)/p)),h.push(b)}var k=d.length;if(k){g=d[0];for(var O=0;O0&&(M.began=!0,I("begin")),!M.loopBegan&&M.currentTime>0&&(M.loopBegan=!0,I("loopBegin")),d<=r&&0!==M.currentTime&&P(0),(d>=l&&M.currentTime!==e||!e)&&P(e),d>r&&d=e&&(u=0,M.remaining&&!0!==M.remaining&&M.remaining--,M.remaining?(o=i,I("loopComplete"),M.loopBegan=!1,"alternate"===M.direction&&x()):(M.paused=!0,M.completed||(M.completed=!0,I("loopComplete"),I("complete"),!M.passThrough&&"Promise"in window&&(s(),f(M)))))}return M.reset=function(){var n=M.direction;M.passThrough=!1,M.currentTime=0,M.progress=0,M.paused=!0,M.began=!1,M.loopBegan=!1,M.changeBegan=!1,M.completed=!1,M.changeCompleted=!1,M.reversePlayback=!1,M.reversed="reverse"===n,M.remaining=M.loop,t=M.children;for(var e=c=t.length;e--;)M.children[e].reset();(M.reversed&&!0!==M.loop||"alternate"===n&&1===M.loop)&&M.remaining++,P(M.reversed?M.duration:0)},M.set=function(n,e){return R(n,e),M},M.tick=function(n){i=n,o||(o=i),T((i+(u-o))*rn.speed)},M.seek=function(n){T(O(n))},M.pause=function(){M.paused=!0,C()},M.play=function(){M.paused&&(M.completed&&M.reset(),M.paused=!1,_.push(M),C(),U||en())},M.reverse=function(){x(),C()},M.restart=function(){M.reset(),M.play()},M.reset(),M.autoplay&&M.play(),M}function tn(n,e){for(var r=e.length;r--;)M(n,e[r].animatable.target)&&e.splice(r,1)}return"undefined"!=typeof document&&document.addEventListener("visibilitychange",function(){document.hidden?(_.forEach(function(n){return n.pause()}),nn=_.slice(0),rn.running=_=[]):nn.forEach(function(n){return n.play()})}),rn.version="3.1.0",rn.speed=1,rn.running=_,rn.remove=function(n){for(var e=Z(n),r=_.length;r--;){var t=_[r],a=t.animations,o=t.children;tn(e,a);for(var u=o.length;u--;){var i=o[u],c=i.animations;tn(e,c),c.length||i.children.length||o.splice(u,1)}a.length||o.length||t.pause()}},rn.get=N,rn.set=R,rn.convertPx=I,rn.path=function(n,e){var r=i.str(n)?g(n)[0]:n,t=e||100;return function(n){return{property:n,el:r,svg:$(r),totalLength:q(r)*(t/100)}}},rn.setDashoffset=function(n){var e=q(n);return n.setAttribute("stroke-dasharray",e),e},rn.stagger=function(n,e){void 0===e&&(e={});var r=e.direction||"normal",t=e.easing?v(e.easing):null,a=e.grid,o=e.axis,u=e.from||0,c="first"===u,s="center"===u,f="last"===u,l=i.arr(n),d=l?parseFloat(n[0]):parseFloat(n),p=l?parseFloat(n[1]):0,h=C(l?n[1]:n)||0,g=e.start||0+(l?d:0),m=[],y=0;return function(n,e,i){if(c&&(u=0),s&&(u=(i-1)/2),f&&(u=i-1),!m.length){for(var v=0;v-1&&_.splice(o,1);for(var s=0;s]+>/gi;var htmlAttribsRegex=/\s?[a-z:]+(?:=['"][^'">]+['"])*/gi;var matches=html.match(htmlRegex);if(matches&&matches.length){matches=matches[0].match(htmlAttribsRegex);if(matches.length){matches.shift();matches.forEach(function(htmlAttrib){var attr=htmlAttrib.trim().split("=");if(attr.length===1){tmpEl.documentElement.setAttribute(attr[0],true)}else{tmpEl.documentElement.setAttribute(attr[0],attr[1].slice(1,-1))}})}}tmpEl.documentElement.innerHTML=html;this.log("load content",tmpEl.documentElement.attributes,tmpEl.documentElement.innerHTML.length);if(document.activeElement&&contains(document,this.options.selectors,document.activeElement)){try{document.activeElement.blur()}catch(e){}}this.switchSelectors(this.options.selectors,tmpEl,document,options)},abortRequest:require("./lib/abort-request"),doRequest:require("./lib/send-request"),handleResponse:require("./lib/proto/handle-response"),loadUrl:function(href,options){options=typeof options==="object"?extend({},this.options,options):clone(this.options);this.log("load href",href,options);this.abortRequest(this.request);trigger(document,"pjax:send",options);this.request=this.doRequest(href,options,this.handleResponse.bind(this))},executeScripts:function(elements){elements.forEach(function(element){var code=element.text||element.textContent||element.innerHTML||"";var script=document.createElement("script");if(element.id){script.id=element.id}if(element.className){script.className=element.className}if(element.type){script.type=element.type}if(element.src){script.src=element.src;script.async=false}if(element.dataset.pjax!==undefined){script.dataset.pjax=""}if(code!==""){script.appendChild(document.createTextNode(code))}element.parentNode.replaceChild(script,element)})},afterAllSwitches:function(){var autofocusEl=Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop();if(autofocusEl&&document.activeElement!==autofocusEl){autofocusEl.focus()}this.options.selectors.forEach(function(selector){forEachEls(document.querySelectorAll(selector),function(el){if(el===0);})});var state=this.state;if(state.options.history){if(!window.history.state){this.lastUid=this.maxUid=newUid();window.history.replaceState({url:window.location.href,title:document.title,uid:this.maxUid,scrollPos:[0,0]},document.title)}this.lastUid=this.maxUid=newUid();window.history.pushState({url:state.href,title:state.options.title,uid:this.maxUid,scrollPos:[0,0]},state.options.title,state.href)}this.forEachSelectors(function(el){this.parseDOM(el)},this);trigger(document,"pjax:complete pjax:success",state.options);if(typeof state.options.analytics==="function"){state.options.analytics()}if(state.options.history){var a=document.createElement("a");a.href=this.state.href;if(a.hash){var name=a.hash.slice(1);name=decodeURIComponent(name);var curtop=0;var target=document.getElementById(name)||document.getElementsByName(name)[0];if(target){if(target.offsetParent){do{curtop+=target.offsetTop;target=target.offsetParent}while(target)}}window.scrollTo(0,curtop)}else if(state.options.scrollTo!==false){if(state.options.scrollTo.length>1){window.scrollTo(state.options.scrollTo[0],state.options.scrollTo[1])}else{window.scrollTo(0,state.options.scrollTo)}}}else if(state.options.scrollRestoration&&state.options.scrollPos){window.scrollTo(state.options.scrollPos[0],state.options.scrollPos[1])}this.state={numPendingSwitches:0,href:null,options:null}}};Pjax.isSupported=require("./lib/is-supported");if(Pjax.isSupported()){module.exports=Pjax}else{var stupidPjax=noop;for(var key in Pjax.prototype){if(Pjax.prototype.hasOwnProperty(key)&&typeof Pjax.prototype[key]==="function"){stupidPjax[key]=noop}}module.exports=stupidPjax}},{"./lib/abort-request":2,"./lib/events/on":3,"./lib/events/trigger":4,"./lib/foreach-els":5,"./lib/foreach-selectors":6,"./lib/is-supported":7,"./lib/parse-options":8,"./lib/proto/attach-form":9,"./lib/proto/attach-link":10,"./lib/proto/handle-response":11,"./lib/proto/log":12,"./lib/proto/parse-element":13,"./lib/send-request":14,"./lib/switches":16,"./lib/switches-selectors":15,"./lib/uniqueid":17,"./lib/util/clone":18,"./lib/util/contains":19,"./lib/util/extend":20,"./lib/util/noop":21}],2:[function(require,module,exports){var noop=require("./util/noop");module.exports=function(request){if(request&&request.readyState<4){request.onreadystatechange=noop;request.abort()}}},{"./util/noop":21}],3:[function(require,module,exports){var forEachEls=require("../foreach-els");module.exports=function(els,events,listener,useCapture){events=typeof events==="string"?events.split(" "):events;events.forEach(function(e){forEachEls(els,function(el){el.addEventListener(e,listener,useCapture)})})}},{"../foreach-els":5}],4:[function(require,module,exports){var forEachEls=require("../foreach-els");module.exports=function(els,events,opts){events=typeof events==="string"?events.split(" "):events;events.forEach(function(e){var event;event=document.createEvent("HTMLEvents");event.initEvent(e,true,true);event.eventName=e;if(opts){Object.keys(opts).forEach(function(key){event[key]=opts[key]})}forEachEls(els,function(el){var domFix=false;if(!el.parentNode&&el!==document&&el!==window){domFix=true;document.body.appendChild(el)}el.dispatchEvent(event);if(domFix){el.parentNode.removeChild(el)}})})}},{"../foreach-els":5}],5:[function(require,module,exports){module.exports=function(els,fn,context){if(els instanceof HTMLCollection||els instanceof NodeList||els instanceof Array){return Array.prototype.forEach.call(els,fn,context)}return fn.call(context,els)}},{}],6:[function(require,module,exports){var forEachEls=require("./foreach-els");module.exports=function(selectors,cb,context,DOMcontext){DOMcontext=DOMcontext||document;selectors.forEach(function(selector){forEachEls(DOMcontext.querySelectorAll(selector),cb,context)})}},{"./foreach-els":5}],7:[function(require,module,exports){module.exports=function(){return window.history&&window.history.pushState&&window.history.replaceState&&!navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)}},{}],8:[function(require,module,exports){var defaultSwitches=require("./switches");module.exports=function(options){options=options||{};options.elements=options.elements||"a[href], form[action]";options.selectors=options.selectors||["title",".js-Pjax"];options.switches=options.switches||{};options.switchesOptions=options.switchesOptions||{};options.history=typeof options.history==="undefined"?true:options.history;options.analytics=typeof options.analytics==="function"||options.analytics===false?options.analytics:defaultAnalytics;options.scrollTo=typeof options.scrollTo==="undefined"?0:options.scrollTo;options.scrollRestoration=typeof options.scrollRestoration!=="undefined"?options.scrollRestoration:true;options.cacheBust=typeof options.cacheBust==="undefined"?true:options.cacheBust;options.debug=options.debug||false;options.timeout=options.timeout||0;options.currentUrlFullReload=typeof options.currentUrlFullReload==="undefined"?false:options.currentUrlFullReload;if(!options.switches.head){options.switches.head=defaultSwitches.switchElementsAlt}if(!options.switches.body){options.switches.body=defaultSwitches.switchElementsAlt}return options};function defaultAnalytics(){if(window._gaq){_gaq.push(["_trackPageview"])}if(window.ga){ga("send","pageview",{page:location.pathname,title:document.title})}}},{"./switches":16}],9:[function(require,module,exports){var on=require("../events/on");var clone=require("../util/clone");var attrState="data-pjax-state";var formAction=function(el,event){if(isDefaultPrevented(event)){return}var options=clone(this.options);options.requestOptions={requestUrl:el.getAttribute("action")||window.location.href,requestMethod:el.getAttribute("method")||"GET"};var virtLinkElement=document.createElement("a");virtLinkElement.setAttribute("href",options.requestOptions.requestUrl);var attrValue=checkIfShouldAbort(virtLinkElement,options);if(attrValue){el.setAttribute(attrState,attrValue);return}event.preventDefault();if(el.enctype==="multipart/form-data"){options.requestOptions.formData=new FormData(el)}else{options.requestOptions.requestParams=parseFormElements(el)}el.setAttribute(attrState,"submit");options.triggerElement=el;this.loadUrl(virtLinkElement.href,options)};function parseFormElements(el){var requestParams=[];var formElements=el.elements;for(var i=0;i1||event.metaKey||event.ctrlKey||event.shiftKey||event.altKey){return"modifier"}if(el.protocol!==window.location.protocol||el.host!==window.location.host){return"external"}if(el.hash&&el.href.replace(el.hash,"")===window.location.href.replace(location.hash,"")){return"anchor"}if(el.href===window.location.href.split("#")[0]+"#"){return"anchor-empty"}}var isDefaultPrevented=function(event){return event.defaultPrevented||event.returnValue===false};module.exports=function(el){var that=this;el.setAttribute(attrState,"");on(el,"click",function(event){linkAction.call(that,el,event)});on(el,"keyup",function(event){if(event.keyCode===13){linkAction.call(that,el,event)}}.bind(this))}},{"../events/on":3,"../util/clone":18}],11:[function(require,module,exports){var clone=require("../util/clone");var newUid=require("../uniqueid");var trigger=require("../events/trigger");module.exports=function(responseText,request,href,options){options=clone(options||this.options);options.request=request;if(responseText===false){trigger(document,"pjax:complete pjax:error",options);return}var currentState=window.history.state||{};window.history.replaceState({url:currentState.url||window.location.href,title:currentState.title||document.title,uid:currentState.uid||newUid(),scrollPos:[document.documentElement.scrollLeft||document.body.scrollLeft,document.documentElement.scrollTop||document.body.scrollTop]},document.title,window.location.href);var oldHref=href;if(request.responseURL){if(href!==request.responseURL){href=request.responseURL}}else if(request.getResponseHeader("X-PJAX-URL")){href=request.getResponseHeader("X-PJAX-URL")}else if(request.getResponseHeader("X-XHR-Redirected-To")){href=request.getResponseHeader("X-XHR-Redirected-To")}var a=document.createElement("a");a.href=oldHref;var oldHash=a.hash;a.href=href;if(oldHash&&!a.hash){a.hash=oldHash;href=a.href}this.state.href=href;this.state.options=options;try{this.loadContent(responseText,options)}catch(e){trigger(document,"pjax:error",options);if(!this.options.debug){if(console&&console.error){console.error("Pjax switch fail: ",e)}return this.latestChance(href)}else{throw e}}}},{"../events/trigger":4,"../uniqueid":17,"../util/clone":18}],12:[function(require,module,exports){module.exports=function(){if(this.options.debug&&console){if(typeof console.log==="function"){console.log.apply(console,arguments)}else if(console.log){console.log(arguments)}}}},{}],13:[function(require,module,exports){var attrState="data-pjax-state";module.exports=function(el){switch(el.tagName.toLowerCase()){case"a":if(!el.hasAttribute(attrState)){this.attachLink(el)}break;case"form":if(!el.hasAttribute(attrState)){this.attachForm(el)}break;default:throw"Pjax can only be applied on or
submit"}}},{}],14:[function(require,module,exports){var updateQueryString=require("./util/update-query-string");module.exports=function(location,options,callback){options=options||{};var queryString;var requestOptions=options.requestOptions||{};var requestMethod=(requestOptions.requestMethod||"GET").toUpperCase();var requestParams=requestOptions.requestParams||null;var formData=requestOptions.formData||null;var requestPayload=null;var request=new XMLHttpRequest;var timeout=options.timeout||0;request.onreadystatechange=function(){if(request.readyState===4){if(request.status===200){callback(request.responseText,request,location,options)}else if(request.status!==0){callback(null,request,location,options)}}};request.onerror=function(e){console.log(e);callback(null,request,location,options)};request.ontimeout=function(){callback(null,request,location,options)};if(requestParams&&requestParams.length){queryString=requestParams.map(function(param){return param.name+"="+param.value}).join("&");switch(requestMethod){case"GET":location=location.split("?")[0];location+="?"+queryString;break;case"POST":requestPayload=queryString;break}}else if(formData){requestPayload=formData}if(options.cacheBust){location=updateQueryString(location,"t",Date.now())}request.open(requestMethod,location,true);request.timeout=timeout;request.setRequestHeader("X-Requested-With","XMLHttpRequest");request.setRequestHeader("X-PJAX","true");request.setRequestHeader("X-PJAX-Selectors",JSON.stringify(options.selectors));if(requestPayload&&requestMethod==="POST"&&!formData){request.setRequestHeader("Content-Type","application/x-www-form-urlencoded")}request.send(requestPayload);return request}},{"./util/update-query-string":22}],15:[function(require,module,exports){var forEachEls=require("./foreach-els");var defaultSwitches=require("./switches");module.exports=function(switches,switchesOptions,selectors,fromEl,toEl,options){var switchesQueue=[];selectors.forEach(function(selector){var newEls=fromEl.querySelectorAll(selector);var oldEls=toEl.querySelectorAll(selector);if(this.log){this.log("Pjax switch",selector,newEls,oldEls)}if(newEls.length!==oldEls.length){throw"DOM doesn’t look the same on new loaded page: ’"+selector+"’ - new "+newEls.length+", old "+oldEls.length}forEachEls(newEls,function(newEl,i){var oldEl=oldEls[i];if(this.log){this.log("newEl",newEl,"oldEl",oldEl)}var callback=switches[selector]?switches[selector].bind(this,oldEl,newEl,options,switchesOptions[selector]):defaultSwitches.outerHTML.bind(this,oldEl,newEl,options);switchesQueue.push(callback)},this)},this);this.state.numPendingSwitches=switchesQueue.length;switchesQueue.forEach(function(queuedSwitch){queuedSwitch()})}},{"./foreach-els":5,"./switches":16}],16:[function(require,module,exports){var on=require("./events/on");module.exports={outerHTML:function(oldEl,newEl){oldEl.outerHTML=newEl.outerHTML;this.onSwitch()},innerHTML:function(oldEl,newEl){oldEl.innerHTML=newEl.innerHTML;if(newEl.className===""){oldEl.removeAttribute("class")}else{oldEl.className=newEl.className}this.onSwitch()},switchElementsAlt:function(oldEl,newEl){oldEl.innerHTML=newEl.innerHTML;if(newEl.hasAttributes()){var attrs=newEl.attributes;for(var i=0;i${text.substring(hit.position, end)}` + prevEnd = end + }) + result += text.substring(prevEnd, slice.end) + return result + } + + const inputEventFunction = () => { + if (!isfetched) return + let searchText = searchInputDom.value.trim().toLowerCase() + let keywords = searchText.split(/[-\s]+/) + if (keywords.length > 1) { + keywords.push(searchText) + } + let resultItems = [] + if (searchText.length > 0) { + // Perform local searching + datas.forEach(({ title, content, url }) => { + let titleInLowerCase = title.toLowerCase() + let contentInLowerCase = content.toLowerCase() + let indexOfTitle = [] + let indexOfContent = [] + let searchTextCount = 0 + keywords.forEach((keyword) => { + indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false)) + indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false)) + }) + + // Show search results + if (indexOfTitle.length > 0 || indexOfContent.length > 0) { + let hitCount = indexOfTitle.length + indexOfContent.length + // Sort index by position of keyword + ;[indexOfTitle, indexOfContent].forEach((index) => { + index.sort((itemLeft, itemRight) => { + if (itemRight.position !== itemLeft.position) { + return itemRight.position - itemLeft.position + } + return itemLeft.word.length - itemRight.word.length + }) + }) + + let slicesOfTitle = [] + if (indexOfTitle.length !== 0) { + let tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText) + searchTextCount += tmp.searchTextCountInSlice + slicesOfTitle.push(tmp) + } + + let slicesOfContent = [] + while (indexOfContent.length !== 0) { + let item = indexOfContent[indexOfContent.length - 1] + let { position, word } = item + // Cut out 100 characters + let start = position - 20 + let end = position + 80 + if (start < 0) { + start = 0 + } + if (end < position + word.length) { + end = position + word.length + } + if (end > content.length) { + end = content.length + } + let tmp = mergeIntoSlice(start, end, indexOfContent, searchText) + searchTextCount += tmp.searchTextCountInSlice + slicesOfContent.push(tmp) + } + + // Sort slices in content by search text's count and hits' count + slicesOfContent.sort((sliceLeft, sliceRight) => { + if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) { + return sliceRight.searchTextCount - sliceLeft.searchTextCount + } else if (sliceLeft.hits.length !== sliceRight.hits.length) { + return sliceRight.hits.length - sliceLeft.hits.length + } + return sliceLeft.start - sliceRight.start + }) + + // Select top N slices in content + let upperBound = parseInt( + KEEP.theme_config.local_search.top_n_per_article + ? KEEP.theme_config.local_search.top_n_per_article + : 1, + 10 + ) + if (upperBound >= 0) { + slicesOfContent = slicesOfContent.slice(0, upperBound) + } + + let resultItem = '' + + if (slicesOfTitle.length !== 0) { + resultItem += `
  • ${highlightKeyword( + title, + slicesOfTitle[0] + )}` + } else { + resultItem += `
  • ${title}` + } + + slicesOfContent.forEach((slice) => { + resultItem += `

    ${highlightKeyword( + content, + slice + )}...

    ` + }) + + resultItem += '
  • ' + resultItems.push({ + item: resultItem, + id: resultItems.length, + hitCount, + searchTextCount + }) + } + }) + } + if (keywords.length === 1 && keywords[0] === '') { + resultContent.innerHTML = '
    ' + } else if (resultItems.length === 0) { + resultContent.innerHTML = '
    ' + } else { + resultItems.sort((resultLeft, resultRight) => { + if (resultLeft.searchTextCount !== resultRight.searchTextCount) { + return resultRight.searchTextCount - resultLeft.searchTextCount + } else if (resultLeft.hitCount !== resultRight.hitCount) { + return resultRight.hitCount - resultLeft.hitCount + } + return resultRight.id - resultLeft.id + }) + let searchResultList = '
      ' + resultItems.forEach((result) => { + searchResultList += result.item + }) + searchResultList += '
    ' + resultContent.innerHTML = searchResultList + window.pjax && window.pjax.refresh(resultContent) + } + } + + const fetchData = () => { + fetch(KEEP.hexo_config.root + searchPath) + .then((response) => response.text()) + .then((res) => { + // Get the contents from search data + isfetched = true + datas = isXml + ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map( + (element) => { + return { + title: element.querySelector('title').textContent, + content: element.querySelector('content').textContent, + url: element.querySelector('url').textContent + } + } + ) + : JSON.parse(res) + // Only match articles with not empty titles + datas = datas + .filter((data) => data.title) + .map((data) => { + data.title = data.title.trim() + data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '' + data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/') + return data + }) + // Remove loading animation + const noResultDom = document.querySelector('#no-result') + noResultDom && (noResultDom.innerHTML = '') + }) + } + + if (KEEP.theme_config?.local_search?.preload === true) { + fetchData() + } + + if (searchInputDom) { + searchInputDom.addEventListener('input', inputEventFunction) + } + + // Handle and trigger popup window + document.querySelectorAll('.search-popup-trigger').forEach((element) => { + element.addEventListener('click', () => { + document.body.style.overflow = 'hidden' + document.querySelector('.search-pop-overlay').classList.add('active') + setTimeout(() => searchInputDom.focus(), 500) + if (!isfetched) fetchData() + }) + }) + + // Monitor main search box + const onPopupClose = () => { + document.body.style.overflow = '' + document.querySelector('.search-pop-overlay').classList.remove('active') + } + + document.querySelector('.search-pop-overlay').addEventListener('click', (event) => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose() + } + }) + document.querySelector('.search-input-field-pre').addEventListener('click', () => { + searchInputDom.value = '' + searchInputDom.focus() + inputEventFunction() + }) + document.querySelector('.close-popup-btn').addEventListener('click', onPopupClose) + window.addEventListener('pjax:success', onPopupClose) + window.addEventListener('keyup', (event) => { + if (event.key === 'Escape') { + onPopupClose() + } + }) +} diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..55f5890 --- /dev/null +++ b/js/main.js @@ -0,0 +1,77 @@ +/* global KEEP */ + +window.addEventListener('DOMContentLoaded', () => { + const { version, local_search, code_block, lazyload } = KEEP.theme_config + + KEEP.themeInfo = { + theme: `Keep v${version}`, + author: 'XPoet', + repository: 'https://github.com/XPoet/hexo-theme-keep' + } + + KEEP.localStorageKey = 'KEEP-THEME-STATUS' + + KEEP.styleStatus = { + isDark: false, + fontSizeLevel: 0, + isShowToc: true + } + + // print theme base info + KEEP.printThemeInfo = () => { + console.log( + `\n %c ${KEEP.themeInfo.theme} %c ${KEEP.themeInfo.repository} \n`, + `color: #fadfa3; background: #333; padding: 6px 0;`, + `padding: 6px 0;` + ) + } + KEEP.printThemeInfo() + + // set version number of footer + KEEP.setFooterVersion = () => { + const vd = document.querySelector('.footer .keep-version') + vd && (vd.innerHTML = KEEP.themeInfo.theme) + const vd2 = document.querySelector('.footer .shields-keep-version') + vd2 && (vd2.src = vd2.src.replace('Keep', KEEP.themeInfo.theme)) + } + + // set styleStatus to localStorage + KEEP.setStyleStatus = () => { + localStorage.setItem(KEEP.localStorageKey, JSON.stringify(KEEP.styleStatus)) + } + + // get styleStatus from localStorage + KEEP.getStyleStatus = () => { + let temp = localStorage.getItem(KEEP.localStorageKey) + if (temp) { + temp = JSON.parse(temp) + for (let key in KEEP.styleStatus) { + KEEP.styleStatus[key] = temp[key] + } + return temp + } else { + return null + } + } + + KEEP.initExecute = () => { + KEEP.initUtils() + KEEP.initHeaderShrink() + KEEP.initModeToggle() + KEEP.initBack2Top() + KEEP.setFooterVersion() + + if (local_search?.enable === true) { + KEEP.initLocalSearch() + } + + if (code_block?.tools?.enable === true) { + KEEP.initCodeBlockTools() + } + + if (lazyload?.enable === true) { + KEEP.initLazyLoad() + } + } + KEEP.initExecute() +}) diff --git a/js/post-helper.js b/js/post-helper.js new file mode 100644 index 0000000..e6fd60a --- /dev/null +++ b/js/post-helper.js @@ -0,0 +1,273 @@ +/* global KEEP */ + +function initToggleShowToc() { + KEEP.utils.postHelper = { + postPageContainerDom: document.querySelector('.post-page-container'), + toggleShowTocBtn: document.querySelector('.toggle-show-toc'), + toggleShowTocTabletBtn: document.querySelector('.toggle-show-toc-tablet'), + toggleShowTocIcon: document.querySelector('.toggle-show-toc i'), + mainContentDom: document.querySelector('.main-content'), + postToolsDom: document.querySelector('.post-tools'), + + isShowToc: false, + + initToggleToc() { + this.toggleShowTocBtn && + this.toggleShowTocBtn.addEventListener('click', () => { + this.isShowToc = !this.isShowToc + KEEP.styleStatus.isShowToc = this.isShowToc + KEEP.setStyleStatus() + this.handleToggleToc(this.isShowToc) + }) + + this.toggleShowTocTabletBtn && + this.toggleShowTocTabletBtn.addEventListener('click', () => { + const tabletTocMask = document.querySelector('.tablet-post-toc-mask') + const tabletToc = tabletTocMask.querySelector('.tablet-post-toc') + + document.body.style.overflow = `hidden` + tabletTocMask.style.background = `rgba(0, 0, 0, 0.25)` + tabletTocMask.style.visibility = `visible` + tabletToc.style.transform = `translateX(0)` + + tabletTocMask.addEventListener('click', () => { + document.body.style.overflow = '' + tabletTocMask.style.background = `rgba(0, 0, 0, 0)` + tabletTocMask.style.visibility = `hidden` + tabletToc.style.transform = `translateX(-100%)` + }) + }) + }, + + handleToggleToc(isOpen) { + if (isOpen) { + this.postPageContainerDom.classList.add('show-toc') + document.body.classList.add('has-toc') + } else { + this.postPageContainerDom.classList.remove('show-toc') + document.body.classList.remove('has-toc') + } + + setTimeout(() => { + this.setPostToolsLayout() + }, 120) + }, + + hasToc(isOpen) { + if (this.toggleShowTocBtn) { + this.toggleShowTocBtn.style.display = 'flex' + this.isShowToc = isOpen + this.handleToggleToc(isOpen) + } + }, + + setPostToolsLayout(mcw) { + const mainContainerWidth = mcw + ? mcw + : this.mainContentDom.getBoundingClientRect().width.toFixed(0) + let offsetX = 5 + + if (window.innerWidth <= 800) { + offsetX = 3 + } + + const layout = KEEP.theme_config.toc?.layout === 'left' ? 'right' : 'left' + this.postToolsDom.style.opacity = `1` + this.postToolsDom.style[ + layout + ] = `calc((100vw - ${mainContainerWidth}px) / 2 - ${offsetX}rem)` + }, + + initSetPostToolsLeft() { + setTimeout(() => { + this.setPostToolsLayout() + }, 150) + + window.addEventListener('resize', () => { + this.setPostToolsLayout() + }) + }, + + // go comment anchor + goToComments() { + const commentsAnchor = document.querySelector('#comments-anchor') + const goToCommentsBtnList = [ + document.querySelector('.post-tools .go-to-comments'), + document.querySelector('.exposed-tools-list .go-to-comments-tablet') + ] + + goToCommentsBtnList.forEach((btn) => { + if (btn && commentsAnchor) { + btn.addEventListener('click', (event) => { + event.preventDefault() + let winScrollY = window.scrollY + winScrollY = winScrollY === 0 ? -20 : winScrollY + const offset = commentsAnchor.getBoundingClientRect().top + winScrollY + window.anime({ + targets: document.scrollingElement, + duration: 300, + easing: 'linear', + scrollTop: offset, + complete: () => { + setTimeout(() => { + KEEP.utils.pageTopDom.classList.add('hide') + }, 150) + } + }) + }) + } + }) + }, + + // watch comments count + watchPostCommentsCount() { + const commentsCountDom = this.postToolsDom.querySelector('.post-comments-count') + if (!commentsCountDom) return + const config = { attributes: true, childList: true } + + const callback = function (mutationsList) { + mutationsList.forEach((item) => { + if (item.type === 'childList') { + const count = Number(item.target.innerHTML) + if (count > 0) { + commentsCountDom.style.display = 'flex' + if (count > 99) { + commentsCountDom.innerHTML = '99+' + observer.disconnect() + } + } + } + }) + } + + const observer = new MutationObserver(callback) + observer.observe(commentsCountDom, config) + }, + + // set post link + initSetPostLink() { + const postLinkContentDom = document.querySelector('.copyright-info-content .post-link') + postLinkContentDom && (postLinkContentDom.innerHTML = decodeURI(window.location.href)) + }, + + // copy copyright info + copyCopyrightInfo() { + const cicDom = document.querySelector('.copyright-info-content') + const copyDom = document.querySelector('.copy-copyright-info') + const copyIcon = copyDom.querySelector('i') + + const ccLang = KEEP.language_copy_copyright + const colon = KEEP.hexo_config.language === 'en' ? ': ' : ':' + + let isCopied = false + + const setCopyDomContent = (class1, class2, content, copied) => { + if (copyIcon) { + copyIcon.classList.remove(class1) + copyIcon.classList.add(class2) + } + const tooltipDom = copyDom.querySelector('.tooltip-content') + tooltipDom && (tooltipDom.innerHTML = content) + isCopied = copied + } + + copyDom.addEventListener('click', () => { + if (!isCopied) { + const author = cicDom.querySelector('.post-author .content').innerHTML + const link = cicDom.querySelector('.post-link').innerHTML + const tgtTxt = `${ccLang.author}${colon}${author}\n${ccLang.link}${colon}${link}` + navigator.clipboard.writeText(tgtTxt).then(() => { + setCopyDomContent('fa-copy', 'fa-check', ccLang.copied, true) + }) + } + }) + + copyDom.addEventListener('mouseleave', () => { + setTimeout(() => { + setCopyDomContent('fa-check', 'fa-copy', ccLang.copy, false) + }, 500) + }) + }, + + // set article aging tips + setArticleAgingDays() { + const agingTipsDom = document.querySelector('.article-content .article-aging-tips') + if (agingTipsDom) { + const daysDom = agingTipsDom.querySelector('.days') + const nowTimestamp = Date.now() + const tmpTimeLength = 24 * 60 * 60 * 1000 + const agingDaysTimestamp = (agingTipsDom.dataset?.agingDays || 30) * tmpTimeLength + const postUpdateTimestamp = new Date(agingTipsDom.dataset.updateDate).getTime() + const timeDifference = nowTimestamp - postUpdateTimestamp + const timeDifferenceDays = (timeDifference / tmpTimeLength).toFixed(0) + if (timeDifference >= agingDaysTimestamp) { + daysDom.innerHTML = timeDifferenceDays + agingTipsDom.style.display = 'block' + } + } + }, + + formatDatetime(fmt = 'YYYY-MM-DD hh:mm:ss', timestamp = Date.now()) { + function padLeftZero(str) { + return `00${str}`.substr(str.length) + } + + const date = new Date(timestamp) + + if (/(y+)/.test(fmt) || /(Y+)/.test(fmt)) { + fmt = fmt.replace(RegExp.$1, `${date.getFullYear()}`.substr(4 - RegExp.$1.length)) + } + + const obj = { + 'M+': date.getMonth() + 1, + 'D+': date.getDate(), + 'd+': date.getDate(), + 'H+': date.getHours(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds() + } + + for (const key in obj) { + if (new RegExp(`(${key})`).test(fmt)) { + const str = `${obj[key]}` + fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str)) + } + } + return fmt + }, + + resetPostUpdateDate() { + const updateDateDom = document.querySelector( + '.article-meta-info-container .article-update-date .pc' + ) + const updated = new Date(updateDateDom.dataset.updated).getTime() + const format = KEEP.theme_config.post?.datetime_format || 'YYYY-MM-DD HH:mm:ss' + updateDateDom.innerHTML = this.formatDatetime(format, updated) + } + } + + KEEP.utils.postHelper.initSetPostToolsLeft() + KEEP.utils.postHelper.setArticleAgingDays() + KEEP.utils.postHelper.resetPostUpdateDate() + + if (KEEP.theme_config.toc?.enable === true) { + KEEP.utils.postHelper.initToggleToc() + } + + if (KEEP.theme_config.comment?.enable === true) { + KEEP.utils.postHelper.goToComments() + KEEP.utils.postHelper.watchPostCommentsCount() + } + + if (KEEP.theme_config.post?.copyright_info === true) { + KEEP.utils.postHelper.initSetPostLink() + KEEP.utils.postHelper.copyCopyrightInfo() + } +} + +if (KEEP.theme_config.pjax?.enable === true && KEEP.utils) { + initToggleShowToc() +} else { + window.addEventListener('DOMContentLoaded', initToggleShowToc) +} diff --git a/js/post/copyright-info.js b/js/post/copyright-info.js new file mode 100644 index 0000000..d851007 --- /dev/null +++ b/js/post/copyright-info.js @@ -0,0 +1,61 @@ +/* global KEEP */ + +function initCopyrightInfoHelper() { + KEEP.utils.copyrightInfoHelper = { + // set post link + initSetPostLink() { + const postLinkContentDom = document.querySelector('.copyright-info-content .post-link') + postLinkContentDom && (postLinkContentDom.innerHTML = decodeURI(window.location.href)) + }, + + // copy copyright info + copyCopyrightInfo() { + const cicDom = document.querySelector('.copyright-info-content') + const copyDom = document.querySelector('.copy-copyright-info') + const copyIcon = copyDom.querySelector('i') + + const ccLang = KEEP.language_copy_copyright + const colon = KEEP.hexo_config?.language === 'en' ? ': ' : ':' + + let isCopied = false + + const setCopyDomContent = (class1, class2, content, copied) => { + if (copyIcon) { + copyIcon.classList.remove(class1) + copyIcon.classList.add(class2) + } + const tooltipDom = copyDom.querySelector('.tooltip-content') + tooltipDom && (tooltipDom.innerHTML = content) + isCopied = copied + } + + copyDom.addEventListener('click', () => { + if (!isCopied) { + const author = cicDom.querySelector('.post-author .content').innerHTML + const link = cicDom.querySelector('.post-link').innerHTML + const tgtTxt = `${ccLang.author}${colon}${author}\n${ccLang.link}${colon}${link}` + navigator.clipboard.writeText(tgtTxt).then(() => { + setCopyDomContent('fa-copy', 'fa-check', ccLang.copied, true) + }) + } + }) + + copyDom.addEventListener('mouseleave', () => { + setTimeout(() => { + setCopyDomContent('fa-check', 'fa-copy', ccLang.copy, false) + }, 500) + }) + } + } + + if (KEEP.theme_config.post?.copyright_info === true) { + KEEP.utils.copyrightInfoHelper.initSetPostLink() + KEEP.utils.copyrightInfoHelper.copyCopyrightInfo() + } +} + +if (KEEP.theme_config.pjax?.enable === true && KEEP.utils) { + initCopyrightInfoHelper() +} else { + window.addEventListener('DOMContentLoaded', initCopyrightInfoHelper) +} diff --git a/js/post/post-helper.js b/js/post/post-helper.js new file mode 100644 index 0000000..6462d4d --- /dev/null +++ b/js/post/post-helper.js @@ -0,0 +1,223 @@ +/* global KEEP */ + +function initPostHelper() { + KEEP.utils.postHelper = { + postPageContainerDom: document.querySelector('.post-page-container'), + toggleShowTocBtn: document.querySelector('.toggle-show-toc'), + toggleShowTocTabletBtn: document.querySelector('.toggle-show-toc-tablet'), + toggleShowTocIcon: document.querySelector('.toggle-show-toc i'), + mainContentDom: document.querySelector('.main-content'), + postToolsDom: document.querySelector('.post-tools'), + + isShowToc: false, + + initToggleToc() { + this.toggleShowTocBtn && + this.toggleShowTocBtn.addEventListener('click', () => { + this.isShowToc = !this.isShowToc + KEEP.styleStatus.isShowToc = this.isShowToc + KEEP.setStyleStatus() + this.handleToggleToc(this.isShowToc) + }) + + this.toggleShowTocTabletBtn && + this.toggleShowTocTabletBtn.addEventListener('click', () => { + const tabletTocMask = document.querySelector('.tablet-post-toc-mask') + const tabletToc = tabletTocMask.querySelector('.tablet-post-toc') + + document.body.style.overflow = `hidden` + tabletTocMask.style.background = `rgba(0, 0, 0, 0.25)` + tabletTocMask.style.visibility = `visible` + tabletToc.style.transform = `translateX(0)` + + tabletTocMask.addEventListener('click', () => { + document.body.style.overflow = '' + tabletTocMask.style.background = `rgba(0, 0, 0, 0)` + tabletTocMask.style.visibility = `hidden` + tabletToc.style.transform = `translateX(-100%)` + }) + }) + }, + + handleToggleToc(isOpen) { + if (isOpen) { + this.postPageContainerDom.classList.add('show-toc') + document.body.classList.add('has-toc') + } else { + this.postPageContainerDom.classList.remove('show-toc') + document.body.classList.remove('has-toc') + } + + setTimeout(() => { + this.setPostToolsLayout() + }, 120) + }, + + hasToc(isOpen) { + if (this.toggleShowTocBtn) { + this.toggleShowTocBtn.style.display = 'flex' + this.isShowToc = isOpen + this.handleToggleToc(isOpen) + } + }, + + setPostToolsLayout(mcw) { + const mainContainerWidth = mcw + ? mcw + : this.mainContentDom.getBoundingClientRect().width.toFixed(0) + let offsetX = 5 + + if (window.innerWidth <= 800) { + offsetX = 3 + } + + const layout = KEEP.theme_config.toc?.layout === 'left' ? 'right' : 'left' + this.postToolsDom.style.opacity = `1` + this.postToolsDom.style[ + layout + ] = `calc((100vw - ${mainContainerWidth}px) / 2 - ${offsetX}rem)` + }, + + initSetPostToolsLeft() { + setTimeout(() => { + this.setPostToolsLayout() + }, 150) + + window.addEventListener('resize', () => { + this.setPostToolsLayout() + }) + }, + + // go comment anchor + goToComments() { + const commentsAnchor = document.querySelector('#comments-anchor') + const goToCommentsBtnList = [ + document.querySelector('.post-tools .go-to-comments'), + document.querySelector('.exposed-tools-list .go-to-comments-tablet') + ] + + goToCommentsBtnList.forEach((btn) => { + if (btn && commentsAnchor) { + btn.addEventListener('click', (event) => { + event.preventDefault() + let winScrollY = window.scrollY + winScrollY = winScrollY === 0 ? -20 : winScrollY + const offset = commentsAnchor.getBoundingClientRect().top + winScrollY + window.anime({ + targets: document.scrollingElement, + duration: 300, + easing: 'linear', + scrollTop: offset, + complete: () => { + setTimeout(() => { + KEEP.utils.pageTopDom.classList.add('hide') + }, 150) + } + }) + }) + } + }) + }, + + // watch comments count + watchPostCommentsCount() { + const commentsCountDom = this.postToolsDom.querySelector('.post-comments-count') + if (!commentsCountDom) return + const config = { attributes: true, childList: true } + + const callback = function (mutationsList) { + mutationsList.forEach((item) => { + if (item.type === 'childList') { + const count = Number(item.target.innerHTML) + if (count > 0) { + commentsCountDom.style.display = 'flex' + if (count > 99) { + commentsCountDom.innerHTML = '99+' + observer.disconnect() + } + } + } + }) + } + + const observer = new MutationObserver(callback) + observer.observe(commentsCountDom, config) + }, + + // set article aging tips + setArticleAgingDays() { + const agingTipsDom = document.querySelector('.article-content .article-aging-tips') + if (agingTipsDom) { + const daysDom = agingTipsDom.querySelector('.days') + const nowTimestamp = Date.now() + const tmpTimeLength = 24 * 60 * 60 * 1000 + const agingDaysTimestamp = (agingTipsDom.dataset?.agingDays || 30) * tmpTimeLength + const postUpdateTimestamp = new Date(agingTipsDom.dataset.updateDate).getTime() + const timeDifference = nowTimestamp - postUpdateTimestamp + const timeDifferenceDays = (timeDifference / tmpTimeLength).toFixed(0) + if (timeDifference >= agingDaysTimestamp) { + daysDom.innerHTML = timeDifferenceDays + agingTipsDom.style.display = 'block' + } + } + }, + + formatDatetime(fmt = 'YYYY-MM-DD hh:mm:ss', timestamp = Date.now()) { + function padLeftZero(str) { + return `00${str}`.substr(str.length) + } + + const date = new Date(timestamp) + + if (/(y+)/.test(fmt) || /(Y+)/.test(fmt)) { + fmt = fmt.replace(RegExp.$1, `${date.getFullYear()}`.substr(4 - RegExp.$1.length)) + } + + const obj = { + 'M+': date.getMonth() + 1, + 'D+': date.getDate(), + 'd+': date.getDate(), + 'H+': date.getHours(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds() + } + + for (const key in obj) { + if (new RegExp(`(${key})`).test(fmt)) { + const str = `${obj[key]}` + fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str)) + } + } + return fmt + }, + + resetPostUpdateDate() { + const updateDateDom = document.querySelector( + '.article-meta-info-container .article-update-date .pc' + ) + const updated = new Date(updateDateDom.dataset.updated).getTime() + const format = KEEP.theme_config.post?.datetime_format || 'YYYY-MM-DD HH:mm:ss' + updateDateDom.innerHTML = this.formatDatetime(format, updated) + } + } + + KEEP.utils.postHelper.initSetPostToolsLeft() + KEEP.utils.postHelper.setArticleAgingDays() + KEEP.utils.postHelper.resetPostUpdateDate() + + if (KEEP.theme_config.toc?.enable === true) { + KEEP.utils.postHelper.initToggleToc() + } + + if (KEEP.theme_config.comment?.enable === true) { + KEEP.utils.postHelper.goToComments() + KEEP.utils.postHelper.watchPostCommentsCount() + } +} + +if (KEEP.theme_config.pjax?.enable === true && KEEP.utils) { + initPostHelper() +} else { + window.addEventListener('DOMContentLoaded', initPostHelper) +} diff --git a/js/post/share.js b/js/post/share.js new file mode 100644 index 0000000..6a1bf22 --- /dev/null +++ b/js/post/share.js @@ -0,0 +1,46 @@ +/* global KEEP */ + +function initPostShareHelper() { + KEEP.utils.postShareHelper = { + postShareHandle() { + const pageUrl = window.location.href + const pageTitle = window.document.title + + const shareContainer = document.querySelector('.post-share-container .share-list-wrap') + + // WeChat share + const wechatShare = shareContainer.querySelector('.wechat') + wechatShare && + wechatShare.setAttribute( + 'data-tooltip-img-url', + `https://api.qrserver.com/v1/create-qr-code?data=${pageUrl}` + ) + + shareContainer.querySelectorAll('.share-item').forEach((item) => { + item.addEventListener('click', () => { + // QQ share + if (item.classList.contains('qq')) { + window.open(`https://connect.qq.com/widget/shareqq/index.html?url=${pageUrl}`) + } + + // WeiBo share + if (item.classList.contains('weibo')) { + window.open( + `https://service.weibo.com/share/share.php?url=${pageUrl}&title=${pageTitle}` + ) + } + }) + }) + } + } + + if (KEEP.theme_config.post?.share === true) { + KEEP.utils.postShareHelper.postShareHandle() + } +} + +if (KEEP.theme_config.pjax?.enable === true && KEEP.utils) { + initPostShareHelper() +} else { + window.addEventListener('DOMContentLoaded', initPostShareHelper) +} diff --git a/js/post/toc.js b/js/post/toc.js new file mode 100644 index 0000000..4255897 --- /dev/null +++ b/js/post/toc.js @@ -0,0 +1,134 @@ +/* global KEEP */ + +function initTOC() { + const pageContainer = document.querySelector('.page-container') + const postPageContainer = document.querySelector('.post-page-container') + const pcTocContainer = document.querySelector('.pc-post-toc') + const tabletTocContainer = document.querySelector('.tablet-post-toc') + + if (KEEP.utils.hasToc) { + KEEP.utils.tocHelper = { + pcTocNavSections: [], + + tabletTocNavSections: [], + + // get active index + getActiveIndex(navSections) { + if (!Array.isArray(navSections)) return + let index = navSections.findIndex((element) => { + return element && element.getBoundingClientRect().top - 20 > 0 + }) + if (index === -1) { + index = navSections.length - 1 + } else if (index > 0) { + index-- + } + return index + }, + + // active nav + activeNav() { + // pc + this.activateNavByIndex(pcTocContainer, this.getActiveIndex(this.pcTocNavSections)) + + // tablet + this.activateNavByIndex(tabletTocContainer, this.getActiveIndex(this.tabletTocNavSections)) + }, + + // register TOC Nav + registerTocNav() { + const register = (tocContainer) => { + return [...tocContainer.querySelectorAll('.post-toc li a.nav-link')].map((element) => { + const target = document.getElementById( + decodeURI(element.getAttribute('href')).replace('#', '') + ) + element.addEventListener('click', (event) => { + event.preventDefault() + let winScrollY = window.scrollY + winScrollY = winScrollY <= 1 ? -19 : winScrollY + const offset = target.getBoundingClientRect().top + winScrollY + window.anime({ + targets: document.scrollingElement, + duration: 500, + easing: 'linear', + scrollTop: offset, + complete: () => { + history.pushState(null, document.title, element.href) + setTimeout(() => { + KEEP.utils.pageTopDom.classList.add('hide') + }, 150) + } + }) + }) + return target + }) + } + // pc + this.pcTocNavSections = register(pcTocContainer) + + // tablet + this.tabletTocNavSections = register(tabletTocContainer) + }, + + activateNavByIndex(tocContainer, index) { + const target = tocContainer.querySelectorAll('.post-toc li a.nav-link')[index] + if (!target || target.classList.contains('active-current')) return + + tocContainer.querySelectorAll('.post-toc .active').forEach((element) => { + element.classList.remove('active', 'active-current') + }) + target.classList.add('active', 'active-current') + let parent = target.parentNode + while (!parent.matches('.post-toc')) { + if (parent.matches('li')) parent.classList.add('active') + parent = parent.parentNode + } + // Scrolling to center active TOC element if TOC content is taller than viewport. + const tocElement = tocContainer.querySelector('.post-toc-wrap') + window.anime({ + targets: tocElement, + duration: 200, + easing: 'linear', + scrollTop: + tocElement.scrollTop - + tocElement.offsetHeight / 2 + + target.getBoundingClientRect().top - + tocElement.getBoundingClientRect().top + }) + }, + + handleShowWhenHasToc() { + const openHandle = () => { + const styleStatus = KEEP.getStyleStatus() + const key = 'isShowToc' + if (styleStatus && styleStatus.hasOwnProperty(key)) { + KEEP.utils.postHelper.hasToc(styleStatus[key]) + } else { + KEEP.utils.postHelper.hasToc(true) + } + } + + const initOpenKey = 'init_open' + + if (KEEP.theme_config.toc.hasOwnProperty(initOpenKey)) { + KEEP.theme_config.toc[initOpenKey] ? openHandle() : KEEP.utils.postHelper.hasToc(false) + } else { + openHandle() + } + } + } + KEEP.utils.tocHelper.handleShowWhenHasToc() + KEEP.utils.tocHelper.registerTocNav() + } else { + pcTocContainer && postPageContainer.removeChild(pcTocContainer) + if (tabletTocContainer) { + pageContainer.removeChild(document.querySelector('.tablet-post-toc-mask')) + } + } +} + +if (KEEP.theme_config?.pjax?.enable === true && KEEP.utils) { + initTOC() +} else { + window.addEventListener('DOMContentLoaded', initTOC) +} diff --git a/js/toc.js b/js/toc.js new file mode 100644 index 0000000..ea9ff89 --- /dev/null +++ b/js/toc.js @@ -0,0 +1,137 @@ +/* global KEEP */ + +function initTOC() { + const pageContainer = document.querySelector('.page-container') + const postPageContainer = document.querySelector('.post-page-container') + const pcTocContainer = document.querySelector('.pc-post-toc') + const tabletTocContainer = document.querySelector('.tablet-post-toc') + + if (KEEP.utils.hasToc) { + KEEP.utils = { + ...KEEP.utils, + + pcTocNavSections: [], + + tabletTocNavSections: [], + + // get active index + getActiveIndex(navSections) { + if (!Array.isArray(navSections)) return + let index = navSections.findIndex((element) => { + return element && element.getBoundingClientRect().top - 20 > 0 + }) + if (index === -1) { + index = navSections.length - 1 + } else if (index > 0) { + index-- + } + return index + }, + + // active nav + activeNav() { + // pc + this.activateNavByIndex(pcTocContainer, this.getActiveIndex(this.pcTocNavSections)) + + // tablet + this.activateNavByIndex(tabletTocContainer, this.getActiveIndex(this.tabletTocNavSections)) + }, + + // register TOC Nav + registerTocNav() { + const register = (tocContainer) => { + return [...tocContainer.querySelectorAll('.post-toc li a.nav-link')].map((element) => { + const target = document.getElementById( + decodeURI(element.getAttribute('href')).replace('#', '') + ) + element.addEventListener('click', (event) => { + event.preventDefault() + let winScrollY = window.scrollY + winScrollY = winScrollY === 0 ? -20 : winScrollY + const offset = target.getBoundingClientRect().top + winScrollY + window.anime({ + targets: document.scrollingElement, + duration: 500, + easing: 'linear', + scrollTop: offset - 10, + complete: () => { + history.pushState(null, document.title, element.href) + setTimeout(() => { + KEEP.utils.pageTopDom.classList.add('hide') + }, 150) + } + }) + }) + return target + }) + } + // pc + this.pcTocNavSections = register(pcTocContainer) + + // tablet + this.tabletTocNavSections = register(tabletTocContainer) + }, + + activateNavByIndex(tocContainer, index) { + const target = tocContainer.querySelectorAll('.post-toc li a.nav-link')[index] + if (!target || target.classList.contains('active-current')) return + + tocContainer.querySelectorAll('.post-toc .active').forEach((element) => { + element.classList.remove('active', 'active-current') + }) + target.classList.add('active', 'active-current') + let parent = target.parentNode + while (!parent.matches('.post-toc')) { + if (parent.matches('li')) parent.classList.add('active') + parent = parent.parentNode + } + // Scrolling to center active TOC element if TOC content is taller than viewport. + const tocElement = tocContainer.querySelector('.post-toc-wrap') + window.anime({ + targets: tocElement, + duration: 200, + easing: 'linear', + scrollTop: + tocElement.scrollTop - + tocElement.offsetHeight / 2 + + target.getBoundingClientRect().top - + tocElement.getBoundingClientRect().top + }) + }, + + handleShowWhenHasToc() { + const openHandle = () => { + const styleStatus = KEEP.getStyleStatus() + const key = 'isShowToc' + if (styleStatus && styleStatus.hasOwnProperty(key)) { + KEEP.utils.postHelper.hasToc(styleStatus[key]) + } else { + KEEP.utils.postHelper.hasToc(true) + } + } + + const initOpenKey = 'init_open' + + if (KEEP.theme_config.toc.hasOwnProperty(initOpenKey)) { + KEEP.theme_config.toc[initOpenKey] ? openHandle() : KEEP.utils.postHelper.hasToc(false) + } else { + openHandle() + } + } + } + + KEEP.utils.handleShowWhenHasToc() + KEEP.utils.registerTocNav() + } else { + pcTocContainer && postPageContainer.removeChild(pcTocContainer) + if (tabletTocContainer) { + pageContainer.removeChild(document.querySelector('.tablet-post-toc-mask')) + } + } +} + +if (KEEP.theme_config?.pjax?.enable === true && KEEP.utils) { + initTOC() +} else { + window.addEventListener('DOMContentLoaded', initTOC) +} diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000..ec0fe69 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,670 @@ +/* global KEEP */ + +KEEP.initUtils = () => { + KEEP.utils = { + rootHtmlDom: document.querySelector('html'), + pageTopDom: document.querySelector('.page-main-content-top'), + scrollProgressBarDom: document.querySelector('.scroll-progress-bar'), + pjaxProgressBarDom: document.querySelector('.pjax-progress-bar'), + pjaxProgressIcon: document.querySelector('.pjax-progress-icon'), + back2TopBtn: document.querySelector('.tool-scroll-to-top'), + headerWrapperDom: document.querySelector('.header-wrapper'), + + innerHeight: window.innerHeight, + pjaxProgressBarTimer: null, + prevScrollValue: 0, + fontSizeLevel: 0, + isHasScrollProgressBar: false, + isHasScrollPercent: false, + isHeaderTransparent: false, + hasToc: false, + + // initialization data + initData() { + const { scroll, first_screen } = KEEP.theme_config?.style || {} + this.isHasScrollProgressBar = scroll?.progress_bar === true + this.isHasScrollPercent = scroll?.percent === true + const { enable, header_transparent } = first_screen || {} + this.isHeaderTransparent = + enable === true && + header_transparent === true && + !window.location.pathname.includes('/page/') + if (!this.isHeaderTransparent) { + this.headerWrapperDom.classList.remove('transparent-1', 'transparent-2') + } + }, + + // scroll Style Handle + styleHandleWhenScroll() { + const scrollTop = document.body.scrollTop || document.documentElement.scrollTop + const scrollHeight = document.body.scrollHeight || document.documentElement.scrollHeight + const clientHeight = window.innerHeight || document.documentElement.clientHeight + + const percent = Math.round((scrollTop / (scrollHeight - clientHeight)) * 100) || 0 + + // back to top + if (scrollTop > 10) { + this.back2TopBtn.classList.add('show') + } else { + this.back2TopBtn.classList.remove('show') + } + + // scroll progress bar + if (this.isHasScrollProgressBar && this.scrollProgressBarDom) { + const progressPercent = ((scrollTop / (scrollHeight - clientHeight)) * 100).toFixed(3) + this.scrollProgressBarDom.style.visibility = percent === 0 ? 'hidden' : 'visible' + this.scrollProgressBarDom.style.width = `${progressPercent}%` + } + + // scroll percent + if (this.isHasScrollPercent && this.back2TopBtn) { + this.back2TopBtn.classList.add('show-percent') + const percentDom = this.back2TopBtn.querySelector('.percent') + if (percent === 0 || percent === undefined) { + this.back2TopBtn.classList.remove('show') + } else { + this.back2TopBtn.classList.add('show') + percentDom.innerHTML = percent.toFixed(0) + if (percent > 99) { + this.back2TopBtn.classList.add('show-arrow') + } else { + this.back2TopBtn.classList.remove('show-arrow') + } + } + } + + // hide header handle + if (scrollTop > this.prevScrollValue && scrollTop > this.innerHeight) { + this.pageTopDom.classList.add('hide') + if (this.isHeaderTransparent) { + this.headerWrapperDom.classList.remove('transparent-1', 'transparent-2') + } + } else { + this.pageTopDom.classList.remove('hide') + if (this.isHeaderTransparent) { + if (scrollTop <= this.headerWrapperDom.getBoundingClientRect().height) { + this.headerWrapperDom.classList.remove('transparent-2') + this.headerWrapperDom.classList.add('transparent-1') + } else if (scrollTop < this.innerHeight) { + this.headerWrapperDom.classList.add('transparent-2') + } + } + } + + this.prevScrollValue = scrollTop + }, + + // register window scroll event + registerWindowScroll() { + window.addEventListener('scroll', () => { + // style handle when scroll + this.styleHandleWhenScroll() + + // TOC scroll handle + if (KEEP.theme_config?.toc?.enable === true && KEEP.utils?.tocHelper) { + KEEP.utils.tocHelper.activeNav() + } + + // header shrink + KEEP.utils.headerShrink.headerShrink() + + // side tools bar show handle + KEEP.utils.headerShrink.sideToolsBarShowHandle() + }) + }, + + // toggle show tools list + toggleShowToolsList() { + const sideToolsListDom = document.querySelector('.side-tools-list') + const toggleShowToolsDom = document.querySelector('.tool-toggle-show') + toggleShowToolsDom.addEventListener('click', (e) => { + sideToolsListDom.classList.toggle('show') + e.stopPropagation() + }) + sideToolsListDom.querySelectorAll('.tools-item').forEach((item) => { + item.addEventListener('click', (e) => { + e.stopPropagation() + }) + }) + document.addEventListener('click', () => { + sideToolsListDom.classList.contains('show') && sideToolsListDom.classList.remove('show') + }) + }, + + // global font adjust + globalFontAdjust() { + const fontSize = document.defaultView.getComputedStyle(document.body).fontSize + const fs = parseFloat(fontSize) + + const initFontSize = () => { + const styleStatus = KEEP.getStyleStatus() + if (styleStatus) { + this.fontSizeLevel = styleStatus.fontSizeLevel + setFontSize(this.fontSizeLevel) + } + } + + const setFontSize = (fontSizeLevel) => { + this.rootHtmlDom.style.setProperty( + 'font-size', + `${fs * (1 + fontSizeLevel * 0.05)}px`, + 'important' + ) + KEEP.styleStatus.fontSizeLevel = fontSizeLevel + KEEP.setStyleStatus() + } + + initFontSize() + + document.querySelector('.tool-font-adjust-plus').addEventListener('click', () => { + if (this.fontSizeLevel === 5) return + this.fontSizeLevel++ + setFontSize(this.fontSizeLevel) + }) + + document.querySelector('.tool-font-adjust-minus').addEventListener('click', () => { + if (this.fontSizeLevel <= 0) return + this.fontSizeLevel-- + setFontSize(this.fontSizeLevel) + }) + }, + + // init has TOC + initHasToc() { + const tocNavDoms = document.querySelectorAll('.post-toc-wrap .post-toc li') + if (tocNavDoms.length > 0) { + this.hasToc = true + document.body.classList.add('has-toc') + } else { + this.hasToc = false + document.body.classList.remove('has-toc') + } + }, + + // zoom in image + zoomInImage() { + let SIDE_GAP = 40 + let isZoomIn = false + let curWinScrollY = 0 + let selectedImgDom = null + const imgDomList = document.querySelectorAll('.keep-markdown-body img') + const zoomInImgMask = document.querySelector('.zoom-in-image-mask') + const zoomInImg = zoomInImgMask?.querySelector('.zoom-in-image') + + const zoomOut = () => { + if (isZoomIn) { + isZoomIn = false + curWinScrollY = 0 + zoomInImg && (zoomInImg.style.transform = `scale(1)`) + zoomInImgMask && zoomInImgMask.classList.remove('show') + setTimeout(() => { + selectedImgDom && selectedImgDom.classList.remove('hide') + }, 300) + } + } + + const zoomOutHandle = () => { + zoomInImgMask && + zoomInImgMask.addEventListener('click', () => { + zoomOut() + }) + + document.addEventListener('scroll', () => { + if (isZoomIn && Math.abs(curWinScrollY - window.scrollY) >= 50) { + zoomOut() + } + }) + } + + const setSideGap = () => { + const w = document.body.offsetWidth + if (w <= 500) { + SIDE_GAP = 10 + } else if (w <= 800) { + SIDE_GAP = 20 + } else { + SIDE_GAP = 40 + } + } + + if (imgDomList.length) { + zoomOutHandle() + imgDomList.forEach((img) => { + img.addEventListener('click', () => { + curWinScrollY = window.scrollY + isZoomIn = !isZoomIn + setSideGap() + zoomInImg.setAttribute('src', img.getAttribute('src')) + selectedImgDom = img + if (isZoomIn) { + const imgRect = selectedImgDom.getBoundingClientRect() + const imgW = imgRect.width + const imgH = imgRect.height + const imgL = imgRect.left + const imgT = imgRect.top + const winW = document.body.offsetWidth - SIDE_GAP * 2 + const winH = document.body.offsetHeight - SIDE_GAP * 2 + const scaleX = winW / imgW + const scaleY = winH / imgH + const scale = (scaleX < scaleY ? scaleX : scaleY) || 1 + const translateX = winW / 2 - (imgRect.x + imgW / 2) + SIDE_GAP + const translateY = winH / 2 - (imgRect.y + imgH / 2) + SIDE_GAP + + selectedImgDom.classList.add('hide') + zoomInImgMask.classList.add('show') + zoomInImg.style.top = imgT + 'px' + zoomInImg.style.left = imgL + 'px' + zoomInImg.style.width = imgW + 'px' + zoomInImg.style.height = imgH + 'px' + zoomInImg.style.transform = `translateX(${translateX}px) translateY(${translateY}px) scale(${scale}) ` + } + }) + }) + } + }, + + // set how long ago language + setHowLongAgoLanguage(p1, p2) { + return p2.replace(/%s/g, p1) + }, + + // get how long ago + getHowLongAgo(timestamp) { + const lang = KEEP.language_ago + const __Y = Math.floor(timestamp / (60 * 60 * 24 * 30) / 12) + const __M = Math.floor(timestamp / (60 * 60 * 24 * 30)) + const __W = Math.floor(timestamp / (60 * 60 * 24) / 7) + const __d = Math.floor(timestamp / (60 * 60 * 24)) + const __h = Math.floor((timestamp / (60 * 60)) % 24) + const __m = Math.floor((timestamp / 60) % 60) + const __s = Math.floor(timestamp % 60) + + if (__Y > 0) { + return this.setHowLongAgoLanguage(__Y, lang.year) + } else if (__M > 0) { + return this.setHowLongAgoLanguage(__M, lang.month) + } else if (__W > 0) { + return this.setHowLongAgoLanguage(__W, lang.week) + } else if (__d > 0) { + return this.setHowLongAgoLanguage(__d, lang.day) + } else if (__h > 0) { + return this.setHowLongAgoLanguage(__h, lang.hour) + } else if (__m > 0) { + return this.setHowLongAgoLanguage(__m, lang.minute) + } else if (__s > 0) { + return this.setHowLongAgoLanguage(__s, lang.second) + } + }, + + // set how long age in home article block + setHowLongAgoInHome() { + const post = document.querySelectorAll('.article-meta-info .home-article-history') + post && + post.forEach((v) => { + const nowTimestamp = Date.now() + const updatedTimestamp = new Date(v.dataset.updated).getTime() + v.innerHTML = this.getHowLongAgo(Math.floor((nowTimestamp - updatedTimestamp) / 1000)) + }) + }, + + // loading progress bar start + pjaxProgressBarStart() { + this.pjaxProgressBarTimer && clearInterval(this.pjaxProgressBarTimer) + if (this.isHasScrollProgressBar) { + this.scrollProgressBarDom.classList.add('hide') + } + + this.pjaxProgressBarDom.style.width = '0' + this.pjaxProgressIcon.classList.add('show') + + let width = 1 + const maxWidth = 99 + + this.pjaxProgressBarDom.classList.add('show') + this.pjaxProgressBarDom.style.width = width + '%' + + this.pjaxProgressBarTimer = setInterval(() => { + width += 5 + if (width > maxWidth) width = maxWidth + this.pjaxProgressBarDom.style.width = width + '%' + }, 100) + }, + + // loading progress bar end + pjaxProgressBarEnd() { + this.pjaxProgressBarTimer && clearInterval(this.pjaxProgressBarTimer) + this.pjaxProgressBarDom.style.width = '100%' + + const temp_1 = setTimeout(() => { + this.pjaxProgressBarDom.classList.remove('show') + this.pjaxProgressIcon.classList.remove('show') + + if (this.isHasScrollProgressBar) { + this.scrollProgressBarDom.classList.remove('hide') + } + + const temp_2 = setTimeout(() => { + this.pjaxProgressBarDom.style.width = '0' + clearTimeout(temp_1), clearTimeout(temp_2) + }, 200) + }, 200) + }, + + // insert tooltip content dom + insertTooltipContent() { + const isLazyLoadImg = KEEP.theme_config?.lazyload?.enable === true + + const init = () => { + // tooltip + document.querySelectorAll('.tooltip').forEach((element) => { + const { tooltipContent, tooltipOffsetX, tooltipOffsetY } = element.dataset + + let styleCss = '' + + if (tooltipOffsetX) { + styleCss += `left: ${tooltipOffsetX};` + } + + if (tooltipOffsetY) { + styleCss += `top: ${tooltipOffsetY};` + } + + if (styleCss) { + styleCss = `style="${styleCss}"` + } + + if (tooltipContent) { + element.insertAdjacentHTML( + 'afterbegin', + `${tooltipContent}` + ) + } + }) + + // tooltip-img + const imgsSet = {} + + const hideTooltipImg = (dom, nameIdx, trigger = 'click') => { + if (trigger === 'hover') { + trigger = 'mouseout' + } + + document.addEventListener(trigger, () => { + if (imgsSet[nameIdx].isShowImg) { + dom.classList.remove('show-img') + imgsSet[nameIdx].isShowImg = false + } + }) + } + + const loadImg = (img, imgLoaded) => { + const temp = new Image() + const { src } = img.dataset + temp.src = src + temp.onload = () => { + img.src = src + img.removeAttribute('lazyload') + imgLoaded = true + } + } + + // tooltip-img + document.querySelectorAll('.tooltip-img').forEach((dom, idx) => { + const { + tooltipImgName, + tooltipImgUrl, + tooltipImgTip, + tooltipImgTrigger = 'click', + tooltipImgStyle + } = dom.dataset + + let styleCss = '' + + if (tooltipImgStyle) { + styleCss = `style="${tooltipImgStyle}"` + } + + let tipDom = '' + if (tooltipImgTip) { + tipDom = `
    ${tooltipImgTip}
    ` + } + + if (tooltipImgUrl) { + const imgDomClass = `tooltip-img-${idx}-${tooltipImgName ? tooltipImgName : Date.now()}` + const nameIdx = `${tooltipImgName}-${idx}` + + const imgDom = `${imgDomClass}` + + const imgTooltipBox = `
    ${imgDom}${tipDom}
    ` + + imgsSet[nameIdx] = { + imgLoaded: false, + isShowImg: false + } + + dom.insertAdjacentHTML('afterbegin', imgTooltipBox) + + let eventTrigger = 'click' + + if (tooltipImgTrigger === 'hover') { + eventTrigger = 'mouseover' + } + + dom.addEventListener(eventTrigger, (e) => { + if (isLazyLoadImg && !imgsSet[nameIdx].imgLoaded) { + loadImg( + document.querySelector(`.tooltip-img-box img.${imgDomClass}`), + imgsSet[nameIdx].imgLoaded + ) + } + imgsSet[nameIdx].isShowImg = !imgsSet[nameIdx].isShowImg + dom.classList.toggle('show-img') + e.stopPropagation() + }) + + hideTooltipImg(dom, nameIdx, tooltipImgTrigger) + } + }) + } + setTimeout(() => { + init() + }, 1000) + }, + + // busuanzi initialize handle + siteCountInitialize() { + if (KEEP.theme_config?.website_count?.busuanzi_count?.enable === true) { + const tmpId = 'busuanzi-js' + let script = document.body.querySelector(`#${tmpId}`) + + if (!script) { + script = document.createElement('script') + script.setAttribute('data-pjax', '') + script.setAttribute('id', tmpId) + script.async = true + script.src = '//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js' + document.body.appendChild(script) + } + + const getText = (selector) => { + return document.querySelector(selector)?.innerText + } + + script.onload = () => { + setTimeout(() => { + if ( + getText('#busuanzi_value_site_uv') || + getText('#busuanzi_value_site_pv') || + getText('#busuanzi_value_page_pv') + ) { + const tmpDom1 = document.querySelector('.footer .count-item .uv') + const tmpDom2 = document.querySelector('.footer .count-item .pv') + const tmpDom3 = document.querySelector('.article-meta-info .article-pv') + tmpDom1 && (tmpDom1.style.display = 'flex') + tmpDom2 && (tmpDom2.style.display = 'flex') + tmpDom3 && (tmpDom3.style.display = 'inline-block') + } + }, 1000) + } + } + }, + + // page number jump handle + pageNumberJump() { + const inputDom = document.querySelector('.paginator .page-number-input') + inputDom && + inputDom.addEventListener('change', (e) => { + const min = 1 + const max = Number(e.target.max) + let current = Number(e.target.value) + + if (current <= 0) { + inputDom.value = min + current = min + } + + if (current > max) { + inputDom.value = max + current = max + } + + const tempHref = window.location.href.replace(/\/$/, '').split('/page/')[0] + + if (current === 1) { + window.location.href = tempHref + } else { + window.location.href = tempHref + '/page/' + current + } + }) + }, + + // custom tabs tag active handle + tabsActiveHandle() { + const activeHandle = (navList, paneList, tab) => { + navList.forEach((nav) => { + if (tab.dataset.href === nav.dataset.href) { + nav.classList.add('active') + } else { + nav.classList.remove('active') + } + }) + + paneList.forEach((pane) => { + if (tab.dataset.href === pane.id) { + pane.classList.add('active') + } else { + pane.classList.remove('active') + } + }) + } + + const tabsList = document.querySelectorAll('.keep-tabs') + tabsList.length && + tabsList.forEach((tabs) => { + const tabNavList = tabs.querySelectorAll('.tabs-nav .tab') + const tabPaneList = tabs.querySelectorAll('.tabs-content .tab-pane') + tabNavList.forEach((tabNav) => { + tabNav.addEventListener('click', () => { + activeHandle(tabNavList, tabPaneList, tabNav) + }) + }) + }) + }, + + // first screen typewriter + initTypewriter() { + const fsc = KEEP.theme_config?.style?.first_screen || {} + const isHitokoto = fsc?.hitokoto === true + + if (fsc?.enable !== true) { + return + } + + if (fsc?.enable === true && !isHitokoto && !fsc?.description) { + return + } + + const descBox = document.querySelector('.first-screen-content .description') + if (descBox) { + descBox.style.opacity = '0' + + setTimeout( + () => { + descBox.style.opacity = '1' + const descItemList = descBox.querySelectorAll('.desc-item') + descItemList.forEach((descItem) => { + const desc = descItem.querySelector('.desc') + const cursor = descItem.querySelector('.cursor') + const text = desc.innerHTML + desc.innerHTML = '' + let charIndex = 0 + + if (text) { + const typewriter = () => { + if (charIndex < text.length) { + desc.textContent += text.charAt(charIndex) + charIndex++ + setTimeout(typewriter, 100) + } else { + cursor.style.display = 'none' + } + } + + typewriter() + } + }) + }, + isHitokoto ? 400 : 300 + ) + } + }, + + // remove white space between children + removeWhitespace(container) { + if (!container) { + return + } + + const childNodes = container.childNodes + const whitespaceNodes = [] + + for (let i = 0; i < childNodes.length; i++) { + const node = childNodes[i] + + if (node.nodeType === 3 && /^\s*$/.test(node.nodeValue)) { + whitespaceNodes.push(node) + } + } + + for (const whitespaceNode of whitespaceNodes) { + container.removeChild(whitespaceNode) + } + }, + trimPostMetaInfoBar() { + this.removeWhitespace( + document.querySelector('.article-meta-info-container .article-category-ul') + ) + this.removeWhitespace(document.querySelector('.article-meta-info-container .article-tag-ul')) + } + } + + KEEP.utils.initData() + KEEP.utils.registerWindowScroll() + KEEP.utils.toggleShowToolsList() + KEEP.utils.globalFontAdjust() + KEEP.utils.initHasToc() + KEEP.utils.zoomInImage() + KEEP.utils.setHowLongAgoInHome() + KEEP.utils.insertTooltipContent() + KEEP.utils.siteCountInitialize() + KEEP.utils.pageNumberJump() + KEEP.utils.tabsActiveHandle() + KEEP.utils.initTypewriter() + KEEP.utils.trimPostMetaInfoBar() +} diff --git a/links/index.html b/links/index.html new file mode 100644 index 0000000..082e59e --- /dev/null +++ b/links/index.html @@ -0,0 +1,475 @@ + + + + + + + + + + + + 友链 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + + + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 0000000..3a1493c --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,735 @@ + + + + + + + + + + + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
      + +
    • + + + +
      + +

      + + Absolute Pace 绝对和平的生存 + +

      + +
      + + 前言在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣! +想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。 +模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性 +和平模... + +
      + + + + + +
      +
    • + +
    • + + + +
      + +

      + + 文明六自定义领袖特色建筑 + +

      + +
      + + 只允许主机、特定领袖、特定文明使用特定功能的模组 +背景一直想实现一个在联机模式下,只允许主机,特定领袖,特定文明使用特定功能的模组. +模组介绍这是一个建筑添加与德国特色工业区下,每回合建筑生产一定的资源并修改城市每回合所产出的资源. +建筑本身加成:黄... + +
      + + + + + +
      +
    • + +
    • + + + +
      + +

      + + 文明六自定义市中心区域建筑模组 + +

      + +
      + + 前言在文明 6 的创意工坊中订阅了两个功能极为相似的模组,感觉这两个模组的代码应该差不多,于是打算分析完成这两个模组就自己写一个. +文明六Sid Meier's Civilization VI 是一款回合制策略游戏,让玩家尝试建立起一个帝国,并接受时... + +
      + + + + + +
      +
    • + +
    • + + + +
      + +

      + + 我的世界自定义配方脚本生成器 + +

      + +
      + + 前言在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣! +想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。 +模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性 +和平模... + +
      + + + + + +
      +
    • + +
    + +
    +
    + +
    + + +
    + /2 +
    + + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/search.xml b/search.xml new file mode 100644 index 0000000..220073d --- /dev/null +++ b/search.xml @@ -0,0 +1,257 @@ + + + + + + + 北境之地资源加成模组 + + /2024/05/14/2024-05-14-bei-jing-zhi-di-zi-yuan-jia-cheng-mo-zu/ + + 记录一次Northgard(北境之地)模组编写的过程

    游戏介绍

    AFTER YEARS OF TIRELESS EXPLORATIONS…

    Brave Vikings have discovered a new land filled with mystery, danger and riches: Northgard.

    The boldest Northmen have set sail to explore and conquer these new shores, bring fame to their Clan and write history through conquest, trading, or devotion to the Gods.

    经过多年不懈的探索……

    勇敢的维京人发现了一片充满神秘、危险和财富的新大陆:北加德。

    最大胆的北方人已经起航去探索和征服这些新的海岸,通过征服、贸易或对神的忠诚来为他们的氏族带来名声,并写下历史。

    准备工作

    确保你已经阅读完了Northgard官方帮助文档,并且你已经知道了如何创建一个mod。确保我们的步骤一致你需要检查一下步骤:

    1. 在Northgard的根目录下创建一个Northgard\mods目录
    2. 打开Northgard——创意——创建地图
    3. 地图大小:巨大
    4. 玩家人数:8
    5. 难度:普通
    6. 游戏类型:随机
    7. 点击创建地图
    8. 然后保存地图并设置一个名称,比如cheatRes
    9. 创意——启动北加尔编辑器——加载刚才创建的模组目录
    10. 从游戏目录中复制Northgard\NGEditor\res\script.hxcheatRes目录下
    11. 打开北加尔编辑器——Database——Diff——Create
    12. 创建一个模组预览图片比如x512、x1024的正方形大小,命名为preview.jpg

    你的模组目录Northgard\mods\cheatRes下应该有以下四个文件:

    cdb.diff修改游戏规则的数据库文件
    map.dat在游戏中创建的自定义地图
    preview.jpg上传创意工坊的模组预览图
    script.hx修改游戏逻辑的

    目标

    每隔一段时间自动增加资源,比如:木材、食物、石材、铁锭等

    script.hx

    首先阅读一遍示例代码,可以发现主要用的只有三个方法:saveState()init()regularUpdate(dt : Float),其他不需要的方法都可以删掉了

    • saveState():用于保存游戏的,保持默认就好
    • init():模组执行逻辑的主入口且只执行一次
    • regularUpdate(dt : Float):每0.5秒就会执行一次

    从这三个方法中可以发现主要代码写在regularUpdate即可,通读官方提供的API文档可以发现有一个Player表示玩家点进去后发现有一个类似添加资源的函数addResource(kind:ResourceKind, amount:Float, produced:Bool = true):Void,这里第一个参数应该是指要添加的资源类型,但遗憾的是文档中并没有提及有哪些资源类型,第二个是指资源的数量,第三个可选。

    那就只能参考别人的模组看看怎么写,打开创意工坊订阅最热门的模组。下载完成后打开SteamLibrary\steamapps\workshop\content目录下修改日期排序中最新的目录,只需要关注script.hx文件,在代码里搜索关键字addResource发现有一行类似的me().addResource(Resource.Fame, 100);,看到这里让我突然想起官方文档中对于玩家的修改使用的就是me()这样就不用判断是否是AI还是玩家了。

    尝试编写模组:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 保存游戏(保持默认)
    savedInt = 0;

    function saveState() {
    state.scriptProps = {
    savedInt : savedInt,
    };
    }

    // 开局执行一次
    function init() {
    if (state.time == 0){
    // 声望
    me().addResource(Resource.Fame, 100);
    }
    }

    打开游戏选择单人游戏,添加模组,选择本地模组,进入游戏。

    可以看到开局增加了100声望,同时增加了货币、食物、木材、知识。从这一点可以发现Resource.Fame是指声望。

    继续查找其他资源类型,又看到一个资源类型Resource.Money,进入游戏发现增加的是货币。然后再次返回别人的代码中查找,遗憾的是没有发现其他资源类型。

    到这里可以大胆的推测一下游戏中的其他资源类型,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    // 石头
    Resource.Stone
    // 铁矿
    Resource.Iron
    // 木头
    Resource.Wood
    // 食物
    Resource.Food

    然后代入参数类型再次尝试运行模组,令人高兴的是推测是正确的。

    然后添加所有可能的资源类型,然后保存。

    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
    // 保存游戏(保持默认)
    savedInt = 0;

    function saveState() {
    state.scriptProps = {
    savedInt : savedInt,
    };
    }

    // 开局执行一次
    function init() {
    if (state.time == 0){
    // 声望
    me().addResource(Resource.Fame, 100);
    }
    }

    // 0.5秒执行一次
    function regularUpdate(dt : Float) {
    // 金钱
    me().addResource(Resource.Money,1);
    // 石头
    me().addResource(Resource.Stone,1);
    // 铁矿
    me().addResource(Resource.Iron,1);
    // 木头
    me().addResource(Resource.Wood,1);
    // 食物
    me().addResource(Resource.Food,1);
    }

    到这里一个模组就编写完成了!

    上传模组

    确保你的模组目录中拥有一个名为preview的图片文件,这将用来在模组浏览页面展示你的模组。

    然后打开游戏——创意——上传模组,上传成功后打开创意工坊中你发布的文件,完善模组信息比如标题和描述以及需要展示的图片或者视频。

    注意:添加的模组标题和描述必须是符合当前语言的,你可以添加:日语、简体中文、英语,其他国家的人会看到不同的语言

    订阅我

    萌新资源大礼包(cheatRes)

    ]]>
    + + + + + Mod + + + + + + + Northgard + + + +
    + + + + + Hexo环境配置 + + /2024/02/25/2024-02-25-hexo-huan-jing-pei-zhi/ + + 电脑有时会出现一些奇怪的问题要重装系统,导致很多环境要重新配置。Hexo是环境配置中最复杂的每次都要在百度查很久,为此今天重新配置环境就顺便写一篇文章来记录。

    0x1 暂停QSync双向同步

    重新初始化Hexo会覆盖整个目录,所以得先提前把博客备份出来。然后删除本地的Hexo目录里的文件夹和NAS端的,然后再打开QSync等待同步完成。

    注意:只保留一个空的Hexo目录

    1
    2
    > dir Hexo
    0 个文件 0 字节

    0x2 安装Node.js

    1. 删除之前安装的Node.js安装目录,否则会安装失败

    2. 安装推荐的LTS版本,LTS是长期支持的稳定版本

    3. 检查安装是否成功,显示版本号就算没问题了

      1
      2
      > node -v
      v20.11.1

    0x3 初始化blog

    1. 打开Clash的TUN模式,在打开系统{代}{理},TUN表示{代}{理}所有应用程序。

    2. 在Hexo目录下打开终端,依次执行

      1
      2
      3
      4
      5
      > npm install hexo-cli -g
      > hexo init blog
      > cd blog
      > npm install
      > hexo server
    3. 如果一切顺利会显示一个本地服务器地址http://localhost:4000/,复制在浏览器里打开能够看到一个Hello World默认文章,就表示已经成功了。

    0x4 运行自己的blog

    1. 把刚才备份博客目录解压缩在Hexo目录下
    2. 在自己blog目录下打开终端执行hexo s
    3. 如果能够显示http://localhost:4000,并且能够正常在浏览器中打开就算本地配置完成了

    0x5 Gitee SSH配置

    1. 设置Gitee账户和用户名

      1
      2
      > git config --global user.email "you@example.com"
      > git config --global user.name "Your Name"
    2. 生成SSH密钥

      1
      > ssh-keygen -t rsa -C "Your Name"
    3. SSH密钥保存位置使用默认路径,不使用密码。只需要连按三次回车即可

      1
      2
      3
      4
      > ssh-keygen -t rsa -C  "Your Name"
      Enter file in which to save the key (C:\Users\user/.ssh/id_rsa):
      Enter passphrase (empty for no passphrase):
      Enter same passphrase again:
    4. 查看密钥,密钥保存位置在上一步命令执行结果中有

      1
      > notepad C:\Users\user/.ssh/id_rsa.pub
    5. 打开你的Gitee设置页面,在安全设置下选择SSH公钥,把刚才打开的密钥文件内容全部复制进来,然后加上标题,点击确定就完成配置了。需要删除之前的公钥,注意不是打开blog的仓库设置,是你的账户设置

    6. 检查配置是否成功

      1
      2
      3
      > ssh -T git@gitee.com
      # 在"?"后面输入yes,如果配置正确会显示你的Gitee账户名称
      Are you sure you want to continue connecting (yes/no/[fingerprint])?yes

    0x6 发布Hexo到Gitee

    1. 在自己的blog目录下执行,删除之前生成的文件

      1
      2
      3
      4
      > hexo clean
      INFO Validating config
      INFO Deleted database.
      INFO Deleted public folder.
    2. 重新生成文件

    1
    2
    3
    4
    5
    > hexo g
    INFO Validating config
    INFO Start processing
    ...
    INFO 108 files generated in 877 ms
    1. 运行本地服务器,检查网站没问题就按Ctrl+C停止运行本地服务器
    1
    2
    3
    4
    > hexo s
    INFO Validating config
    INFO Start processing
    INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.
    1. 同步到Gitee仓库

      1
      > hexo d

    0x7 Gitee部署Hexo

    1. 打开Gitee同步blog的仓库
    2. 点击右上角的管理,确保仓库处于开源状态,然后点击保存。有时候会因为没有签协议而被改为私有
    3. 返回到仓库页面,点击服务中的Gitee Pages,点击更新
    4. 等几分钟再查看blog

    至此,一切都已经配置完成。

    ]]>
    + + + + + 环境配置 + + + + + + + Hexo + + + +
    + + + + + 基于Node.js跨平台局域网消息传输与QNAP部署 + + /2023/08/28/2023-08-28-node-js-kua-ping-tai-ju-yu-wang-xiao-xi-chuan-shu-yu-qnap-bu-shu-shou-ce/ + + 0x1 引言

    0x1.1 目的与背景

    当我刚进入大学时,我得到了我的第一台游戏本。当时,我经常需要在手机和电脑之间传输文件和文档。最初,我使用QQ和手机热点来进行传输,这显得相当繁琐。后来,我尝试了蓝牙传输,但速度过慢。尽管我使用了小米的热点文件传输功能,但仅为了传输简单的文本或图片,这样的操作还是过于复杂。

    毕业后,我的生活出现了很大的改变。拥有了路由器后,内网传输变得相对简单。但当我获得了iPhone和MacBook,我发现跨操作系统传输文件并不简单。例如,从iPhone向Windows传输文本或链接时,我还需依赖QQ等软件。尝试使用Windows的SMB共享服务和MacBook的文件共享都有其局限性和问题。

    后来,我转向开源的云盘解决方案,但传输速度的限制使我非常不满意。直到我获得了QNAP,大部分的文件传输问题得到了解决。然而,在局域网下保持Windows和MacBook数据的一致性,尤其是文本数据,依然是一个问题。显然,依赖QQ这样的方式效率低下。

    因此,创建一个简单的网页成为了一种必要,让任何设备仅通过输入一个地址就能发送消息,避免了下载和设置各种软件的麻烦。

    0x1.2 目标受众

    1. 大学生和职场新人:他们经常在不同的设备之间传输文件和文档,尤其是在手机和电脑之间,需要一个高效的解决方案。

    2. 跨操作系统用户:特别是那些使用iPhone和Windows或MacBook的人,他们经常面临操作系统之间的传输难题。

    3. 网络技术爱好者:对于那些寻求局域网下文件和数据传输解决方案的人,他们可能已经尝试了各种方法,但仍然在寻找更加高效的方法。

    4. 对效率有追求的人:不想依赖于多个应用程序和软件,而是寻求一个集中的、简化的解决方案来处理日常的文件和数据传输任务。

    5. 云存储和网络存储用户:特别是那些使用开源云盘或QNAP这样的解决方案的人,他们在寻找更快、更直接的传输方法。

    0x2 必要的理论知识解释

    0x2.1 基础概念

    1. 文件传输

      • 定义:文件传输指的是从一个设备、系统或者位置将文件或数据移动到另一个设备、系统或位置的过程。
      • 应用:例如,通过USB、网络或云服务将文档从电脑传输到手机。
    2. 操作系统差异

      • 定义:不同的操作系统(如Windows、MacOS、Linux等)在架构、功能和界面上有所不同。
      • 应用:某些软件可能只在特定操作系统上运行,或在不同系统上有不同的表现。
    3. 局域网(LAN)原理

      • 定义:局域网是一个限定在较小地理范围内(如家庭、办公室)的计算机网络。
      • 应用:局域网使得同一位置的多台设备可以共享资源,如打印机或文件。
    4. 蓝牙和热点传输

      • 定义:蓝牙是一种无线技术标准,用于短距离数据交换,而热点传输则是通过Wi-Fi将文件从一个设备分享到另一个设备。
      • 应用:例如,用蓝牙耳机听音乐或使用手机热点给电脑上网。
    5. 云存储与网络存储

      • 定义:云存储是通过互联网存储数据在远程服务器上的服务;网络存储则是在局域网内提供集中化的数据存储。
      • 应用:如,使用Google Drive或Dropbox来存储文件或使用NAS来在家庭网络中共享文件。
    6. 跨平台兼容性

      • 定义:指的是软件或应用在多个操作系统或设备上的运行能力。
      • 应用:例如,一个应用既可以在Android上运行,也可以在iOS上运行。
    7. Web技术基础

      • 定义:关于如何构建和呈现互联网内容的技术和原理。
      • 应用:例如,使用HTML、CSS和JavaScript来构建网页。
    8. Node.js

      • 定义:是一个允许在服务器上运行JavaScript的运行时环境。
      • 应用:例如,构建后端API或Web应用。
    9. HTML

      • 定义:是用来描述网页结构的标记语言。
      • 应用:例如,定义网页中的标题、段落和链接。
    10. CSS

    • 定义:是用于描述网页外观和格式的语言。
    • 应用:例如,设置字体、颜色和布局。
    1. Socket
    • 定义:是计算机之间进行通信的端点。
    • 应用:例如,实时聊天应用。
    1. Server
    • 定义:是为其他计算机或应用提供资源、服务或数据的系统或应用。
    • 应用:例如,Web服务器存储和提供网页内容。
    1. Docker
    • 定义:是一个平台,用于创建、运行和管理容器化的应用。
    • 应用:例如,为应用提供一致的运行环境。
    1. IP地址
    • 定义:是分配给联网设备的数字地址,用于识别和定位。
    • 应用:例如,访问特定的网站或远程服务器。
    1. User Agent
    • 定义:是描述浏览器或其他客户端如何与服务器通信的字符串。
    • 应用:例如,网站可以识别用户的设备和浏览器,并据此提供优化的内容。

    0x2.2 相关技术背景

    随着技术的日益发展,文件传输和数据共享已经成为现代工作和生活中不可或缺的部分。无论是学生、企业家还是日常用户,我们都经常面临跨设备、跨操作系统的数据传输挑战。

    1. 文件传输:这是一种基本的需求,涉及将文件从一个设备传送到另一个设备。尽管有许多可用的方法,例如USB、蓝牙和热点传输,但它们都有自己的局限性和速度问题。

    2. 操作系统差异:由于Windows、MacOS和其他操作系统的内在差异,许多应用和服务在跨操作系统通信时面临挑战。

    3. 局域网原理:局域网为设备提供了一种在有限的地理范围内共享资源的方式。例如,家庭或办公室内的设备可以通过局域网共享打印机或文件。

    4. 蓝牙与热点传输:尽管这两种技术都提供了一种无线传输方式,但它们通常受限于传输距离和速度。

    5. 云存储与网络存储:为了解决大文件传输的问题,许多用户转向了云存储或网络存储解决方案,如Google Drive或NAS。

    6. 跨平台兼容性:这是当今技术世界中的一大挑战。由于多样性的增加,需要开发可以在多个平台上运行的应用。

    7. Web技术基础:HTML、CSS和JavaScript等技术为创建跨平台解决方案提供了基础,允许用户通过浏览器访问应用和服务。

    8. 服务器和后端技术:Node.js提供了一个强大的平台,用于构建后端服务和API,而Socket技术使实时通信成为可能。这些技术经常被用于创建动态、实时的Web应用。

    9. 容器化技术:Docker为开发者提供了一种方式,将其应用及其所有依赖性封装在一个容器中,确保其在任何环境中的一致性和可移植性。

    10. 网络识别:IP地址和User Agent允许设备和网络服务识别请求的来源,从而为用户提供个性化的体验和内容。

    0x3 Node.js原型开发

    0x3.1 环境配置

    初始化项目

    1
    2
    mkdir realtime-text
    cd realtime-text

    安装Node.js,最好选择LTS版本,在项目根目录执行

    1
    2
    3
    npm init -y
    npm install express socket.io
    npm install multer

    0x3.2 基本功能实现

    创建服务器 (server.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
    const express = require('express');
    const http = require('http');
    const socketIo = require('socket.io');

    const app = express();
    const server = http.createServer(app);
    const io = socketIo(server);

    app.use('/socket.io', express.static('node_modules/socket.io/client-dist/'));

    app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
    });

    io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('send text', (text) => {
    io.emit('receive text', text);
    });

    socket.on('disconnect', () => {
    console.log('user disconnected');
    });
    });

    server.listen(3000, () => {
    console.log('listening on *:3000');
    });

    创建前端页面 (index.html)

    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
    <!doctype html>
    <html>
    <head>
    <title>Realtime Text</title>
    </head>
    <body>
    <textarea id="textArea" rows="10" cols="50"></textarea>
    <button id="sendButton">Send</button>
    <hr>
    <h3>Received Text:</h3>
    <div id="receivedText"></div>

    <script src="/socket.io/socket.io.js"></script>
    <script>
    var socket = io();

    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    socket.emit('send text', text);
    };

    socket.on('receive text', function(text) {
    document.getElementById('receivedText').innerText = text;
    });
    </script>
    </body>
    </html>

    启动服务器

    本地服务器默认地址:http://localhost:3000/

    1
    node server.js

    创建启动服务器脚本 (run.bat)

    1
    2
    node server.js
    pause

    0x4 按需复制,无需深究学习

    不想学习思路直接运行代码,按照以下步骤操作,如果需要上传NAS请继续阅读 0xA Docker封装与测试

    创建对应的目录结构,并把index.htmlserver.js这两个的代码复制到你的网站根目录下

    下载JetBrains Mono字体,解压JetBrainsMono-Regular.ttfJetBrainsMono-Regular.woff2到网站根目录

    现在你的目录结构应该是这样的

    1
    2
    3
    4
    5
    6
    7
    8
     D:\realtime_text 的目录
    2023/08/27 04:51 11,332 index.html
    2023/01/14 23:20 273,900 JetBrainsMono-Regular.ttf
    2023/01/14 23:20 92,164 JetBrainsMono-Regular.woff2
    2023/08/27 01:47 <DIR> node_modules
    2023/08/27 01:12 52 run.bat
    2023/08/27 01:49 1,942 server.js
    2023/08/27 13:27 <DIR> uploads

    0x4.1 index.html

    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
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    <!doctype html>
    <html>
    <head>
    <title>Realtime Text</title>
    <style>
    [data-theme="dark"] input, [data-theme="dark"] textarea, [data-theme="dark"] button {
    color: white; /* 将文字颜色设置为白色 */
    border-color: white; /* 如果需要, 也可以设置边框颜色 */
    }

    :root {
    /* 浅色模式的设置 */
    --text-color: black;
    --background-color: white;
    --input-bg: #fff;
    --container-bg: #fff;
    --header-bg: #fff;
    --button-color: black; /* 新增 */
    }

    [data-theme="dark"] {
    /* 深色模式的设置 */
    --text-color: black; /* 文本颜色为黑色 */
    --background-color: white; /* 背景颜色为白色 */
    }

    .bottom-controls {
    display: flex;
    justify-content: center;
    }
    .bottom-controls > div {
    display: flex; /* 这确保了内部容器的子元素水平对齐 */
    align-items: center;
    }
    /* 为固定底部工具栏添加填充 */
    div[style*="position: fixed; bottom: 0;"] {
    padding-left: 10px;
    padding-right: 10px;
    }
    /* 保证body的背景颜色被应用 */
    body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: var(--background-color) !important;
    color: var(--text-color);
    font-family: "JetBrainsMono", Arial, sans-serif;
    }
    .container {
    border: 1px solid #ddd;
    background-color: #fff;
    }
    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    .bottom-controls {
    justify-content: flex-start; /* 这将使内部容器与起始位置对齐 */
    }
    .bottom-controls > div {
    flex-direction: column; /* 这确保了内部容器的子容器是垂直堆叠的 */
    align-items: center;
    width: 100%;
    }

    /* 更新底部控件的容器样式 */
    div[style*="position: fixed; bottom: 0;"] {
    background-color: var(--container-bg);

    padding-left: 10px;
    padding-right: 10px;
    display: flex;
    flex-direction: column; /* 确保控件垂直堆叠 */
    }

    /* 确保所有控件拉伸以填充可用宽度 */
    #deviceName, #textArea, #imageUpload, #sendButton {
    background-color: var(--container-bg);

    width: 100%;
    box-sizing: border-box; /* 确保填充和边框包含在宽度中 */
    margin-bottom: 5px; /* 添加控件之间的间距 */
    }

    /* 增加高度,便于触摸 */
    #deviceName, #textArea, #sendButton {
    height: 25px;
    }
    /* 在邮件列表的底部添加填充 */
    #messages {
    padding-bottom: 70px; /* 根据底部工具栏的高度调整此值 */
    }
    #deleteButton{
    width: 100%;
    height: 25px;
    }
    .container{
    max-height: 480px;
    }
    #themeToggle{
    width: 100%;
    height: 25px;
    margin-bottom: 5px;
    }
    }
    /* 大屏幕的样式(例如,Windows, MacBook) */
    @media (min-width: 501px) {
    #deviceName, #textArea, #imageUpload, #sendButton {
    background-color: var(--input-bg);

    height: 60px;
    font-size: 18px;
    padding: 10px;
    margin-bottom: 10px;
    }
    #textArea {
    width: 50%;
    }
    #sendButton {
    padding: 10px 20px; /* 调整按钮的填充 */
    margin-left: 10px; /* 在发送按钮的左侧添加一些边距,以保持间距 */
    margin-right: 5px;
    width: 100px;
    }
    #deleteButton{
    margin-left: 5px;
    width: 150px;
    height: 50px;
    }
    #themeToggle{
    margin-right: 10px;
    width: 100px;
    height: 50px;
    }
    }
    body, button {
    background-color: var(--bg-color);
    color: var(--text-color);
    }
    /* 为了确保按钮的文本颜色能够在两种主题下都清晰可见 */
    button {
    color: var(--button-color);
    }
    /* 底部控件的文本颜色 */
    .bottom-controls input,
    .bottom-controls textarea,
    .bottom-controls button {
    color: var(--text-color);
    border-color: var(--text-color);
    }
    .container {
    background-color: var(--container-bg);
    overflow-y: auto;
    min-width: 350px;
    position: sticky;
    margin-bottom: 100px;
    }
    body.dark-theme {
    /* 深色模式的设置 */
    --text-color: white;
    --background-color: #555;
    --input-bg: #555;
    --container-bg: #444;
    --header-bg: #444;
    --button-color: white; /* 新增 */
    }
    #header {
    background-color: var(--header-bg);
    }
    @font-face {
    font-family: "JetBrainsMono"; /* 可以为字体起一个名字 */
    src: url('JetBrainsMono-Regular.woff2') format('woff2'), /* 最优先 */
    url('JetBrainsMono-Regular.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    }

    </style>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    </head>
    <body>

    <div class="container">
    <div id="header" style="background-color: var(--container-bg);">
    <ul id="messages" style="max-height: 70vh"></ul>
    </div>
    </div>

    <!-- 调整底部工具栏结构 -->
    <div class="bottom-controls" style="position: fixed; bottom: 0; width: 100%; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1); background-color: var(--container-bg);">
    <div style="display: flex; align-items: center;">
    <button id="themeToggle">切换主题</button>
    <input type="text" id="deviceName" placeholder="Device Name">
    <input type="file" id="imageUpload" accept="image/*">
    <textarea id="textArea" placeholder="写点什么?!"></textarea>
    <button id="sendButton">发送</button>
    <button id="deleteButton">删除选中消息</button>
    </div>
    </div>

    <script src="/socket.io/socket.io.js"></script>
    <script>
    window.addEventListener('load', function() {
    setTimeout(function() {
    window.scrollTo(0, 1);
    }, 0);

    // 请求历史消息
    fetch('/messages')
    .then(response => response.json())
    .then(data => {
    const messagesDiv = document.getElementById('messages');
    data.messages.forEach(message => {
    const p = document.createElement('p');
    p.textContent = message;
    messagesDiv.appendChild(p);
    });
    })
    .catch(error => {
    console.error('获取消息错误:', error);
    });
    });


    document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;


    var socket = io();

    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    var deviceName = document.getElementById('deviceName').value || "匿名设备";
    var imageFile = document.getElementById('imageUpload').files[0];
    var listCheckBox = document.getElementById('listCheckBox');

    if (imageFile) {
    var formData = new FormData();
    formData.append('image', imageFile);
    formData.append('deviceName', deviceName);
    formData.append('text', text);
    formData.append('listCheckBox', listCheckBox);

    fetch('/upload-image', {
    method: 'POST',
    body: formData
    }).then(response => response.json()).then(data => {
    socket.emit('send text', data);
    });
    } else {
    socket.emit('send text', {text, deviceName });
    }
    // 在所有情况下都清除textArea和图片选择器的值
    document.getElementById('textArea').value = '';
    document.getElementById('imageUpload').value = ''; // 这将清除文件输入选择器的内容
    };

    var messageId = 0; // 初始化一个全局变量
    socket.on('receive text', function(data) {
    var li = document.createElement("li");

    // 创建复选框
    var checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.className = "messageCheckbox";
    li.appendChild(checkbox); // 将复选框添加到列表项中

    var time = data.timestamp || new Date().toLocaleString(); // 获取时间戳,如果没有则使用当前时间

    var content = `[${data.deviceName} @ ${time}]: `; // 显示设备名和时间
    if (data.text.startsWith("```") && data.text.endsWith("```")) {
    // 如果文本以 "```" 开头和结尾,将其识别为代码
    content += `<pre>${data.text.slice(3, -3)}</pre>`;
    } else {
    content += `${data.text}`;
    }

    if (data.imageUrl) {
    content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
    }

    li.innerHTML += content;
    document.getElementById('messages').appendChild(li);

    li.setAttribute('data-message-id', messageId); // 为每个消息设置唯一ID
    messageId++;

    console.log("设备名称和IP地址:", data.deviceName);
    });

    // 发送HTTP DELETE请求到后端来删除消息
    document.getElementById('deleteButton').onclick = function() {
    var selectedMessages = document.querySelectorAll('.messageCheckbox:checked');
    var messageIdsToDelete = [];
    selectedMessages.forEach(function(checkbox) {
    var li = checkbox.closest('li');
    messageIdsToDelete.push(li.getAttribute('data-message-id'));
    li.remove();
    });
    socket.emit('delete messages', messageIdsToDelete);

    // 向后端发送删除请求
    messageIdsToDelete.forEach(id => {
    fetch(`/message/${id}`, {
    method: 'DELETE'
    }).then(response => {
    if (!response.ok) {
    // 以某种方式处理错误响应
    console.error('从服务器删除消息失败');
    }
    });
    });
    };


    socket.on('delete messages', function(messageIds) {
    messageIds.forEach(function(messageId) {
    var li = document.querySelector('li[data-message-id="' + messageId + '"]');
    if (li) li.remove();
    });
    io.emit('delete messages', messageIds); // 向所有客户端广播消息ID,以供删除
    });



    document.addEventListener('click', function(event) {
    // 如果消息中的图像被点击
    if (event.target.tagName === 'IMG' && event.target.closest('#messages')) {
    var modal = document.getElementById('imageModal');
    var modalImage = document.getElementById('modalImage');

    modalImage.src = event.target.src;
    modal.style.display = 'flex';

    modal.addEventListener('click', function() {
    modal.style.display = 'none';
    });
    }
    });

    // 按下'Escape'关闭模式
    document.addEventListener('keydown', function(event) {
    if (event.key === 'Escape') {
    var modal = document.getElementById('imageModal');
    modal.style.display = 'none';
    }
    });

    function getDeviceNameFromUserAgent() {
    var userAgent = navigator.userAgent || navigator.vendor || window.opera;

    // Windows Phone必须先行一步,因为它的用户界面也包含“Android”。
    if (/windows phone/i.test(userAgent)) {
    return "Windows Phone";
    }

    if (/android/i.test(userAgent)) {
    return "Android Device";
    }

    // iOS检测
    if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
    return "iOS Device";
    }

    if (/macintosh|mac os x/i.test(userAgent)) {
    return "Mac";
    }

    if (/windows/i.test(userAgent)) {
    return "Windows PC";
    }

    return "匿名设备";
    }

    // 使用函数来自动填充设备名称
    document.getElementById('deviceName').value = getDeviceNameFromUserAgent();

    // 切换主题
    document.getElementById('themeToggle').addEventListener('click', function() {
    document.body.classList.toggle('dark-theme');
    });


    socket.on('load history', function(history) {
    history.forEach(data => {
    var li = document.createElement("li");
    var content = `[${data.deviceName}]: ${data.text}`;
    if (data.imageUrl) {
    content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
    }
    li.innerHTML = content;
    document.getElementById('messages').appendChild(li);
    });
    });

    </script>

    <!-- 图像模块 -->
    <div id="imageModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 9999; align-items: center; justify-content: center;">
    <img id="modalImage" src="" style="max-width: 90%; max-height: 90%;">
    </div>

    </body>
    </html>

    0x4.2 server.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
    const express = require('express');
    const http = require('http');
    const socketIo = require('socket.io');
    const multer = require('multer');
    const path = require('path');
    const sqlite3 = require('sqlite3').verbose();

    const app = express();
    const server = http.createServer(app);
    const io = socketIo(server);

    const messageHistory = [];


    // 创建或打开数据库
    const db = new sqlite3.Database('./messages.db');

    // 创建message表,如果存在则什么都不做
    // 包含id、content、deviceName、 ipAddress、 timestamp字段
    db.run("CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, deviceName TEXT, ipAddress TEXT, timestamp TEXT)", (err) => {
    if (err) {
    console.error("创建/检查表错误:", err);
    return;
    }
    console.log("表检查/创建成功");
    });



    // 在服务器启动时从数据库加载消息历史记录
    db.all("SELECT id, content, deviceName, ipAddress, timestamp FROM messages", [], (err, rows) => {
    if (err) {
    console.error("从数据库获取历史消息出错:", err);
    return;
    }
    // 遍历数据库每条记录
    rows.forEach(row => {
    const message = {
    id: row.id, // 确保这里保留 id
    text: row.content,
    deviceName: row.deviceName,
    ipAddress: row.ipAddress,
    timestamp: row.timestamp
    };
    // 将数据库中的数据保存在 messageHistory
    messageHistory.push(message);
    });
    console.log("从数据库加载历史消息");
    });


    app.use(express.json());

    app.post('/send', (req, res) => {
    const messageContent = req.body.message;

    if (messageContent) {
    // 存储消息到数据库
    db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)", [data.text, data.deviceName, clientIpAddress, data.timestamp],
    (err) => {
    if (err) {
    console.error("插入数据库出错:", err);
    return;
    }
    console.log('收到的信息:', data.text || messageContent);
    });
    } else {
    res.status(400).send({error: "消息内容为空"});
    }
    });


    app.use('/socket.io', express.static('node_modules/socket.io/client-dist/'));

    app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
    });

    io.on('connection', (socket) => {
    console.log('有一个用户连接');

    function formatMessageForEmission(originalMessage) {
    return {
    ...originalMessage,
    deviceName: `${originalMessage.deviceName} (${originalMessage.ipAddress})`
    };

    }

    // 发送历史消息给新连接的用户
    messageHistory.forEach((message) => {
    socket.emit('receive text', formatMessageForEmission(message));
    // console.log("format的设备和IP地址:",message)
    });




    socket.on('send text', (data) => {
    // 获取客户端的IP地址
    let clientIpAddress = socket.request.connection.remoteAddress.replace(/^::ffff:/, ''); // 清除IPv6前缀

    if (clientIpAddress === '::1' || clientIpAddress === '127.0.0.1') {
    clientIpAddress = 'Localhost';
    }

    // 添加ipAddress字段到data对象
    data.ipAddress = clientIpAddress;

    // 默认设备名称
    if (!data.deviceName || data.deviceName.trim() === "") {
    data.deviceName = "匿名设备";
    }

    // 添加消息发送时间
    data.timestamp = new Date().toLocaleString();

    // 添加消息到数据库中
    db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)",
    [data.text, data.deviceName, clientIpAddress, data.timestamp],
    function(err) { // 使用函数关键字以便访问 this.lastID
    if (err) {
    console.error("插入数据库出错:", err);
    return;
    }
    data.id = this.lastID; // 获取新插入的行的 ID
    messageHistory.push(data); // 将新消息添加到历史记录
    console.log('通过套接字收到的消息:', data.text);
    }
    );

    io.emit('receive text', formatMessageForEmission(data));
    });

    socket.on('disconnect', () => {
    console.log('有一个用户断开连接');
    });
    });

    // 注意:仅使用一个侦听器,app.listen 或 server.listen
    server.listen(3000, () => {
    console.log('服务器地址:http://localhost:3000');
    });

    const storage = multer.diskStorage({
    destination: (req, file, cb) => {
    cb(null, 'uploads/')
    },
    filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname))
    }
    });

    const upload = multer({ storage: storage });

    app.post('/upload-image', upload.single('image'), (req, res) => {
    let imageUrl = `/uploads/${req.file.filename}`;
    res.json({ text: req.body.text, deviceName: req.body.deviceName, imageUrl: imageUrl });
    });

    app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

    // 静态文件处理
    app.use('/css', express.static(path.join(__dirname, 'css')));
    app.use('/js', express.static(path.join(__dirname, 'js')));
    app.use('/', express.static(path.join(__dirname, 'JetBrainsMono-Regular.ttf')));
    app.use('/', express.static(path.join(__dirname, 'JetBrainsMono-Regular.woff2')));

    app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'index.html'));
    });

    app.delete('/message/:id', (req, res) => {
    const messageId = req.params.id;
    if (messageId) {
    db.run("DELETE FROM messages WHERE id=?", [messageId], (err) => {
    if (err) {
    console.error("从数据库删除消息出错:", err);
    res.status(500).send({error: "删除消息失败"});
    return;
    }

    // 从 messageHistory 数组中删除消息
    const index = messageHistory.findIndex(message => message.id === Number(messageId));
    if (index !== -1) {
    messageHistory.splice(index, 1);
    }

    res.send({status: "消息已删除"});
    });
    } else {
    res.status(400).send({error: "无效的消息ID"});
    }
    });

    启动服务器

    1
    node server.js

    0x4.3 直接下载已打包好的

    避免重复上传已打包文件参考:局域网文件和消息传输

    请确保您具有吾爱破解的账号,且用户组为锋芒初露,本帖的权限设置旨在防止第三方网站未经告知私自获取

    要继续学习请往下看

    0x5 界面图像优化与适配

    0x5.1 消息展示与设备自适应

    0x5.1.1 设备信息消息中心

    • 美化布局:使用简单的内联CSS让布局居中
    • 显示多条消息:而不是只覆盖显示一条
    • 显示设备名称/IP:为简化起见,将让用户输入一个设备名称。关于IP地址,我们可以从请求头中获取

    更新index.html如下:

    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
    <!doctype html>
    <html>
    <head>
    <title>Realtime Text</title>
    <style>
    body {
    font-family: Arial, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f4f4f4;
    }
    .container {
    padding: 20px;
    border: 1px solid #ddd;
    background-color: #fff;
    }
    </style>
    </head>
    <body>
    <div class="container">
    <input id="deviceName" placeholder="Enter Device Name" />
    <textarea id="textArea" rows="4" cols="50" placeholder="Type your message..."></textarea>
    <button id="sendButton">Send</button>
    <hr>
    <h3>Received Messages:</h3>
    <ul id="messages"></ul>
    </div>

    <script src="/socket.io/socket.io.js"></script>
    <script>
    var socket = io();

    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    var deviceName = document.getElementById('deviceName').value || "Unknown";
    socket.emit('send text', { text, deviceName });
    };

    socket.on('receive text', function(data) {
    var li = document.createElement("li");
    li.innerText = `[${data.deviceName}]: ${data.text}`;
    document.getElementById('messages').appendChild(li);
    });
    </script>
    </body>
    </html>

    server.js中,我们也要做相应的修改以支持新的消息格式并发送设备的IP地址:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('send text', (data) => {
    let clientIpAddress = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
    data.ipAddress = clientIpAddress;
    io.emit('receive text', data);
    });

    socket.on('disconnect', () => {
    console.log('user disconnected');
    });
    });

    这样,当用户发送消息时,它们将以[DeviceName]: Message的格式显示,并在服务器端的console中记录IP地址。

    我们可以在server.js中获取连接的客户端的局域网IP地址,并将其与设备名称一同发送到前端。

    首先,修改server.js的消息处理部分以包含IP地址:

    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
    io.on('connection', (socket) => {
    console.log('a user connected');

    socket.on('send text', (data) => {
    // 获取客户端的IP地址,清除IPv6前缀,使其更易读
    let clientIpAddress = socket.request.connection.remoteAddress.replace(/^::ffff:/, '');
    // 如果从运行服务器的同一台机器上连接,将会显示“Localhost”而不是::1或127.0.0.1
    if (clientIpAddress === '::1' || clientIpAddress === '127.0.0.1') {
    clientIpAddress = 'Localhost';
    }


    // 默认设备名称
    if (!data.deviceName || data.deviceName.trim() === "") {
    data.deviceName = "匿名设备";
    }

    // 将设备名和IP地址结合在一起
    data.deviceName = `${data.deviceName} (${clientIpAddress})`;

    io.emit('receive text', data);
    });

    socket.on('disconnect', () => {
    console.log('user disconnected');
    });
    });

    更新index.html如下:

    1
    2
    3
    4
    5
    6
    7
    var socket = io();

    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    var deviceName = document.getElementById('deviceName').value || "匿名设备";
    socket.emit('send text', { text, deviceName });
    };

    现在,在发送消息时,如果用户没有输入设备名称,它将显示为“匿名设备”,并且每个消息都会在设备名称后面带有IP地址。

    当用户发送消息时,它们将以 [DeviceName (IP Address)]: Message 的格式显示。

    0x5.1.2 iPhone页面自适应

    现在我希望在iPhone的浏览器中更好的显示,因为我在iPhone中需要先缩小页面,然后在移动到页面中间再放大才能继续输入消息

    为了确保页面在移动设备上正常显示,需要添加一个viewport元数据标签来控制页面的缩放。

    index.html文件的<head>部分,添加以下meta标签:

    1
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    所以<head>部分现在应该是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <head>
    <title>Realtime Text</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
    body {
    font-family: Arial, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f4f4f4;
    }
    .container {
    padding: 20px;
    border: 1px solid #ddd;
    background-color: #fff;
    }
    </style>
    </head>

    添加此元数据标签后,页面应该会根据设备的屏幕大小自动调整缩放,并且在iPhone等移动设备上应该可以正常显示。

    0x5.2 图像优化与传输

    0x5.2.1 图像传输逻辑

    1. 在前端添加一个文件输入以选择图片
    2. 上传图片到服务器
    3. 服务器保存图片并返回一个可访问的URL
    4. 在客户端中显示图片

    修改index.html来添加文件输入,在<div class="container">内添加一个文件输入:

    1
    <input type="file" id="imageUpload" accept="image/*">

    并修改发送按钮的逻辑来检查是否选择了图片:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    var deviceName = document.getElementById('deviceName').value || "匿名设备";
    var imageFile = document.getElementById('imageUpload').files[0];

    if (imageFile) {
    var formData = new FormData();
    formData.append('image', imageFile);
    formData.append('deviceName', deviceName);
    formData.append('text', text);

    fetch('/upload-image', {
    method: 'POST',
    body: formData
    }).then(response => response.json()).then(data => {
    socket.emit('send text', data);
    });
    } else {
    socket.emit('send text', { text, deviceName });
    }
    };

    multer在服务器上处理图像上传,它是一个node.js中间件,用于处理multipart/form-data,主要用于上传文件

    然后,在server.js中添加以下代码来处理图片上传:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const multer = require('multer');
    const path = require('path');

    const storage = multer.diskStorage({
    destination: (req, file, cb) => {
    cb(null, 'uploads/')
    },
    filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname))
    }
    });

    const upload = multer({ storage: storage });

    app.post('/upload-image', upload.single('image'), (req, res) => {
    let imageUrl = `/uploads/${req.file.filename}`;
    res.json({ text: req.body.text, deviceName: req.body.deviceName, imageUrl: imageUrl });
    });

    app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

    确保在服务器目录中创建一个名为uploads的文件夹来存储上传的图片

    修改前端显示逻辑,修改socket.on('receive text', ...)逻辑以处理可能的图片消息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    socket.on('receive text', function(data) {
    var li = document.createElement("li");
    var content = `[${data.deviceName}]: ${data.text}`;
    if (data.imageUrl) {
    content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
    }
    li.innerHTML = content;
    document.getElementById('messages').appendChild(li);
    });

    现在,应该能够在前端选择图片,上传到服务器,并在接收消息时看到图片显示

    0x5.2.2 图片显示与优化

    现在我发现在发送多条消息时,发送消息的控件显示在屏幕之外时无法再继续输入消息了

    这个问题出现的原因是,随着消息的增加,页面内容增长,但浏览器并没有自动滚动以保持输入控件在可见范围内。

    为了解决这个问题,我们可以强制页面在添加新消息后滚动到底部。这样,输入框和按钮始终保持在屏幕上的可见范围内

    首先更新HTML结构,我们需要对HTML的结构进行一些修改以分离消息列表和输入区域。将输入区域和按钮放入一个固定的底部工具栏中,使其始终位于屏幕底部。

    index.html<div class="container">应该更改为以下结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="container">
    <ul id="messages" style="max-height: 80vh; overflow-y: auto;"></ul>

    <div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 10px;">
    <input type="text" id="deviceName" placeholder="Device Name">
    <textarea id="textArea" placeholder="Type your message..."></textarea>
    <input type="file" id="imageUpload" accept="image/*">
    <button id="sendButton">Send</button>
    </div>
    </div>

    上述更改将消息列表的最大高度限制为视口的80%,并为其添加了滚动条。发送消息的工具栏被固定在底部。

    添加自动滚动逻辑,在每次添加消息到列表后,执行滚动到底部的逻辑

    socket.on('receive text', ...)的末尾添加以下代码:

    1
    document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;

    这段代码会将消息列表滚动到最底部,确保新消息总是可见的。

    这些更改应该确保无论发送多少消息,输入框和按钮始终保持在屏幕上的可见范围内。

    消息多发导致标签被遮挡

    我们需要对HTML和CSS进行调整以确保”Received Messages:”标签始终可见。

    以下是如何修改的,修改HTML结构

    index.html中,将”Received Messages:”标签从当前的位置移到工具栏上方,并为其添加一个包围元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <div class="container">
    <div id="header" style="background-color: white; padding: 10px;">
    Received Messages:
    </div>

    <ul id="messages" style="max-height: 70vh; overflow-y: auto;"></ul>

    <div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 10px;">
    <input type="text" id="deviceName" placeholder="Device Name">
    <textarea id="textArea" placeholder="Type your message..."></textarea>
    <input type="file" id="imageUpload" accept="image/*">
    <button id="sendButton">Send</button>
    </div>
    </div>

    调整CSS样式,修改<ul id="messages">的样式来考虑标题的高度。确保消息列表在标题和工具栏之间:

    1
    2
    3
    4
    5
    #messages {
    margin-top: 30px;
    max-height: 70vh;
    overflow-y: auto;
    }

    这些更改应该确保”Received Messages:”标签不会被挡住,并且在添加消息时,列表仍然会正确滚动。

    0x5.3 iPhone界面与交互优化

    0x5.3.1 点击图片放大原尺寸

    为了实现图片点击放大的效果,可以使用一个简单的模态窗口(modal)。当用户点击图片时,模态窗口会显示,并且在其中展示原始尺寸的图片。当用户点击模态窗口之外的任何位置或按下Esc键时,模态窗口会关闭。

    index.html<body> 末尾添加模态窗口的HTML结构:

    1
    2
    3
    4
    <!-- Image Modal -->
    <div id="imageModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); z-index: 9999; align-items: center; justify-content: center;">
    <img id="modalImage" src="" style="max-width: 90%; max-height: 90%;">
    </div>

    添加显示和关闭模态窗口的逻辑,在现有的JavaScript代码下添加以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    document.addEventListener('click', function(event) {
    // If an image inside the messages is clicked
    if (event.target.tagName === 'IMG' && event.target.closest('#messages')) {
    var modal = document.getElementById('imageModal');
    var modalImage = document.getElementById('modalImage');

    modalImage.src = event.target.src;
    modal.style.display = 'flex';

    modal.addEventListener('click', function() {
    modal.style.display = 'none';
    });
    }
    });

    // Close modal on pressing 'Escape'
    document.addEventListener('keydown', function(event) {
    if (event.key === 'Escape') {
    var modal = document.getElementById('imageModal');
    modal.style.display = 'none';
    }
    });

    这些代码首先会检查是否点击了消息列表中的图片。如果是,则显示模态窗口并设置图片的源。点击模态窗口的任何位置都会关闭它。如果按下’Escape’键,也会关闭模态窗口。

    现在,当点击图片时,应该能够在模态窗口中放大显示图片,而点击模态窗口的任何位置或按下Esc键都会关闭它。

    0x5.3.2 iPhone浏览器send按钮超屏

    这个问题可能是由于固定底部工具栏的内容太多或样式设置不合适,在某些屏幕尺寸或设备上导致了溢出。为了解决这个问题,我们需要对工具栏的布局和样式进行调整,确保在较小的屏幕上也能适应。

    我们可以采用以下策略:

    1. 将输入框和发送按钮放在一行。
    2. 调整宽度和样式以适应不同的屏幕尺寸。
    3. 使用flex布局简化样式和布局。

    更新HTML结构,在index.html中,将工具栏部分修改为:

    1
    2
    3
    4
    5
    6
    7
    8
    <div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1);">
    <div style="display: flex; align-items: center;">
    <input type="text" id="deviceName" placeholder="Device Name" style="flex: 1; margin-right: 5px;">
    <input type="file" id="imageUpload" accept="image/*" style="margin-right: 5px;">
    <textarea id="textArea" placeholder="Type your message..." style="flex: 2; margin-right: 5px;"></textarea>
    <button id="sendButton">Send</button>
    </div>
    </div>

    调整样式,确保每个元素都有合适的宽度和间距,使其适应小屏幕。这里,我们使用了flex布局,让deviceNametextArea元素根据剩余空间动态调整其宽度。

    现在,工具栏应该能够更好地适应较小的屏幕,如iPhone的浏览器,并且发送按钮应该始终可见和可用。可能需要根据实际设备和屏幕尺寸进行进一步的微调。

    0x5.3.3 iPhone输入框超出屏幕

    为了在iPhone的浏览器中获得更好的响应式布局,我们可以采用以下策略:

    1. 使用媒体查询来针对不同的屏幕尺寸进行样式调整。
    2. 在较小的屏幕上,考虑简化布局,可能需要将一些控件移至其他位置。

    更新HTML结构,在index.html中,继续使用上一个给出的工具栏HTML结构。

    添加CSS媒体查询,我们将为小屏幕(如iPhone)添加特定的样式。为此,在的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
    /* 大屏幕的默认样式 */
    #deviceName, #textArea {
    flex: 1;
    margin-right: 5px;
    }

    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    #deviceName {
    display: none; /* 在小屏幕上隐藏设备名称输入 */
    }

    #imageUpload {
    width: 40px; /* 根据需要调整此值 */
    margin-right: 5px;
    }

    #textArea {
    flex: 3; /* 给文本区域更多的空间 */
    }

    #sendButton {
    flex-shrink: 0; /* 防止按钮收缩 */
    }
    }

    这里的做法是,当屏幕宽度小于或等于500px时(可以根据需要调整此值),隐藏了deviceName输入框以节省空间,并给予textArea更多的空间。此外,还调整了上传按钮和发送按钮的尺寸和样式,以更好地适应小屏幕。

    这些调整应该会改进iPhone浏览器上的布局,使其更加友好,可能需要根据实际设备和屏幕尺寸进行进一步的微调

    0x5.3.4 iPhone按钮输入框重叠修正

    为了实现这个目标,我们需要对较小屏幕的布局进行改进,使其变为多行排列。

    更新HTML结构,在index.html中,继续使用上一个给出的工具栏HTML结构。

    调整CSS媒体查询以实现多行排列布局,我们将在小屏幕上采用多行排列布局。添加或修改以下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
    /* 大屏幕的默认样式 */
    #deviceName, #textArea {
    flex: 1;
    margin-right: 5px;
    }

    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    /* 对于较小的屏幕,将伸缩方向更改为列 */
    div[style*="position: fixed; bottom: 0;"] > div {
    flex-direction: column;
    }

    #deviceName {
    margin-bottom: 5px;
    }

    #imageUpload {
    margin-right: 0; /* 重置margin-right */
    margin-bottom: 5px;
    width: 100%;
    }

    #textArea {
    margin-right: 0; /* 重置margin-right */
    margin-bottom: 5px;
    flex: 3;
    }

    #sendButton {
    width: 100%;
    margin-bottom: 5px; /* 留出一点间距 */
    }
    }

    这样的改变会使得在小屏幕上,设备名称、图片上传、文本输入框和发送按钮都单独占据一行,而不会相互重叠。这应该会在iPhone上提供更好的用户体验

    0x5.3.5 iPhone布局对齐与点击优化

    更新媒体查询

    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
    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    /* 对于较小的屏幕,将伸缩方向更改为列 */
    div[style*="position: fixed; bottom: 0;"] > div {
    flex-direction: column;
    }

    #deviceName, #textArea {
    margin-right: 0; /* 重置margin-right */
    margin-bottom: 5px;
    width: 100%; /* 全宽 */
    height: 50px; /* 增加高度,便于触摸 */
    }

    #imageUpload {
    margin-right: 0; /* 重置margin-right */
    margin-bottom: 5px;
    width: 100%; /* 全宽 */
    }

    #sendButton {
    width: 100%; /* 全宽 */
    height: 50px; /* 增加高度,便于触摸 */
    margin-bottom: 5px; /* 留出一点间距 */
    }
    }

    这些调整会使得在小屏幕上,每一个元素都占满了整个屏幕宽度,并且增大了输入框和按钮的高度,以便用户更容易地进行触摸操作。

    这应该会提供更好的用户体验,特别是考虑到iPhone和其他移动设备上的拇指操作。可能还需要根据实际设备和需求进行进一步的微调。

    0x5.3.6 控件边缘重叠与iPhone点击

    1. 控件与屏幕边缘的间距问题
    2. iPhone上首次加载时的交互问题

    设置控件与屏幕边缘的间距

    为了增加边距,可以为工具栏内部的元素添加适当的间距。在的CSS中,可以添加或修改以下样式:

    1
    2
    3
    4
    5
    6
    7
    /* 为固定底部工具栏添加填充 */
    div[style*="position: fixed; bottom: 0;"] {
    padding-left: 10px;
    padding-right: 10px;
    }

    /* ... (CSS的其余部分) */

    这会为底部工具栏的左右两侧增加10px的间距。可以根据需要调整这个值

    iPhone上首次加载时的交互问题

    iPhone上的Safari浏览器和其他移动浏览器有一个常见的问题,即当页面首次加载时,固定的元素可能不会响应触摸事件。这是因为浏览器的地址栏和底部工具栏可能会覆盖页面的部分内容。通常,通过滚动页面可以暂时解决这个问题。

    为了解决这个问题,可以尝试在页面首次加载时自动滚动页面一小段距离,这样可以确保底部工具栏不会被浏览器的界面元素覆盖。以下是如何实现:

    index.html<script>标签内部,添加以下JavaScript代码:

    1
    2
    3
    4
    5
    window.addEventListener('load', function() {
    setTimeout(function() {
    window.scrollTo(0, 1);
    }, 0);
    });

    上述代码会在页面加载完成后,立即将页面向上滚动1px。这样可以确保底部的固定工具栏不会被浏览器的界面元素覆盖,并且应该可以响应用户的触摸事件

    0x5.3.7 iPhone上两侧间距问题

    对于在iPhone上的边距问题,我们需要更具体地处理与小屏幕或特定设备相关的CSS样式。

    下面是如何更具体地针对较小屏幕(如iPhone)设置边距:

    首先,确保已经使用了适当的元数据标签来控制视口。在的HTML文件的<head>部分,添加以下标签:

    1
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    这会确保页面的宽度匹配设备的宽度,并且页面的初始缩放级别是1.0。

    接下来,修改的CSS媒体查询以增加边距。将下面的代码添加或合并到的CSS中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* 大屏幕的默认样式 */
    /* ... (你现在的样式) */

    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    /* ... (你当前的小屏幕风格) */

    /* 为整个底部工具栏添加填充 */
    div[style*="position: fixed; bottom: 0;"] {
    padding: 0 10px; /* 这将在左右两边添加10px的内边距 */
    }
    }

    通过添加这些更改,iPhone上的边距应该会生效,并且页面内容会根据设备的屏幕宽度适当地调整。

    0x5.3.8 iPhone输入框宽度异常

    这个问题可能是由于paddingwidth: 100%的组合引起的。当我们为一个元素同时设置了paddingwidth: 100%时,该元素的总宽度会超过其父元素的宽度,因为这里的宽度是指内容宽度,而不包括边距、边框或内边距。

    为了解决这个问题,可以使用box-sizing属性,将其设置为border-box,这样元素的总宽度将包括内容、内边距和边框(但不包括外边距)。

    1. 保证边距。
    2. 使用flexbox来确保子元素在小屏幕上垂直排列。

    在CSS中,找到与小屏幕相关的媒体查询,并进行如下修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @media (max-width: 500px) {
    div[style*="position: fixed; bottom: 0;"] {
    width: 100%;
    padding: 5px;
    box-sizing: border-box;
    }

    #deviceName, #textArea, #imageUpload, #sendButton {
    box-sizing: border-box;
    width: 100%;
    margin-bottom: 5px;
    }

    #deviceName, #textArea {
    height: 50px;
    }

    #sendButton {
    height: 50px;
    }
    }

    这些样式应该确保了在小屏幕设备上,特别是iPhone上,所有的控件都正常排列,并且有足够的边距。

    0x5.3.9 iPhone布局校验与滑动优化

    1. 控件应该按照一行行排列: 这需要调整底部控件的样式。当前的代码中已经使用了flex-direction: column,这是正确的,但的底部控件的子控件仍然在一个flex容器中,这使得它们在同一行上。需要分离它们以使它们在单独的行上。
    2. 两侧与屏幕保持10px的间距: 可以在底部控件中设置padding-leftpadding-right10px
    3. 第一次打开页面需要滚动一小段距离: 已经有了这个功能,使用window.scrollTo(0, 1)在页面加载时向下滚动。这通常用于隐藏浏览器的地址栏,但在最新版本的iOS中可能不起作用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /* ... 现有的样式 ... */

    @media (max-width: 500px) {
    /* ... 其他样式 ... */

    div[style*="position: fixed; bottom: 0;"] {
    padding-left: 10px;
    padding-right: 10px;
    display: flex;
    flex-direction: column;
    }

    #deviceName, #textArea, #imageUpload, #sendButton {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 5px;
    }

    #deviceName, #textArea, #sendButton {
    height: 50px;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- ... 现有的HTML ... -->

    <!-- 调整底部工具栏结构 -->
    <div style="position: fixed; bottom: 0; width: 100%; background-color: white; padding: 5px; box-shadow: 0 -2px 5px rgba(0,0,0,0.1);">
    <input type="text" id="deviceName" placeholder="Device Name">
    <input type="file" id="imageUpload" accept="image/*">
    <textarea id="textArea" placeholder="Type your message..."></textarea>
    <button id="sendButton">Send</button>
    </div>

    0x5.3.A iPhone布局遮挡与间距修正

    1. 两侧控件的距离问题: 如果右侧和屏幕之间的距离是10px,但左侧并非如此,可能是某些样式或其他浏览器默认样式干扰了。可以考虑为每个子控件设置margin-leftmargin-right,以确保他们都有正确的边距。
    2. 图片消息被底部的输入框挡住: 一个常见的方法是为消息列表添加底部内边距,该边距的高度与底部工具栏的高度相同,这样最后一条消息(无论是文本还是图片)都可以完全滚动到视图中而不被挡住。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /* ... 其他样式 ... */

    @media (max-width: 500px) {
    /* ... 其他样式 ... */

    #deviceName, #textArea, #imageUpload, #sendButton {
    margin-left: 10px;
    margin-right: 10px;
    /* ... 现有的其他样式 ... */
    }

    #messages {
    padding-bottom: 70px;
    }
    }

    0x5.4 消息发送与设备名自动填充

    0x5.4.1 发送后清除已选图像

    为了在发送完消息后清除上次选择的图像,可以在发送消息的函数中清除 <input type="file"> 的值。这将重置文件输入,并确保下次用户点击发送按钮时不会再次发送同一张图片。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    var deviceName = document.getElementById('deviceName').value || "匿名设备";
    var imageFile = document.getElementById('imageUpload').files[0];

    if (imageFile) {
    var formData = new FormData();
    formData.append('image', imageFile);
    formData.append('deviceName', deviceName);
    formData.append('text', text);

    fetch('/upload-image', {
    method: 'POST',
    body: formData
    }).then(response => response.json()).then(data => {
    socket.emit('send text', data);

    // 在此处清除文件输入的值
    document.getElementById('imageUpload').value = "";
    });
    } else {
    socket.emit('send text', { text, deviceName });
    }
    };

    以上的修改确保了每次发送图像消息后,文件输入都会被清除。这样,用户在发送下一条消息时,之前选择的图片不会再次被发送

    0x5.4.2 自动填充设备名称

    为了自动获取设备名称,首先需要明确一个事实:Web 浏览器出于隐私和安全的考虑,并不允许直接获取设备的真实名称。但是,可以使用一些方法来近似识别或标识设备,例如使用浏览器的用户代理字符串。

    用户代理 (User Agent) 字符串包含关于浏览器、操作系统和设备的信息,可以通过这些信息来推断或标识用户的设备类型

    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
    function getDeviceNameFromUserAgent() {
    var userAgent = navigator.userAgent || navigator.vendor || window.opera;

    // Windows Phone必须先行一步,因为它的用户界面也包含“Android”。
    if (/windows phone/i.test(userAgent)) {
    return "Windows Phone";
    }

    if (/android/i.test(userAgent)) {
    return "Android Device";
    }

    // iOS检测
    if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
    return "iOS Device";
    }

    if (/macintosh|mac os x/i.test(userAgent)) {
    return "Mac";
    }

    if (/windows/i.test(userAgent)) {
    return "Windows PC";
    }

    return "Unknown Device";
    }

    // 使用函数来自动填充设备名称
    document.getElementById('deviceName').value = getDeviceNameFromUserAgent();

    此函数根据用户代理字符串来近似地识别设备,并返回一个描述性名称。注意,这只是一个基本的示例,而真实的环境中可能需要更复杂的逻辑来准确识别设备。

    这种方法只是提供了一个近似的设备名称,并不能真正的识别具体的设备型号或品牌

    0x5.4.3 消息发送后自动清空输入框

    为了在发送完消息后自动清除 textArea (你之前的代码中使用这个ID代表消息输入框)中的值,你可以在发送消息的函数中添加一行代码来实现这个功能

    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
    document.getElementById('sendButton').onclick = function() {
    var text = document.getElementById('textArea').value;
    var deviceName = document.getElementById('deviceName').value || "匿名设备";
    var imageFile = document.getElementById('imageUpload').files[0];

    // 如果选择了图片,则发送图片
    if (imageFile) {
    var formData = new FormData();
    formData.append('image', imageFile);
    formData.append('deviceName', deviceName);
    formData.append('text', text);

    fetch('/upload-image', {
    method: 'POST',
    body: formData
    }).then(response => response.json()).then(data => {
    socket.emit('send text', data);
    });
    } else {
    // 如果没有选择图片,则直接发送文本消息
    socket.emit('send text', { text, deviceName });
    }

    // 在所有情况下都清除textArea和图片选择器的值
    document.getElementById('textArea').value = '';
    document.getElementById('imageUpload').value = ''; // 这将清除文件输入选择器的内容
    };

    我在函数的底部添加了清除textArea和图片选择器imageUpload的代码。这样,无论是否选择了图片,都会在发送完消息后清除文本框和图片选择器的内容。

    0x5.5 大屏适配与控件布局调整

    0x5.5.1 Windows居中 iPhone逐行

    修改CSS以在大屏幕上对底部控件进行水平居中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .bottom-controls {
    display: flex;
    justify-content: center; /* 内容器水平居中 */
    }

    .bottom-controls > div {
    display: flex; /* 这确保了内部容器的子元素水平对齐 */
    align-items: center;
    }

    /* ... 其他样式 ... */

    使用媒体查询来特定地为小屏幕(例如iPhone)定义样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    .bottom-controls {
    justify-content: flex-start; /* 这将使内部容器与起始位置对齐 */
    }
    .bottom-controls > div {
    flex-direction: column; /* 这确保了内部容器的子容器是垂直堆叠的 */
    align-items: center;
    width: 100%;
    }
    #deviceName, #textArea, #imageUpload, #sendButton {
    display: block;
    width: 100%; /* 确保控件在小屏幕上占满宽度 */
    }
    /* ... 你的其他小屏幕风格 ... */
    }

    这样,底部的控件在大屏幕上会水平居中,而在小屏幕(如iPhone)上,则会按照行排列。

    0x5.5.2 扩大大屏幕底部控件尺寸

    为了在大屏幕上使控件更大,可以使用媒体查询来区分大屏幕和小屏幕的样式。在大屏幕的样式中,可以增加控件的高度和字体大小。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* 大屏幕的样式(例如,Windows, MacBook) */
    @media (min-width: 501px) {
    #deviceName, #textArea, #imageUpload, #sendButton {
    height: 60px; /* 增加控件的高度 */
    font-size: 18px; /* 增加字体大小以获得更好的可视性 */
    padding: 10px; /* 为美观添加一些填充 */
    }
    #sendButton {
    padding: 10px 20px; /* 调整按钮的填充 */
    }
    }

    这段代码将确保在宽度超过501px的屏幕上,控件的高度、字体大小和内部间距都增加了,从而更适合大屏幕使用。可以根据实际需要进一步调整这些值。

    0x5.5.3 大屏幕输入框长度及控件间距

    为了在大屏幕上调整输入框的长度并在输入框和按钮之间添加合适的距离,可以进行以下修改:

    1. 在大屏幕上为输入框和按钮添加合适的间距
    2. 为大屏幕上的输入框增加宽度
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /* 大屏幕的样式(例如,Windows, MacBook) */
    @media (min-width: 501px) {
    #deviceName, #textArea, #imageUpload, #sendButton {
    height: 60px;
    font-size: 18px;
    padding: 10px;
    margin-right: 10px;
    }
    #sendButton {
    padding: 10px 20px;
    }
    #textArea {
    width: 60%;
    }
    /* 可以移除伸缩容器中最后一个元素的右距 */
    div[style*="position: fixed; bottom: 0;"] > div > *:last-child {
    margin-right: 0;
    }
    }

    这段代码增加了#textArea的宽度并在所有底部控件之间添加了margin-right。为了确保最后一个元素(按钮)不与其父元素的右边界有太大的间隔,我添加了一个特定选择器来移除其右边距

    0x5.6 增加深浅色主题

    1. 在HTML中添加一个切换按钮。
    2. 为深色模式定义CSS样式。
    3. 使用JavaScript监听按钮点击事件来切换主题。

    更新index.html如下:

    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
    [data-theme="dark"] input, [data-theme="dark"] textarea, [data-theme="dark"] button {
    color: white; /* 将文字颜色设置为白色 */
    border-color: white; /* 如果需要, 也可以设置边框颜色 */
    }

    :root {
    /* 浅色模式的设置 */
    --text-color: black;
    --background-color: white;
    --input-bg: #fff;
    --container-bg: #fff;
    --header-bg: #fff;
    --button-color: black; /* 新增 */
    }

    [data-theme="dark"] {
    /* 深色模式的设置 */
    --text-color: black; /* 文本颜色为黑色 */
    --background-color: white; /* 背景颜色为白色 */
    }

    .bottom-controls {
    display: flex;
    justify-content: center;
    }
    .bottom-controls > div {
    display: flex;
    align-items: center;
    }
    /* 为固定底部工具栏添加填充 */
    div[style*="position: fixed; bottom: 0;"] {
    padding-left: 10px;
    padding-right: 10px;
    }
    /* 保证body的背景颜色被应用 */
    body {
    font-family: Arial, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: var(--background-color) !important;
    color: var(--text-color);
    font-family: "JetBrainsMono", Arial, sans-serif;
    }
    /* 如果有特定的元素想要应用不同的文本或背景颜色,可以为它们单独设置: */
    .element {
    color: var(--text-color);
    background-color: var(--background-color);
    }
    .container {
    padding: 20px;
    border: 1px solid #ddd;
    background-color: #fff;
    }
    /* 小屏幕的样式(如iphone) */
    @media (max-width: 500px) {
    .bottom-controls {
    justify-content: flex-start;
    }
    .bottom-controls > div {
    flex-direction: column;
    align-items: center;
    width: 100%;
    }

    /* 更新底部控件的容器样式 */
    div[style*="position: fixed; bottom: 0;"] {
    background-color: var(--container-bg);

    padding-left: 10px;
    padding-right: 10px;
    display: flex;
    flex-direction: column;
    }

    /* 确保所有控件拉伸以填充可用宽度 */
    #deviceName, #textArea, #imageUpload, #sendButton {
    background-color: var(--container-bg);

    width: 100%;
    box-sizing: border-box;
    margin-bottom: 5px;
    }

    /* 增加高度,便于触摸 */
    #deviceName, #textArea, #sendButton {
    height: 50px;
    }
    /* 在邮件列表的底部添加填充 */
    #messages {
    padding-bottom: 70px;
    }
    }
    /* 大屏幕的样式(例如,Windows, MacBook) */
    @media (min-width: 501px) {
    #deviceName, #textArea, #imageUpload, #sendButton {
    background-color: var(--input-bg);

    height: 60px;
    font-size: 18px;
    padding: 10px;
    margin-bottom: 10px;
    }
    #textArea {
    width: 50%;
    }
    #sendButton {
    padding: 10px 20px;
    margin-left: 10px;
    }
    }
    body, button {
    background-color: var(--bg-color);
    color: var(--text-color);
    }
    /* 为了确保按钮的文本颜色能够在两种主题下都清晰可见 */
    button {
    color: var(--button-color);
    }
    /* 底部控件的文本颜色 */
    .bottom-controls input,
    .bottom-controls textarea,
    .bottom-controls button {
    color: var(--text-color);
    border-color: var(--text-color);
    }
    .container {
    background-color: var(--container-bg);
    }
    body.dark-theme {
    /* 深色模式的设置 */
    --text-color: white;
    --background-color: #555;
    --input-bg: #555;
    --container-bg: #444;
    --header-bg: #444;
    --button-color: white; /* 新增 */
    }
    #header {
    background-color: var(--header-bg);
    }
    1
    2
    3
    4
    // 切换主题
    document.getElementById('themeToggle').addEventListener('click', function() {
    document.body.classList.toggle('dark-theme');
    });

    现在,当点击”Toggle Theme”按钮,页面的主题应该会在深色和浅色模式之间切换,并且文本应该会根据相应的模式改变颜色。

    0x6 导入自定义字体

    1. 选择字体: 首先,需要一个字体文件。这可以是.ttf.woff.woff2.eot等格式。确保拥有使用该字体的权利,特别是如果打算在商业项目中使用。
    2. 字体文件上传: 将字体文件上传到的服务器或CDN,并确保知道文件的URL。
    3. 在CSS中导入字体: 在CSS文件或<style>标签中,使用@font-face规则来定义和导入字体。

    下载JetBrains Mono字体,解压JetBrainsMono-Regular.ttfJetBrainsMono-Regular.woff2到网站根目录

    1
    2
    3
    4
    5
    6
    7
    @font-face {
    font-family: "JetBrainsMono"; /* 可以为字体起一个名字 */
    src: url('JetBrainsMono-Regular.woff2') format('woff2'), /* 最优先 */
    url('JetBrainsMono-Regular.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    }

    在这里,path-to-your-font应该是字体文件的URL。如果有多种格式的字体文件,列出它们都是一个好主意,因为这可以确保更好的兼容性

    1
    2
    3
    4
    5
    /* 保证body的背景颜色被应用 */
    body {
    /* ... 其他样式 ... */
    font-family: "JetBrainsMono", Arial, sans-serif;
    }

    在这里,"MyCustomFont"是我们为自定义字体定义的名字,Arial, sans-serif是后备字体,用于在某些情况下(例如,如果自定义字体加载失败)

    考虑性能:字体文件可能很大,所以考虑只包括真正需要的字体权重和样式。例如,如果只需要常规和加粗,那么不要加载斜体或其他权重

    0x7 添加自动加载历史记录功能

    如果你想添加一个自动加载历史记录的功能,你需要在服务器端存储这些历史消息。这样,当新用户连接时,你就可以将这些历史消息发送给他们。

    更新server.js如下:

    在服务器上添加一个变量来存储消息

    1
    const messageHistory = [];

    当接收到新的文本消息时,将它添加到历史记录中

    1
    2
    3
    4
    5
    6
    7
    8
    socket.on('send text', (data) => {
    // ...(其他代码不变)

    // 将消息添加到历史记录
    messageHistory.push(data);

    io.emit('receive text', data);
    });

    当新用户连接时,发送所有历史消息给他们

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    io.on('connection', (socket) => {
    console.log('a user connected');

    // 发送历史消息给新连接的用户
    messageHistory.forEach((message) => {
    socket.emit('receive text', message);
    });

    // ...(其他代码不变)
    });

    index.html在客户端的 JavaScript 里,你不需要做任何修改。因为客户端已经设置了一个监听函数来处理 ‘receive text’ 事件,所以当它接收到历史消息时,会自动将其添加到消息列表。

    这样,当有新用户加入聊天时,服务器就会自动发送所有历史消息给他们

    0x8 添加消息代码格式化和时间戳

    为了在消息中添加发送时间,可以在服务器端获取当前时间,并将其与其他数据一起发送。同时,也需要在客户端的JavaScript中修改接收数据的部分,以显示这一时间。

    首先,在 server.js'send text' 事件处理程序中添加发送时间:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    socket.on('send text', (data) => {
    // ...其它代码

    // 添加消息发送时间
    data.timestamp = new Date().toLocaleString();

    // 将新消息添加到历史记录
    messageHistory.push(data);

    io.emit('receive text', data);
    });

    修改 index.html 在接收消息部分,添加代码格式支持和时间戳

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    socket.on('receive text', function(data) {
    var li = document.createElement("li");
    var time = data.timestamp || new Date().toLocaleString(); // 获取时间戳,如果没有则使用当前时间

    var content = `[${data.deviceName} @ ${time}]: `; // 显示设备名和时间
    if (data.text.startsWith("```") && data.text.endsWith("```")) {
    // 如果文本以 "```" 开头和结尾,将其识别为代码
    content += `<pre>${data.text.slice(3, -3)}</pre>`;
    } else {
    content += `${data.text}`;
    }

    if (data.imageUrl) {
    content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
    }

    li.innerHTML = content;
    document.getElementById('messages').appendChild(li);
    });

    这样,如果用户在 textArea 输入的文本以 **”```”** 开头和结尾,这段文本将会被认为是代码,并用 <pre> 标签进行包裹,以维持其格式。同时,每条消息也会显示发送时间。

    0x9 增加消息删除与数据库

    0x9.1 增加消息删除

    创建一个复选框要对应每条消息,所以必须设置每条消息和复选框的ID保持一致,在选择复选框时应该同时删除后面的消息

    修改index.html文件

    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
    var messageId = 0;  // 初始化一个全局变量
    socket.on('receive text', function(data) {
    var li = document.createElement("li");

    // 创建复选框
    var checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.className = "messageCheckbox";
    li.appendChild(checkbox); // 将复选框添加到列表项中

    var time = data.timestamp || new Date().toLocaleString(); // 获取时间戳,如果没有则使用当前时间

    var content = `[${data.deviceName} @ ${time}]: `; // 显示设备名和时间
    if (data.text.startsWith("```") && data.text.endsWith("```")) {
    // 如果文本以 "```" 开头和结尾,将其识别为代码
    content += `<pre>${data.text.slice(3, -3)}</pre>`;
    } else {
    content += `${data.text}`;
    }

    if (data.imageUrl) {
    content += `<br><img src="${data.imageUrl}" alt="Uploaded image" style="max-width: 300px;">`;
    }

    li.innerHTML += content;
    document.getElementById('messages').appendChild(li);

    li.setAttribute('data-message-id', messageId); // 为每个消息设置唯一ID
    messageId++;

    console.log("设备名称和IP地址:", data.deviceName);
    });

    向服务器发送删除请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    document.getElementById('deleteButton').onclick = function() {
    var selectedMessages = document.querySelectorAll('.messageCheckbox:checked');
    var messageIdsToDelete = [];
    selectedMessages.forEach(function(checkbox) {
    var li = checkbox.closest('li');
    messageIdsToDelete.push(li.getAttribute('data-message-id'));
    li.remove();
    });
    socket.emit('delete messages', messageIdsToDelete);

    // 向后端发送删除请求
    messageIdsToDelete.forEach(id => {
    fetch(`/message/${id}`, {
    method: 'DELETE'
    }).then(response => {
    if (!response.ok) {
    // 以某种方式处理错误响应
    console.error('从服务器删除消息失败');
    }
    });
    });
    };

    向所有客户端广播消息ID,以供删除

    1
    2
    3
    4
    5
    6
    7
    socket.on('delete messages', function(messageIds) {
    messageIds.forEach(function(messageId) {
    var li = document.querySelector('li[data-message-id="' + messageId + '"]');
    if (li) li.remove();
    });
    io.emit('delete messages', messageIds);
    });

    0x9.2 前端数据库联动删消息

    0x9.2.1 创建数据库

    修改server.js文件

    1
    const db = new sqlite3.Database('./messages.db');

    0x9.2.2 创建表

    创建message表,如果存在则什么都不做,包含id、content、deviceName、 ipAddress、 timestamp字段

    1
    2
    3
    4
    5
    6
    7
    db.run("CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, deviceName TEXT, ipAddress TEXT, timestamp TEXT)", (err) => {
    if (err) {
    console.error("创建/检查表错误:", err);
    return;
    }
    console.log("表检查/创建成功");
    });

    0x9.2.3 读取数据库所有消息

    从数据库中查询所有的消息记录,并将每条消息存储到messageHistory数组中,若查询过程中出现错误,会在控制台中打印错误信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    db.all("SELECT id, content, deviceName, ipAddress, timestamp FROM messages", [], (err, rows) => {
    if (err) {
    console.error("从数据库获取历史消息出错:", err);
    return;
    }
    // 遍历数据库每条记录
    rows.forEach(row => {
    const message = {
    id: row.id, // 确保这里保留 id
    text: row.content,
    deviceName: row.deviceName,
    ipAddress: row.ipAddress,
    timestamp: row.timestamp
    };
    // 将数据库中的数据保存在 messageHistory
    messageHistory.push(message);
    });
    console.log("从数据库加载历史消息");
    });

    0x9.2.4 保存前端消息到数据库

    定义了一个HTTP POST路由/send,用于从请求体中接收消息内容,并在消息内容存在时将其存储到数据库中,若消息内容为空,则返回一个错误响应

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    app.post('/send', (req, res) => {
    const messageContent = req.body.message;

    if (messageContent) {
    // 存储消息到数据库
    db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)", [data.text, data.deviceName, clientIpAddress, data.timestamp],
    (err) => {
    if (err) {
    console.error("插入数据库出错:", err);
    return;
    }
    console.log('收到的信息:', data.text || messageContent);
    });
    } else {
    res.status(400).send({error: "消息内容为空"});
    }
    });

    0x9.2.5 广播所有消息给用户

    当一个用户与服务器建立Socket连接时的操作:首先打印出用户已连接的消息,然后将存储在messageHistory数组中的每条历史消息(经过修改以显示设备名和IP地址)发送给新连接的用户

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    io.on('connection', (socket) => {
    console.log('有一个用户连接');

    function formatMessageForEmission(originalMessage) {
    return {
    ...originalMessage,
    deviceName: `${originalMessage.deviceName} (${originalMessage.ipAddress})`
    };
    }

    // 发送历史消息给新连接的用户
    messageHistory.forEach((message) => {
    socket.emit('receive text', formatMessageForEmission(message));
    // console.log("format的设备和IP地址:",message)
    });

    0x9.2.6 广播Socket客户端消息

    当服务器通过Socket接收到客户端发送的send text事件时,首先获取和处理客户端的IP地址,为没有提供设备名称的消息设置默认名称,然后将消息及相关信息添加到数据库中。之后,会修改消息的设备名称,将新消息添加到历史记录数组,并通过Socket广播这条消息给所有连接的客户端

    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
    socket.on('send text', (data) => {
    // 获取客户端的IP地址
    let clientIpAddress = socket.request.connection.remoteAddress.replace(/^::ffff:/, ''); // 清除IPv6前缀

    if (clientIpAddress === '::1' || clientIpAddress === '127.0.0.1') {
    clientIpAddress = 'Localhost';
    }

    // 添加ipAddress字段到data对象
    data.ipAddress = clientIpAddress;

    // 默认设备名称
    if (!data.deviceName || data.deviceName.trim() === "") {
    data.deviceName = "匿名设备";
    }

    // 添加消息发送时间
    data.timestamp = new Date().toLocaleString();

    // 添加消息到数据库中
    db.run("INSERT INTO messages (content, deviceName, ipAddress, timestamp) VALUES (?, ?, ?, ?)",
    [data.text, data.deviceName, clientIpAddress, data.timestamp],
    function(err) { // 使用函数关键字以便访问 this.lastID
    if (err) {
    console.error("插入数据库出错:", err);
    return;
    }
    data.id = this.lastID; // 获取新插入的行的 ID
    messageHistory.push(data); // 将新消息添加到历史记录
    console.log('通过套接字收到的消息:', data.text);
    }
    );

    io.emit('receive text', formatMessageForEmission(data));
    });

    0x9.2.7 处理DELETE请求删数据库消息

    当收到HTTP DELETE请求时,根据指定的消息ID从数据库中删除相应的消息记录;若删除过程中出现错误,则返回错误信息,否则通知请求者消息已被删除。如果未提供有效的消息ID,则返回“无效的消息ID”错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    app.delete('/message/:id', (req, res) => {
    const messageId = req.params.id;
    if (messageId) {
    db.run("DELETE FROM messages WHERE id=?", [messageId], (err) => {
    if (err) {
    console.error("从数据库删除消息出错:", err);
    res.status(500).send({error: "删除消息失败"});
    return;
    }

    // 从 messageHistory 数组中删除消息
    const index = messageHistory.findIndex(message => message.id === Number(messageId));
    if (index !== -1) {
    messageHistory.splice(index, 1);
    }

    res.send({status: "消息已删除"});
    });
    } else {
    res.status(400).send({error: "无效的消息ID"});
    }
    });

    0xA Docker封装与测试

    部署Node.js网站到QNAP NAS上的一个优雅的方式是使用Docker。这种方法提供了隔离,便于管理和升级,而不会影响主机系统

    0xA.1 Docker环境准备

    0xA.1.1 创建Dockerfile

    1. 打开终端或命令提示符:导航到Node.js项目的根目录。

    2. 创建一个新的Dockerfile

      如果使用的是Linux或Mac,可以在终端中输入以下命令:

      1
      touch Dockerfile

      如果在Windows上,可以使用命令提示符或PowerShell并输入以下命令:

      1
      echo. > Dockerfile
    3. 编辑Dockerfile

      使用喜欢的文本编辑器打开新创建的Dockerfile。例如,可以使用VS Code、Notepad++、vim等。

    4. 在Dockerfile中输入内容

      复制上文给出的Dockerfile内容,并粘贴到的Dockerfile中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # 或者选择的其他Node.js版本
    FROM node:14 AS build-stage

    # 设置工作目录
    WORKDIR /usr/src/app

    # 将package.json和package-lock.json文件复制到工作目录
    COPY package*.json ./

    # 安装应用程序的依赖项
    RUN npm install

    # 复制应用程序的源代码到工作目录
    COPY . .

    # 暴露端口
    # 或的应用程序运行的其他端口
    EXPOSE 3000

    # 定义Docker容器的启动命令
    CMD ["npm", "start"]

    保存并关闭文件

    0xA.1.2 构建Docker镜像

    在项目的目录中执行以下命令,以构建一个Docker镜像:

    1
    docker build -t realtimetext .

    要确保的镜像已经正确构建,可以使用以下命令列出所有本地的Docker镜像:

    1
    docker images

    0xA.1.3 运行Docker容器

    确保Docker已安装并正在运行,然后运行以下命令以启动的Node.js应用:

    1
    docker run -p 4000:3000 realtime_text

    0xA.2 导出容器上传并运行

    0xA.2.1 导出Docker容器

    首先,需要确定一个有效的路径来保存tar文件

    1
    mkdir docker-images

    查看当前运行的docker ID

    1
    docker ps

    将容器提交为新的Docker镜像

    1
    docker commit [CONTAINER_ID] realtime_text

    这应该会将的realtime_text镜像保存为realtime_text.tar文件,位于docker-images/目录下

    1
    docker save -o docker-images/realtime_text.tar realtime_text

    0xA.2.2 上传Docker容器

    1. 需要确保在QNAP中的FileStation创建一个名为Container的目录

    2. 在App Center中搜索并安装Container Station,等待服务启动完成就可以打开了

    3. 打开映像页面,点击右上角的导入,从本地计算机选择刚导出的Docker镜像文件realtime_text,再点击导入

    0xA.2.3 运行Docker容器

    在容器页面启动名为realtime_text的容器,查看其详细信息及Web URL:http://192.168.50.145:32769,就是部署后的网页地址

    0xA.3 维护容器占用空间

    长期发送图片消息会导致upload目录占用较大空间,必要情况下可以在容器页面选择容器后的设置按钮,选择执行/bin/sh控制台

    1
    2
    cd uploads
    rm *

    0xB 常见问题与解决方案

    0xB.1 构建失败

    • 在构建Docker时出现任何问题请先确保你在realtime_text根目录下

    • 构建docker的镜像名必须是小写

    • 构建docker镜像必须指定导出目录

    • 如果你在Linux下创建docker请确保相关文件有执行的权限

      1
      2
      chmod +x script_name.sh
      ./script_name.sh

    0xB.2 端口占用

    在运行时报告了一个端口占用错误

    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
    “node:events:491
    throw er; // Unhandled 'error' event
    ^
    Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2] (node:net:1485:16)
    at listenInCluster (node:net:1533:12)
    at Server.listen (node:net:1621:7)
    at Object.<anonymous> (D:\code\win-code\NodeJS\realtime_text\server.js:60:8)
    at Module._compile (node:internal/modules/cjs/loader:1159:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
    at Module.load (node:internal/modules/cjs/loader:1037:32)
    at Module._load (node:internal/modules/cjs/loader:878:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47
    Emitted 'error' event on Server instance at:
    at emitErrorNT (node:net:1512:8)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
    code: 'EADDRINUSE',
    errno: -4091,
    syscall: 'listen',
    address: '::',
    port: 3000
    }

    Node.js v18.12.1”

    这个错误 Error: listen EADDRINUSE: address already in use :::3000 意味着端口 3000 已经被其他程序或者相同的程序的另一个实例使用了。你有几种方式来解决这个问题:

    1. 杀掉使用该端口的进程

      1. 在 Windows 上:
        1. 打开命令行窗口,然后运行 netstat -ano | findstr :3000
        2. 这会显示使用端口 3000 的所有进程。找到进程 ID(PID)。
        3. 执行 taskkill /PID <PID> /F 来杀掉进程。
      2. 在 macOS 或 Linux 上:
        1. 打开终端,然后运行 lsof -i :3000
        2. 这会显示使用端口 3000 的所有进程。找到进程 ID(PID)。
        3. 执行 kill -9 <PID> 来杀掉进程。
    2. 更改应用程序使用的端口

      在你的 server.js 文件中,找到下面这行代码:

      1
      const port = process.env.PORT || 3000;

      你可以更改 3000 到其他未被使用的端口号,例如:

      1
      const port = process.env.PORT || 4000;

      然后重新运行你的应用程序。

    3. 重启计算机

      如果上面的方法没有解决问题或者你不确定如何进行,重启计算机通常能够解决端口冲突的问题。

      希望这能帮到你!

    0xB.3 数据库自增ID列

    DataGrip 是一个流行的数据库管理工具,允许你通过其图形界面来进行各种数据库操作。要在 DataGrip 中修改表的 id 列为自增,可以按照以下步骤进行:

    1. 打开 DataGrip 并连接到你的 SQLite 数据库。

    2. 在左侧的 数据库浏览器 中,找到你的表。

    3. 右键点击 你想要修改的表,然后选择 Modify Table

    4. 在打开的窗口中,找到 id 列。

    5. 选中 id 列,然后在右侧的属性面板中找到 IdentityAuto Increment 选项并勾选。

    6. 右键点击 该列名,并从上下文菜单中选择 Set Primary Key

    7. 最后,点击窗口下方的 提交应用 按钮来保存更改。

    请注意,不同的数据库系统和不同的版本可能会有些微差异。确保在进行此类操作之前备份你的数据。

    如果 DataGrip 不直接支持 SQLite 的自增特性或有任何其他问题,你可能需要使用 SQL 语句来手动修改表结构,或者像之前所描述的那样,通过创建一个新表、复制数据、删除旧表、然后重命名新表的方式来达到目的。

    0xB.4 确保前端页面与数据库内容同步

    前端消息的ID与数据库ID对应。若前端删除消息失败,可能是ID不匹配或Docker中的sqlite3库问题。若需手动删除消息,以下是相关sql语句。不使用Docker容器可避免该问题,并注意Docker会将24小时制改为12小时制

    0xB.4.1 连接数据库

    1
    sqlite3 messages.db

    0xB.4.2 查询表

    1
    SELECT * FROM messages;

    0xB.4.3 查ID列

    1
    SELECT id FROM messages;

    0xB.4.4 删除指定ID行

    1
    DELETE FROM messages WHERE id = 5;

    0xB.4.5 删除指定多行ID

    1
    DELETE FROM messages WHERE id IN (3, 4, 5);

    0xB.4.6 查询指定ID列的值

    1
    SELECT * FROM messages WHERE id = 5;

    0xB.4.7 查询指定多行ID列的值

    1
    SELECT * FROM messages WHERE id IN (3, 4, 5);

    0xB.4.8 修改指定ID

    在这个语句中,18 替代了第一个 ?21 替代了第二个 ?,而 21 替代了第三个 ?。这样的话,当你执行这个SQL语句时,它会将 messages 表中 rowid 为 21,且 id 为 21 的行的 id 字段的值更新为 18。

    1
    UPDATE "messages" SET "id" = 18 WHERE "rowid" = 21 AND "id" = 21

    0xB.4.9 SSH连接正在运行的docker

    1
    docker exec -it <container_id_or_name> /bin/bash

    0xB.4.A docker中安装vim

    1
    2
    apt-get update
    apt-get install -y vim

    0xB.4.B 重置id列自增值

    查询当前最大值

    1
    SELECT MAX(id) FROM messages;

    将自增ID的SEQ设置为最大ID值:使用以下SQL语句来设置自增ID列的序列(sequence)为当前最大ID值

    1
    UPDATE sqlite_sequence SET seq = 31 WHERE name = 'messages';

    删除消息后需重启docker容器生效

    0xC 运行效果

    等一会,加载图片比较慢

    0xC.1 大屏幕设备

    0xC.2 小屏幕设备

    ]]>
    + + + + + Node.js + + + + + + + QNAP + + + +
    + + + + + 基于MCreator+Fabric+Forge的自定义工作台 + + /2023/07/01/2023-07-01-mcreator-zhi-zuo-zi-ding-yi-gong-zuo-tai/ + + 前言

    玩Minecraft已经有9年了一直都想给Minecraft写Mod,但苦于不会写代码并且几年前就连搭建开发环境都一直没有成功。现在MCreator可以解决这些问题,只需要几分钟就能设计出属于你的Mod

    名词释义

    模组 Modification 缩写为 Mod ,简体中文翻译为模组,它是游戏的一种修改或增强程序.比如玩家向游戏中添加了游戏本身不存在的物体使游戏更加有趣.

    MCreator是开源软件,用于使用直观的易于学习的界面或集成的代码编辑器制作Minecraft Java版mod, Bedrock Edition Add-Ons和数据包。它在世界范围内被Minecraft玩家,mod开发者使用,用于教育和STEM研讨会

    MCreator is open source software used to make Minecraft Java Edition mods, Bedrock Edition Add-Ons, and data packs using an intuitive easy-to-learn interface or with an integrated code editor. It is used worldwide by Minecraft players, mod developers, for education and by STEM workshops.

    Fabric是一个轻量级的,实验性的Minecraft建模工具链。

    Fabric is a lightweight, experimental modding toolchain for Minecraft.

    Forge是Minecraft的主流API接口,其功能与Fabric相似

    Minecraft《我的世界》是一款3D沙盒电子游戏,由Mojang Studios开发。玩家可无拘无束地与由方块、实体构成的3个维度环境互动。

    参考链接

    搭建环境

    MCreator+Forge

    1. 打开科学上网
    2. 下载安装并打开MCreator
    3. 设置MCreator科学上网
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 打开 C:\Users\username\.mcreator\gradle\gradle.properties
    systemProp.http.proxyHost=127.0.0.1
    systemProp.https.proxyHost=127.0.0.1
    systemProp.https.proxyPort=你的科学端口
    systemProp.http.proxyPort=你的科学端口

    #如果没有效果就安装Android Studio,打开 C:\Users\username\.gradle\gradle.properties
    systemProp.http.proxyHost=127.0.0.1
    systemProp.https.proxyHost=127.0.0.1
    systemProp.https.proxyPort=你的科学端口
    systemProp.http.proxyPort=你的科学端口
    1. 设置完保存并重启Mcreator

    2. 设置中文简体,依次点击:Preferences—Interface language—Chinese(China)—Apply—Save,设置完保存并重启Mcreator

    3. 新建工作区,工作区类型选择 Minecraft Forge 模组,模组显示名称 testcase,模组ID / 命名空间 testcase,Minercaft版本(生成器) **Minecraft Forge for 1.19.2 (43.2.0)**,模组Java包名称 str.stringod.testcase,工作区文件夹 D:\code\Minecraft\testcase,点击创建新的工作区。如果Minecraft版本没有1.19.2可以选择任意版本,如果在新建工作区的时候提示Build Field类似的提示请重复第3-4步骤

    如果你想给旧版本开发模组,但是在Minecraft版本中没有找到,那么你需要在MCreator plugins下载对应版本,然后把下载的文件放在.\MCreator\plugins\目录下,然后重启MCreator

    MCreator+Fabric

    1. 打开科学上网
    2. 下载安装并打开MCreator
    3. 设置MCreator科学上网
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 打开 C:\Users\username\.mcreator\gradle\gradle.properties
    systemProp.http.proxyHost=127.0.0.1
    systemProp.https.proxyHost=127.0.0.1
    systemProp.https.proxyPort=你的科学端口
    systemProp.http.proxyPort=你的科学端口

    #如果没有效果就安装Android Studio,打开 C:\Users\username\.gradle\gradle.properties
    systemProp.http.proxyHost=127.0.0.1
    systemProp.https.proxyHost=127.0.0.1
    systemProp.https.proxyPort=你的科学端口
    systemProp.http.proxyPort=你的科学端口
    1. 设置完保存并重启Mcreator

    2. 设置中文简体,依次点击:Preferences—Interface language—Chinese(China)—Apply—Save,设置完保存并重启Mcreator

    3. MCreator plugins下载Fabric版本,然后把下载的文件放在.\MCreator\plugins\目录下,然后重启MCreator

    4. 新建工作区,工作区类型选择 Minecraft Fabric 模组,模组显示名称 testcase,模组ID / 命名空间 testcase,Minercaft版本(生成器) Minecraft Fabric for 1.19.2 - 0.67.1,模组Java包名称 str.stringod.testcase,工作区文件夹 D:\code\Minecraft\testcase,点击创建新的工作区。如果Minecraft版本没有1.19.2可以选择任意版本,如果在新建工作区的时候提示Build Field类似的提示请重复第3-4步骤

    制作Forge模组

    方块外观

    1. 新建模组元素
    2. 方块
    3. 方块名称StringodCrafttable
    4. 创建新方块
    5. 导入方块并使用纹理

    1

    方块属性

    1. 创造模式物品栏选项卡Building Blocks
    2. 硬度15
    3. 亮度15
    4. 光不透明度0
    5. 启用发光渲染
    6. 能够摧毁它的工具pickaxe

    2

    方块生成

    1. 此方块可以替换:删除可以替换的方块
    2. 保存模组元素

    3

    方块配方

    1. 新建模组元素
    2. 配方
    3. 配方名称MakeStringodCrafttable
    4. 创建新配方
    5. 配方组名称stringod

    4

    添加完配方后,保存模组元素

    方块用户图形界面

    1. 新建模组元素
    2. 用户图形界面
    3. 用户图形界面名称UiStringodCrafttable
    4. 创建新用户图形界面
    5. 在界面左侧按照顺序从左到右,从上到下依次添加9个输入槽
    6. 在界面右侧添加一个输出槽
    7. 在界面中间添加一个文本标签,文本———>

    5

    添加完成后保存模组元素

    牛蛋配方流程

    1. 新建模组元素
    2. 流程
    3. 流程名称CrafttableCowEgg
    4. 创建新流程
    5. 流程模板
    6. GUI Template - 3 x 3 Crafting table
    7. 然后删除非必要的代码块

    6

    添加好代码块,保存模组元素

    配方调用流程

    1. 新建模组元素
    2. 流程
    3. 流程名称StringodCrafttableCall
    4. 创建新流程
    5. 点击左侧的高级,选择调用流程,选取CrafttableCowEgg流程

    7

    添加好代码块,保存模组元素

    方块绑定用户图形界面

    1. 打开StringodCrafttable
    2. 方块实体
    3. 为此方块启用实体功能
    4. 绑定此方块到图形界面:UiStringodCrafttable
    5. 启用右键打开绑定的界面
    6. 物品栏大小(储存槽数量):10

    8

    完成后保存模组元素

    用户图形界面绑定配方调用流程

    1. 打开UiStringodCrafttable
    2. 界面左下角展开GUI流程触发器
    3. 当这个界面打开时,每刻发生StringodCrafttableCall

    9

    完成后保存模组元素

    用户图形界面绑定输入槽和输出槽配方调用流程

    1. 打开UiStringodCrafttable
    2. 依次双击9个输入槽和1个输出槽
    3. 当储存槽内容改变时执行StringodCrafttableCall

    10

    完成后保存模组元素

    构建并运行模组

    点击右上角的绿色三角形运行游戏

    11

    制作Fabric模组

    方块外观

    1. 新建模组元素
    2. 方块
    3. 方块名称StringodCrafttable
    4. 创建新方块
    5. 导入方块并使用纹理

    1

    方块属性

    1. 创造模式物品栏选项卡Building Blocks
    2. 硬度15
    3. 亮度15
    4. 光不透明度0
    5. 启用发光渲染
    6. 能够摧毁它的工具pickaxe

    2

    方块生成

    1. 此方块可以替换:删除可以替换的方块
    2. 保存模组元素

    3

    方块配方

    1. 新建模组元素
    2. 配方
    3. 配方名称MakeStringodCrafttable
    4. 创建新配方
    5. 配方组名称stringod

    4

    添加完配方后,保存模组元素

    方块用户图形界面

    1. 新建模组元素
    2. 用户图形界面
    3. 用户图形界面名称UiStringodCrafttable
    4. 创建新用户图形界面
    5. 在界面左侧按照顺序从左到右,从上到下依次添加9个输入槽
    6. 在界面右侧添加一个输出槽
    7. 在界面中间添加一个文本标签,文本———>

    5

    添加完成后保存模组元素

    牛蛋配方流程

    1. 新建模组元素
    2. 流程
    3. 流程名称CrafttableCowEgg
    4. 创建新流程
    5. 流程模板
    6. GUI Template - 3 x 3 Crafting table
    7. 然后删除非必要的代码块

    6

    添加好代码块,保存模组元素

    配方调用流程

    1. 新建模组元素
    2. 流程
    3. 流程名称StringodCrafttableCall
    4. 创建新流程
    5. 点击左侧的高级,选择调用流程,选取CrafttableCowEgg流程

    7

    添加好代码块,保存模组元素

    方块绑定用户图形界面

    1. 打开StringodCrafttable
    2. 方块实体
    3. 为此方块启用实体功能
    4. 绑定此方块到图形界面:UiStringodCrafttable
    5. 启用右键打开绑定的界面
    6. 物品栏大小(储存槽数量):10

    8

    完成后保存模组元素

    用户图形界面绑定配方调用流程

    1. 打开UiStringodCrafttable
    2. 界面左下角展开GUI流程触发器
    3. 当这个界面打开时StringodCrafttableCall

    Fabric 1.19.2版本的插件显然不支持在图形用户界面每刻更新,所以只能在第二次选择当打开图形界面时更新配方

    9_1

    完成后保存模组元素

    用户图形界面绑定输入槽和输出槽配方调用流程

    1. 打开UiStringodCrafttable
    2. 依次双击9个输入槽和1个输出槽
    3. 当储存槽内容改变时执行StringodCrafttableCall

    10

    完成后保存模组元素

    构建并运行模组

    点击右上角的绿色三角形运行游戏

    10_2

    导出模组

    1. 点击MCreator窗口顶部菜单栏中的工作区
    2. 导出模组用于分发
    3. 导出模组而不捐赠,如果你有多余的钱可以捐赠
    4. 选择一个位置并给你的模组命名

    下载示例模组

    目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

    ]]>
    + + + + + Minecraft + + + + + + + MCreator + + + +
    + + + + + 植物大战僵尸阳光修改器 + + /2023/06/24/2023-06-24-zhi-wu-da-zhan-jiang-shi-yang-guang-xiu-gai-qi/ + + 前言

    一直都想搞一个植物大战僵尸的修改器,想通过学习自己制作一个

    打算使用Cheat Engine修改器找到阳光的真正地址,然后再用易语言制作一个针对植物大战僵尸的阳光修改器

    名词释义

    植物大战僵尸:可怕的僵尸即将入侵你的家,唯一的防御方式就是你栽种的植物!武装你的植物,切换他们不同的功能,诸如强悍的豌豆射手或樱桃炸弹,更加快速有效的将僵尸阻挡在入侵的道路上。不同的敌人,不同的玩法构成五种不同的游戏模式,加之夕阳、浓雾以及泳池之类的障碍增加了其挑战性,奇特的游戏乐趣永无止境!

    Cheat Engine:是一款旨在帮助您在没有互联网连接的情况下修改单人游戏的修改器,以便您可以根据自己的喜好使游戏变得更难或更容易(例如:发现 100hp 太容易了,尝试玩最大 1 HP 的游戏),简称CE

    Cheat Engine is a tool designed to help you with modifying single player games without internet connection so you can make them harder or easier depending on your preference(e.g: Find that 100hp is too easy, try playing a game with a max of 1 HP)

    易语言:是一个自主开发,适合国情,不同层次不同专业的人员易学易用的汉语编程语言。易语言降低了广大电脑用户编程的门槛,尤其是根本不懂英文或者英文了解很少的用户,可以通过使用本语言极其快速地进入Windows程序编写的大门

    功能

    • 设置一个很大的阳光数值

    下载地址

    目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

    参考链接

    准备工具

    • Cheat Engine v6.7
    • 易语言 v5.9
    • 易语言超级模块 v9.2

    基础概念

    内存

    内存,一切皆内存,我们操作的全部都是内存。

    所有地址相关的概念,都可以理解为坐标,用来给我们做标记的而已。

    如果实在搞不明白,你想想指南针为啥指向北边,为啥叫南北,只是定义,只是公认

    基址

    以阳光为例,由于内存是动态分配的,我们每一次搜索存放阳光的内存地址都会不一样。

    基址,就是指每一次分配我们都能通过偏移,来找到动态的地址,一般是由模块地址+固定的偏移实现。

    例:我知道阳光在内存中存放地址,是模块地址+400的内存位置,所以阳光基址就一定是模块地址+400。所以动态就变成了,30格的时候,30+400。在40格的时候,40+400了。

    指针

    指针其实就可以理解成外号,你的外号叫二狗子,别人先知道你的外号,然后熟悉了才可以根据外号找到你的真名。

    更多的用处是引用,就像别人往往更愿意叫你二狗子,而不愿意叫你真名一样。

    例:一个指针叫做point,他指向内存的0xFF923200。那他就可以有两个作用,一个就是直接使用指向的内存地址FF923200;另一个可以直接查到FF923200里的值。

    为什么要找基址

    因为游戏退出基址不会变化,只有游戏更新后才会变化,而要是用找到的内存地址,例如:血的内存地址,游戏退出是会变化的,不方便我们编写辅助调用。

    寻找阳光的数量的地址

    需要注意的是每次、每个人搜索的地址都是不是一样的,所以只需要和我的步骤保持一致就行

    首先打开游戏,使用CE加载游戏进程。

    进入游戏,在CE的数值处输入起始默认的50阳光,然后点击首次扫描

    选择好要加入战斗的植物,然后种下一个向日葵,此时阳光数量变为0,再次使用CE修改数值为0,然后点击再次扫描

    此时列表中出现了两个结果,第二个结果的当前值就是阳光现在的数量,选中这个结果右键选择找出是什么改写了这个地址,然后在弹出的确认提示框中点击Yes,然后回到游戏收获一个阳光

    选中0041F4D0 - 01 88 78550000 - add [eax+00005578],ecx这一行点击右侧的详细信息,然后在弹出的窗口中复制这个红色框的地址,需要记住的是这个蓝色框的地址5578

    勾选Hex复选框,输入刚才复制的红色框地址,点击新的扫描,然后再次点击首次扫描会看到有47个结果,首先关注蓝色框地址这一列,如果没有绿色的地址那么就从上自下找一个地址相差比较大的,比如红色框中的地址就和上面的地址相差比较大。选中这个红色框的地址右键选择找出是什么访问了这个地址

    和第5步骤相同,在弹出的窗口中可以看到有很多指令,如果你的窗口中没有任何指令,可以在游戏中拾取1个阳光。选择第一个地址004577C3 - 8B B7 68080000 - mov esi,[edi+00000868]然后点击右侧的详细信息,在弹出的窗口中可以看到和之前一样的界面,这里同样复制红色框中的地址,并手动记录下蓝色框的数字868

    和第6步骤相同,把刚才复制的地址粘贴到数字窗口并确保你已经选中了Hex复选框,然后点击新的扫描,再次点击首次扫描,到这里你应该能在地址列看到绿色的地址,当然如果你并没有看到绿色的地址,那么你可以向下滚动鼠标。如果你依然没有找到绿色的地址,那么请重复以上的步骤直到找到绿色的地址。当你找到绿色的地址时选中其中一个地址并双击会被添加到下面的窗口

    双击地址00731C50会打开一个小窗口,取消勾选十六进制,勾选指针,填写刚才手动记住的两个值5578和868,这里的5578和868是阳光地址的偏移量,然后你会看到这个窗口中的地址20994C40后面的数字是25,这就是游戏中阳光的真正地址和阳光的真实数值,然后点击确定

    你会看到在下面窗口中刚才的地址00731C50已经变成了P->20994C40,然后导出保存这个地址,下次打开CE直接加载这个文件就可以直接修改。需要注意的是当前这个地址的数值还是25,你需要双击数值并修改成你需要的一个数值,比如4789。下次打开这个脚本就可以直接修改阳光为4789

    使用CE生成修改器

    在CE中依次点击文件从表单中生成通用修改器Lua脚本,点击添加热键设置激活的键位和功能描述,也可以设置激活和禁用的声音,修改完成可以设置修改器的程序名称、标题、程序图标。这里需要注意的是一定要确保CE和CE要生成的修改器名称和路径都是英文并不含特殊字符的,否则会生成失败。

    特殊字符:\ / * ? : | “<> . , ; :

    下次打开游戏就可以直接使用CE生成的修改器来修改游戏的阳光数量了。如果你的修改器没有生效,但是你发现却可以使用之前导出的P->20994C40地址的脚本来修改游戏,在这种情况下最好还是使用其他语言编写修改器来达到和CE生成修改器一样的目的

    使用易语言编写修改器

    搭建环境

    新建一个Windows窗口程序,创建好之后双击窗口会打开代码编辑页面

    首先需要把超级模块加入模块引用列表

    编写代码

    思路

    首先根据之前CE的操作步骤,我们可以先记录下每一个步骤的关键信息

    1. 首先使用CE打开了游戏进程
    2. 寻找阳光在当前游戏进程中的地址
    3. 根据阳光的地址找到偏移量
    4. 根据阳光的地址偏移量找到了游戏基址
    5. 根据游戏基址又找到了阳光的真实地址
    6. 最后生成修改器

    开始编写

    首先创建四个程序集变量,如果担心出问题可以按照我的写,接着空白处往下写就行

    根据游戏的进程名获取进程ID,从CE界面的上面不难发现进程名popcapgame1.exe

    1
    进程ID = 取进程ID (“popcapgame1.exe”)

    然后打开CE修改器就是刚才导出的脚本,看到下面红色框出来的地址00731C50,这个地址就是游戏的基址

    1
    游戏基址 = 读内存整数型 (进程ID, 十六到十 (“00731C50”))

    然后在游戏的基址之上加入5578和868的偏移量,切记先加868,然后才是5578

    1
    2
    阳光偏移 = 读内存整数型 (进程ID, 游戏基址 + 十六到十 (“868”))
    阳光基址 = 阳光偏移 + 十六到十 (“5578”)

    然后就可以设置阳光的数量了,这块的到整数 (阳光基址) + 54188是每次打开修改器修改的阳光数量,值得注意的是每次打开游戏时阳光基址都会变在加上54188就会变得更大。这里的阳光基址相对于游戏基址不会改变,因为每次打开游戏的游戏基址都并不是固定的,阳光基址是在游戏基址基础之上确定的相对的地址

    1
    写内存整数型 (进程ID, 阳光基址, 到整数 (阳光基址) + 54188)

    然后保存代码,设置窗口标题和背景图

    还可以设置程序的图标和个人信息

    调试并运行修改器

    打开游戏进入战斗界面,就可以点击红色框的运行修改器,蓝色框为停止运行修改器。如果你的修改器不能正常工作,请停止运行修改器并检查重复以上步骤

    生成修改器

    依次点击编译—独立编译,在弹出的对话框中点击确定,然后选择你要放在哪个位置下

    效果图

    ]]>
    + + + + + + + + + + + + 植物大战僵尸 + + + +
    + + + + + Minecraft BSL 汉化包 + + /2022/11/26/2022-11-26-minecraft-bsl-han-hua-bao/ + + BSL 是 Minecraft 的光影包,前两天在官网下载了最新版发现没有中文支持,然后自己就花了两天时间汉化完了。

    需要注意的

    • 只有一两处还是英文,这两个还有英文的地方我在原英文语言文件中没找到,应该是其他我不知道文件中的,不过总体上不影响使用。
    • 有几个单词翻译过来比较奇怪,保守一点就没翻译。
    • 中文语言文件位置 BSL_v8.2.zip\shaders\lang\zh_CN.lang,有翻译不准确的兄弟们自己看着改吧!

    实际效果图

    下载

    不想替换语言文件的兄弟可以直接拿去用

    请确保您具有吾爱破解的账号,本帖的权限设置旨在防止第三方网站未经告知私自获取

    汉化思路

    1. 先打开光影包找到语言文件路径 BSL_v8.2.zip\shaders\lang\en_US.lang ,从文件名不难看出 lang 应该是 language 语言的缩写,其次是 en_US 这表示 英语 美国 ,直接百度查一下 en_US 是什么意思,然后根据这个意思搜索 中文简体 的表示方法
    2. en_US.lang 文件解压出来,重命名为 zh_CN.lang,准备汉化
    3. 打开文件看到第一行是 #shaders/lang/en_us.langen_us.lang 改成对应的 zh_cn.lang
    4. 从第3行不难看出 = 前面的是代码,后面的则是要翻译的语言,并且 # 是注释后面写什么都行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #Profiles en_US
    profile.LOW=Low
    profile.MEDIUM=Medium
    profile.HIGH=High
    profile.ULTRA=Ultra

    #Profiles zh_cn
    profile.LOW=最低
    profile.MEDIUM=中等
    profile.HIGH=高

    全部翻译完就可以把文件压缩到与 en_US.lang 同一目录,导入游戏就可以用了

    我才发现我前面几行连代码都改成中文了,第一张图里

    ]]>
    + + + + + Minecraft + + + + + + + 汉化 + + + +
    + + + + + Absolute Pace 绝对和平的生存 + + /2022/10/01/2022-03-02-jue-dui-he-ping-de-sheng-cun/ + + 前言

    在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣!

    想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。

    模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性

    和平模式:没有难度、没有敌对势力生成、玩家受到非致死伤害时自动恢复生命

    怪物的凋落物:一般情况下能够获得怪物的凋落物,意味着玩家困难度设置的是“简单、中等、困难”

    模组介绍

    这个模组名为 Absolute Pace,它的作用是向游戏中添加模拟怪物被击杀时的凋落物。

    扩展模块

    扩展模块将突破绝对和平的限制,将会围绕有困难度的模式下做出修改。

    未来可能添加的扩展模块:

    • 安全的矿物世界
    • 绝对和平专用合成工作台、熔炉、烟熏炉
    • 绝对和平专用方块、箱子、发光方块
    • 更加简单的物品合成配方、合成数量
    • 生物物品掉落盒
    • 蜘蛛恐惧症模式

    现在已经具备的功能

    理论上支持1.16.5以上的版本

    和平模式下提供更多非可合成的配方

    • 牛奶合成恶魂之泪【牛奶恶魂之泪毕竟都是 白色

    恶魂之泪

    • 压缩书架【解压缩书架时不知道为什么多出来了很多个

    压缩书架

    • 三叉戟【昨晚溺尸把三叉戟的制作配方传授给了我

    三叉戟

    • 这两天熬夜和史蒂夫肝了更多的配方
      • 可以合成大部分敌对生物的凋落物
      • 降低了常用物品合成难度:比如9个书架能合成64个书架
      • 可以合成怪物蛋、各种马鞍、烟花、潜影盒、鞘翅、烈焰棒、暗影珍珠

    更多安全的维度

    安全的维度:不会自然生成敌对怪物和友善的生物,只有你才能进入的世界

    • 铁块空岛【魔改末地

    铁块空岛

    • 绿宝石大陆

    绿宝石大陆

    • 受支持的高级维度还包括:
      • 黄金块空岛
      • 钻石块大陆
      • 煤炭大陆
      • 石英石大陆

    更多的独有特殊方块

    特殊方块:具有象征性意义的方块,在未来的规划中将会添加新的工作模式至该方块【看上去很炫的样子在夜晚

    • 就是用来滑稽的

    QQ头像方块

    • 微信和博客头像的方块【目前每10s能生成一个绿宝石

    微信和博客头像的方块

    绿宝石盒

    高级燃料

    高级燃料:可燃烧更长的时间

    水桶:据说地球上水资源是古老文明最强大的能量

    岩浆桶:地球内部喷出地表的热腾腾液体

    • 水桶:提供四分钟的燃烧时间
    • 岩浆桶:提供八分钟的燃烧时间

    怪物盒:模拟怪物被干死之后掉落的物品【复现了不支持当前版本的 Tiny Farm

    预计添加所有生物的怪物盒,这其中包含了敌对生物盒和友善的生物

    • 蜘蛛盒

    蜘蛛盒

    • 烈焰人盒
    • 骷髅生物盒
    • 苦力怕生物盒
    • 粘液怪生物盒
    • 岩浆怪生物盒
    • 末影人生物盒
    • 僵尸生物盒
    • 溺尸生物盒
    • 僵尸猪人生物盒
    • 唤魔者生物盒
    • 掠夺者生物盒

    打印机:可以复制物品

    打印机:复制物品

    蜘蛛恐惧症模式

    • 当新的实体生成时会干掉距离玩家50个方块之内的所有蜘蛛

    矿石

    • 经验矿石:生成范围1-64层,掉落1236经验

    武器

    40米大刀

    • 40米大刀:你有事么?!先跟我的40米大刀聊聊吧
      • 攻击速度为6,是钻石剑攻击速度的3.75倍
      • 附魔能力提升至69
      • 对生物的攻击伤害提升至369
      • 使用次数耐久设置为了3699

    工具

    铁匠稿

    • 铁匠稿:给铁匠塞了 亿 点点钱,他就把祖传的 铁匠稿 送给了我
      • 效率为36,相当于效率Ⅴ附魔书的1.38倍
      • 附魔能力设置为了69
      • 攻击速度设置为了1
      • 对生物的攻击伤害设置为了9
      • 使用次数耐久设置为了3699

    斧子

    • 斧子:woc,你拿我斧子干啥了?!咋变成屎黄色了?
      • 效率为:36
      • 附魔能力:69
      • 攻击速度:1
      • 攻击伤害:9
      • 耐久:3699

    铲子

    • 铲子:嗯?!女朋友送了一顶好看的绿帽子,还顺手帮我做了把绿色的铲子,可真贴心呐
      • 效率:36
      • 附魔能力:69
      • 攻击速度:1
      • 伤害:4
      • 耐久:3699

    矿场

    去掉了掉落经验瓶,由采集经验矿石代替

    矿场

    • 木炭盒
    • 煤盒
    • 铜锭盒
    • 钻石盒
    • 绿宝石盒
    • 黄金盒
    • 铁盒
    • 下届碎片盒

    超大容量的箱子

    永远不会装满的箱子

    • 180存储位,比大箱子大3.33倍
    • 一个红色位删除多余物品

    超大容量箱子

    跳的超高

    • 跳跃因子是普通跳跃的4倍,高度约为17格
    • 跳跃自带1级缓降效果

    跳的超高

    高清画

    添加几幅高清画

    阳光下的狐狸

    少女

    盔甲

    真正强大的盔甲是自己的那颗心

    • 头盔
      • +33 护甲值
      • +9 盔甲韧性
      • +50 击退抗性
    • 胸甲
      • +36 护甲值
      • +9 盔甲韧性
      • +50 击退抗性
    • 护腿
      • +36 护甲值
      • +9 盔甲韧性
      • +50 击退抗性
    • 靴子
      • +69 护甲值
      • +9 盔甲韧性
      • +50 击退抗性

    盔甲外观

    压缩的煤块

    • 生成范围1-125
    • 掉落5个煤

    压缩的煤块

    回收站

    将超大容量箱子中的删除物品位单独分离出来

    回收站

    铜装备

    铜变得更加实用

    • 武器:铜剑
      • 伤害:10 【钻石剑:7】
      • 耐久:1741
    • 工具:铜斧、稿、锹
      • 伤害:8
      • 耐久:1741
    • 铜锹
      • 伤害:7
      • 耐久:1741
    • 盔甲:被一个TNT炸的只剩下1格血
      • 帽子
        • 护甲:8
        • 耐久:660
      • 上衣
        • 护甲:20
        • 耐久:960
      • 裤子
        • 护甲:24
        • 耐久:900
      • 鞋子
        • 护甲:8
        • 耐久:780

    下载地址

    目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

    ]]>
    + + + + + Mod + + + + + + + Minecraft + + + +
    + + + + + 文明六自定义领袖特色建筑 + + /2021/06/01/2021-06-01-wen-ming-liu-zi-ding-yi-ling-xiu-te-se-jian-zhu/ + + 只允许主机、特定领袖、特定文明使用特定功能的模组

    背景

    一直想实现一个在联机模式下,只允许主机,特定领袖,特定文明使用特定功能的模组.

    模组介绍

    这是一个建筑添加与德国特色工业区下,每回合建筑生产一定的资源并修改城市每回合所产出的资源.

    建筑本身加成:黄金 + 5, 食物 + 5, 文化 + 1, 科技 + 1, 信仰 + 5, 生产力 + 5

    城市资源加成:黄金 + 116, 信仰 + 116, 文化 + 5, 生产力 + 20, 食物 + 20, 科技 + 5

    建筑名称加成类型建筑每回合生产城市每回合生产
    少年的野心/DefinCivilizationTraitBuilding黄金+5+116
    食物+5+116
    文化+1+5
    科技+1+5
    信仰+5+116
    生产力+5+20

    订阅地址

    你可以直接订阅这个模组:Stem 创意工坊

    参考链接

    思路记录

    玉皇大帝开发者终于回复我了

    下午打开 Steam 时收到来自玉皇大帝模组开发者的回复.

    墨影斋主人:在你的建筑下添加一个修改器,判定建造条件是 “玩家”,如果是绑定领袖或者文明,就直接用 TraitType

    我迫不及待想要去尝试下

    打开 HBX(HBuilderX) 检索一下 TraitType 看下这玩意怎么写的.

    我在 Civilizations.xml 文件中的 第 200 行 找到了一条相关有可能的但与玉皇大帝作者说的完全不同的代码

    1
    <Row CivilizationType="CIVILIZATION_GERMANY" LeaderType="LEADER_BARBAROSSA"

    第 2657 - 2659 行有点像

    1
    2
    3
    <Row CivilizationType="CIVILIZATION_GERMANY" TraitType="TRAIT_CIVILIZATION_IMPERIAL_FREE_CITIES"/>
    <Row CivilizationType="CIVILIZATION_GERMANY" TraitType="TRAIT_CIVILIZATION_DISTRICT_HANSA"/>
    <Row CivilizationType="CIVILIZATION_GERMANY" TraitType="TRAIT_CIVILIZATION_UNIT_GERMAN_UBOAT"/>

    我没有在修改器文件Modifiers.xml文件中找到相关代码,据猜测这可能是自由添加,或许游戏源文件没有可能说明那只是一部分示例代码,并且游戏开发商不可能想象到各种可能性.

    修改器文件如何编写

    第一种可能性

    他说的是修改器Modifiers 应该不可能是 ModifierArgumentsBuildingModifiers这两个修改器.

    我没有见过单独绑定领袖的写法,在读一遍修改器文件吧Modifiers.xml

    这两千多行代码全都是一个写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <Types>
    <Row Type="MODIFIER_ALL_PLAYERS_ATTACH_MODIFIER" Kind="KIND_MODIFIER"/>
    </Types>
    <DynamicModifiers>
    <Row>
    <ModifierType>MODIFIER_ALL_PLAYERS_ATTACH_MODIFIER</ModifierType>
    <CollectionType>COLLECTION_ALL_PLAYERS</CollectionType>
    <EffectType>EFFECT_ATTACH_MODIFIER</EffectType>
    </Row>
    </DynamicModifiers>

    根据这段代码,可以猜测出一种可能性.

    • 第六行代码的修改器类型和第 2 行的类型相等,这说明引用的是建筑名称
    • 后面第7和8行不知道表达的什么意思
    • 可能需要拼接代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <Types>
    <Row Type="BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING" Kind="KIND_BUILDING" />
    </Types>
    <Modifiers>
    <Row>
    <!-- 声明建筑要修改的建筑名称 -->
    <ModifierType>BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING</ModifierType>
    <!-- 绑定玩家拥有使用当前建筑权限 -->
    <OwnerRequirementSetId>PLAYER_IS_HUMAN</OwnerRequirementSetId>
    <!-- 绑定指定的领袖使用当前建筑: 德国 -->
    <TraitType>CIVILIZATION_GERMANY</TraitType>
    </Row>
    </Modifiers>

    第 10 行完全是文明类型和区域类型的两种结合写法,先构建一下试试效果吧.

    woc难道我成功了

    试了下其他文明是否有这个建筑,很遗憾,并没有成功绑定领袖

    第二种可能性

    我打开了领袖Leaders.xml 文件,终于发现了一个比较合理的代码.

    换了好几个关键字,终于用巴巴罗萨的英译名在第 828 行找到了LEADER_BARBAROSSA

    1
    <Row LeaderType="LEADER_BARBAROSSA" TraitType="TRAIT_LEADER_HOLY_ROMAN_EMPEROR"/>

    应该是这一行了,很遗憾,也不是这个

    第三种可能性

    我阅读了玉皇大帝的领袖文件,我猜测写法应该是这样.

    1
    2
    3
    4
    5
    6
    7
    8
    <Row 
    BuildingType="BUILDING_DEFIN_CIVILIZATION_TRAIT_BUILDING"

    PrereqDistrict="DISTRICT_CITY_CENTER"
    PurchaseYield="YIELD_GOLD"
    Cost="0"
    AdvisorType="ADVISOR_GENERIC"
    TraitType="TRAIT_LEADER_HOLY_ROMAN_EMPEROR"/>

    我直接追加到了建筑名称的后面,当我使用其它领袖的时候,他成功的没有显示出来.当然不排除是我写错了,在没有写正确的情况下是不会显示的.

    我再试试德国,万一我写对了呢.对吧!

    这下才是真正成功了

    竟然真成功了,再试试一下热座模式,因为仅允许玩家使用和德国使用,看看这两个条件冲突不

    还真能用,没有任何冲突,终于完成了

    总结

    即时在不依靠国内的各种文档,视频教程,依然可以依靠阅读英文文档和官网的帮助以及自己的猜测和摸索一样能达成目标.

    虽然我最后实现功能的方式和玉皇大帝模组开发者说的答案有点出入,但还是他提醒了我,我愣是依靠一条信息找到了答案"领袖,文明,特征TraitType"才完成了我一直以来的目标.

    ]]>
    + + + + + Civ6 + + + + + + + Mod + + + +
    + + + + + 文明六自定义市中心区域建筑模组 + + /2021/02/04/2021-02-04-wen-ming-liu-zi-ding-yi-shi-zhong-xin-qu-yu-jian-zhu-mo-zu/ + + 前言

    在文明 6 的创意工坊中订阅了两个功能极为相似的模组,感觉这两个模组的代码应该差不多,于是打算分析完成这两个模组就自己写一个.

    文明六Sid Meier's Civilization VI 是一款回合制策略游戏,让玩家尝试建立起一个帝国,并接受时间的考验.玩家将创建及带领自己的文明从石器时代迈向信息时代,并成为世界的领导者.

    模组 Modification 缩写为 Mod ,简体中文翻译为模组,它是游戏的一种修改或增强程序.比如玩家向游戏中添加了游戏本身不存在的物体使游戏更加有趣.

    创意工坊Steam 游戏平台针对特定游戏提供一个 Mod 下载页面,玩家可以下载自己喜欢的 Mod 来增强自己的游戏体验

    游戏中开局你将扮演一个领袖在地图上创建你的第一座城市,城市下有很多的区域比如:学院、市场、娱乐区域、圣地区域(市民进行宗教活动的区域)、军营和港口等.

    市中心区域是城市创建后首先要建造的基础设施用来保障市民的生活.

    前段时间在 B 站看到了一个文明六的创建一个简单模组的教程,于是乎把压抑已久的想法想要实现一下.

    但是当打开模组开发工具时,我发现模组开发并不那么容易,跟着这个 B 站教程做了一遍之后遇到了许多困难.过了几个月,也就是前几天,我决定从别人的模组开始入手学习,制作一个属于自己的模组.

    本文主要讲解这个模组的设计思路,以及如何创建属于自己的模组.

    模组介绍

    这个模组名为StringOD of House ,它的作用是,向文明六任意国家的任意已创建城市的市中心区域下新增一个带有各种加成的建筑物,目前添加的建筑为 “少年的出租屋

    它的加成和模组信息如下图:

    资源加成表

    Mod 信息

    建筑信息

    下载地址

    如果你不想学习创建属于自己的 Mod 你可以直接下载并体验它【目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我】

    模组的存放路径为 %userprofile%\Documents\My Games\Sid Meier's Civilization VI\Mods,你需要解压缩把StringOD 文件夹放在 Mods 目录下.

    如果你想学习创建属于自己的 Mod 并向游戏中添加自己更多的想法,请耐心的花费十分钟的时间看完这篇文章.

    参考链接

    搭建环境

    1. Steam 中打开
    2. 点击 下面的 游戏 下拉框,勾选 工具

      库 下面的 主页 下面的

    3. 安装 Sid Meier's Civilization VI Development Tools
    4. 启动之后选择 ModBuddy

    至此,环境搭建完成

    创建你的第一个建筑模组

    这个时候你应该能看到 Start Page 的页面.

    1. 创建新的模组选择 New Mod
    2. 更改下面的模组名称 Name 最好是英文
    3. 更改模组的存储位置 Location (当然可以不更改)
    4. 选择右小角的 OK

    至此,当你看到一个英文的 Starter Project 页面时说明你的第一个模组创建成功.

    开始分析别人的模组

    模组介绍

    这是上文提到的两个相似的模组,他们都在市中心区域添加了单独的建筑:

    • God's House - Cheat Mod 针对当前城市修改了城市的产量,这其中包含:黄金,信仰,文化,科技,食物,生产力,住房以及宜居度.
    • Player Only Cheat Building 针对当前城市修改了城市的产量,这其中包含:黄金,信仰,文化,科技,食物,生产力,住房,宜居度.战略资源包含了:铁,煤,马,硝石,铝,铀.

    这两个模组在每一回合都会生产预先设置好的资源.

    寻找模组文件

    首先,得找到模组的文件路径.我发现从创意工坊下载的模组都是一串数字代替的名字.比如:

    1
    2
    1109505115
    1122081356

    这个时候我想到每个模组文件中一定会包含模组在游戏中显示的名字或模组的建筑名称.(用已知字符串搜索整个可能存在这些文件的目录下的每个文件中的内容)

    启动 HBuilderX 打开 Steam 的创意工坊目录 ..\SteamLibrary\steamapps\workshop\content\289070,然后在这个目录下的所有文件中查找 包含有 God's House 字符串的文件,根据搜索结果得到模组的目录名称 1395251854

    第二个模组的文件路径所使用的搜索字符串是游戏中显示的建筑名称,创意工坊给出的模组名称和模组中显示的名称不一样.最后根据游戏中显示的建筑名称 YoRHa_Core 在整个目录检索到的目录名称为 1956601153

    至此,得到了两个模组的文件路径.

    分析文件结构

    从文件名称猜测

    先对比一下两个模组的文件结构.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    1956601153 - YoRHa_Core
    Blu.png
    YoRHaCore.modinfo
    YoRHa_Core_Gameplay.xml
    YoRHa_Core_Icons.xml
    YoRHa_Core_Text.xml

    1395251854 - God's House
    GodsHouseCheatMod.modinfo
    NewBuilding_Gameplay.xml
    NewBuilding_Icons.xml
    NewBuilding_Text.xml

    可以看出感觉这两个模组差不多,唯一不同的是 YoRHa_Core 目录下多了一个 Blu.png 这是一个图片文件,猜测可能是背景图.

    从模组的文件名字中能大概猜测到每个文件的作用.

    • xxx_Gameplay.xml 应该是整个模组最主要的部分
    • xxx_Icons.xml 应该是该模组在游戏中显示的图标
    • xxx_Text.xml 应该是存放文本的文件,比如游戏中显示文字
    • xxx.modinfo 应该是用来存储当前模组的基本信息文件

    为了进一步确定每个文件的作用和功能就得打开文件读代码.

    打开每个文件读代码

    在这里就用 YoRHa_Core 这个 Mod 来解释和翻译.

    打开文件翻译每对标签的中文翻译.先从最外层往内层翻译.

    • xxx_Gameplay.xml 模组的最重要的游戏数据文件
    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
    <!-- 游戏数据, 整个模组的代码都得写在这个里面 -->
    <GameData>

    <!-- 建筑类名称和类型 Kind为建筑分类, 一般情况下不用改-->
    <Types>
    <Row Type="BUILDING_YORHA_CORE" Kind="KIND_BUILDING" />
    </Types>

    <!-- 建筑成本 附加属性 -->
    <Buildings>
    <!--
    BuildingType 这是建筑类型, 一般的和建筑名称可以是一样的(自己命名)
    Name 这是建筑名称
    PrereqDistrict="DISTRICT_CITY_CENTER" 表示这个模组中的建筑将添加在市中心区域下
    PurchaseYield="YIELD_FAITH" 表示当前模组的建筑建造成本必须拥有的信仰值, 实际上也需要生产力
    Cost="10" 表示需要多少信仰值才能建造当前模组中提供的建筑
    AdvisorType="ADVISOR_GENERIC" 经过翻译是顾问类型为一般, 具体不知道什么意义, 默认不用改
    -->
    <Row BuildingType="BUILDING_YORHA_CORE" />
    </Buildings>

    <!-- 建筑加成 建筑每回合生产的资源 -->
    <Building_YieldChanges>
    <!-- 建筑类型, 建筑生产资源类型为信仰, 生产数值为1 -->
    <Row BuildingType="BUILDING_YORHA_CORE" YieldType="YIELD_FAITH" YieldChange="1"/>
    </Building_YieldChanges>

    <!-- 修改类型 每个建筑可以被附加各种不同的属性需要提前声明才能被引用和赋值 -->
    <Modifiers>
    <!--
    ModifierId 建筑属性ID, 每种加成都需要一个特殊的名字
    ModifierType 建筑引用类型 这个必须调用游戏已有的建筑类型
    OwnerRequirementSetId 指定只有玩家可以使用, AI不可以
    -->
    <Row>
    <ModifierId>YORHA_CORE_FAITH</ModifierId>
    <ModifierType>MODIFIER_SINGLE_CITY_ADJUST_CITY_YIELD_MODIFIER</ModifierType>
    <OwnerRequirementSetId>PLAYER_IS_HUMAN</OwnerRequirementSetId>
    </Row>
    </Modifiers>

    <!-- 修改参数 引用修改类型并重新赋值 -->
    <ModifierArguments>
    <!-- 这里是主要修改部分, 其他部分可以不用管, 每一对 Value 标签中的数字可以随便改, 这就是城市的生产资源 -->
    <!-- 信仰 -->
    <Row>
    <!-- 引用 Modifiers 中的名称 -->
    <ModifierId>YORHA_CORE_FAITH</ModifierId>
    <!-- 生产收益 -->
    <Name>Amount</Name>
    <!-- 生产信仰 116 -->
    <Value>116</Value>
    </Row>
    <Row>
    <!-- 引用 Modifiers 中的名称 -->
    <ModifierId>YORHA_CORE_FAITH</ModifierId>
    <!-- 生产收益类型名称 -->
    <Name>YieldType</Name>
    <Value>YIELD_FAITH</Value>
    </Row>
    </ModifierArguments>

    <!-- 建筑类型引用 -->
    <BuildingModifiers>
    <!-- 建筑类型名称 和 建筑ID名称 -->
    <Row>
    <BuildingType>BUILDING_YORHA_CORE</BuildingType>
    <ModifierId>YORHA_CORE_FAITH</ModifierId>
    </Row>
    </BuildingModifiers>

    </GameData>
    • xxx_Icons.xml 这是一个图标引用文件,主要作用是针对这个模组在生产队列中给建筑提供图标
    1
    2
    3
    4
    <!-- 图标命名 引用游戏自带图标 26代表工厂建筑图标 -->
    <IconDefinitions>
    <Row />
    </IconDefinitions>
    • xxx_Text.xml 这是一个语言文件,当你的游戏主要语言更改时模组会选择一个已提供的语言进行显示,如果没有就默认显示英文(模组默认支持的语种)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- 在游戏中显示的各种名称和解释以及描述, 针对不同的语种 -->
    <LocalizedText>
    <!-- 英语 -->
    <Row Tag="LOC_BUILDING_YORHA_CORE" Language="en_US">
    <Text>Core of YoRHa Android</Text>
    </Row>
    <!-- 韩语 -->
    <Row Tag="LOC_BUILDING_YORHA_CORE" Language="ko_KR">
    <Text>요르하 안드로이드 코어</Text>
    </Row>
    <!-- 简体中文 -->
    <Row Tag="LOC_BUILDING_YORHA_CORE" Language="zh_CN">
    <Text>YoRHa的机器人核心</Text>
    </Row>
    </LocalizedText>
    • xxx.modinfo 这是一个模组创建完成且在发布时所需要填写的信息文件
    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
    <!-- 模组唯一ID -->
    <Mod id="b630189f-13ab-4eb7-a131-fc4c32625397" version="1">
    <!-- 属性 -->
    <Properties>
    <!-- 模组名称 在构建模组时名称必须为英文, 但是可以用其他文本编辑器更改成中文, 这是我后来汉化的文件 -->
    <Name>YoRHa的机器人核心</Name>
    <!-- 描述 -->
    <Description>
    只添加玩家操作建筑。
    这个mod是以下mod的集成:
    1. 上帝的房子-欺骗Mod由,僵尸马特。
    2. 由Surt Ignir Magnis设计的资源生产建筑。
    3. 由 阳光下的少年(StringOD) 汉化, 做出大量调整使得玩家在局域网模式下可以更好的发展
    </Description>
    <!-- 创建编号 -->
    <Created>1577944373</Created>
    <!-- 概要 -->
    <Teaser>
    只添加玩家操作建筑。
    这个mod是以下mod的集成:
    1. 上帝的房子-欺骗Mod由,僵尸马特。
    2. 由Surt Ignir Magnis设计的资源生产建筑。
    3. 由 阳光下的少年(StringOD) 汉化, 做出大量调整使得玩家在局域网模式下可以更好的发展
    </Teaser>
    <!-- 作者 -->
    <Authors>Unknown Enemy</Authors>
    <!-- 感谢名单 -->
    <SpecialThanks>L U N A / R \, Zombie Matt, and Surt Ignir Magnis.</SpecialThanks>
    <!-- 兼容版本号 -->
    <CompatibleVersions>1.2,2.0</CompatibleVersions>
    </Properties>
    <!-- 依赖关系 -->
    <Dependencies>
    <!-- 依赖的模组ID和名称 -->
    <Mod id="4873eb62-8ccc-4574-b784-dda455e74e68" title="Expansion: Gathering Storm" />
    </Dependencies>

    <!-- 整个模组所作出的更改将更新到游戏文件中 -->
    <InGameActions>
    <UpdateDatabase id="Gameplay">
    <File>YoRHa_Core_Gameplay.xml</File>
    </UpdateDatabase>
    <UpdateIcons id="Icons">
    <File>YoRHa_Core_Icons.xml</File>
    </UpdateIcons>
    <UpdateText id="Text">
    <File>YoRHa_Core_Text.xml</File>
    </UpdateText>
    </InGameActions>
    <!-- 需要更新的文件 -->
    <Files>
    <File>YoRHa_Core_Gameplay.xml</File>
    <File>YoRHa_Core_Icons.xml</File>
    <File>YoRHa_Core_Text.xml</File>
    </Files>
    </Mod>

    至此,每个文件的作用以及具体写法已经了解了.

    开始编写模组

    至此,我们应该已经创建完成了属于我们的第一个模组.如果你还没有完成创建,你可能需要先完成上一个步骤

    描述需求

    添加一个市中心下的建筑,建造成本为 100 金币,每回合生产3文化值,每个回合建筑生产1个点数艺术家,作家,音乐家工程师,城市每回合产出 100 黄金.

    编写模组

    打开 Solution Explorer 面板时,我们创建的项目下多了两个文件 xxx_Art.xml 直接删掉这个文件,我也不知道这个文件的作用,GettingStarted.html 这个也可以删掉这是一个帮助页面.为了学习别人的模组最好和别人保持一致.

    你可能会感到疑惑,为什么没有出现 xxx.modinfo 文件?

    我后来生成模组时发现,这是因为这个文件是在后来构建或者运行模组时编译器自动生成的.

    xxx_Gameplay.xml 文件代码编写

    直接看代码,先把英文注释翻译为中文.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <GameData>
    <!-- 几乎所有的游戏类型都应该首先添加到类型表中。 -->
    <!-- 这是引用任何类型的主要方式 -->
    <Types></Types>
    <!--在这里定义建筑。
    关于附加属性和价值的例子,请看建筑物。在这个目录下的所有文件中 /Base/Assets/Gameplay/Data/-->
    <Buildings></Buildings>
    <Building_YieldChanges></Building_YieldChanges>
    <Building_GreatPersonPoints></Building_GreatPersonPoints>
    </GameData>

    这里最有意思的就是第三个注释中提供的文件路径,打开资源浏览器找到这个目录..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\Gameplay\Data

    这个目录下有很多的文件都是英文,猜测应该是游戏中每个分类的数据文件.我是说再写模组时想要修改游戏本身的数据就得需要这些文件.还是得先翻译一下这些英文都是什么意思才行.在当前目录下打开命令行

    1
    2
    # 输出当前目录所有文件名到 CivFileList.txt
    dir /B > CivFileList.txt

    然后打开 CivFileList.txt 这个文件,拷贝所有英文到 谷歌或其他翻译词典里面翻译.得到中文翻译:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 部分翻译
    Agendas
    Barbarians
    Behavior Trees
    Beliefs
    Buildings

    议程
    野蛮人
    行为树
    信仰
    建筑

    先用代码编辑器打开一个与我们写模组最有直接关系的文件 Buildings.xml,没了解过 xml 文件的大兄弟,就可以把它当作 HTML 文件一样看待.主要关注标签的译文和标签内的代码,尤其是出现频率最高的单词比如:xxxName, xxxType 之类的.先通读这两千多行代码,主要关注注释和标签内前几行内容,得先知道这个文件整体结构怎么写的.

    到这里,已经了解完了这个文件.至少是靠着翻译读了一遍.看这个文件的目的为了能在自己的模组中添加更多的内容.

    打开别人的模组 YoRHaCore 中对应的文件 YoRHa_Core_Gameplay.xml 来复制粘贴到我的模组中然后测试运行,看效果.

    • 根据 YoRHa_Core_Gameplay.xml 复制粘贴的修改城市每回合黄金产量为 116,每回合产出伟人点数 + 1(艺术家,工程师,音乐家,作家),BUILDING_TestCase 建筑每回合产出 3 个文化值.
    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
    <?xml version="1.0" encoding="utf-8" ?>
    <GameData>
    <!-- 几乎所有的游戏类型都应该首先添加到类型表中。 -->
    <!-- 这是引用任何类型的主要方式 -->
    <Types>
    <Row Type="BUILDING_TestCase" Kind="KIND_BUILDING" />
    </Types>

    <!-- 在这里定义建筑。关于附加属性和价值的例子,请看建筑物。都在这个目录下的所有xml文件中 /Base/Assets/Gameplay/Data/-->
    <Buildings>
    <Row BuildingType="BUILDING_TestCase" />
    </Buildings>
    <Building_YieldChanges>
    <!-- 建筑每回合生产3个文化值 -->
    <Row BuildingType="BUILDING_TestCase" YieldType="YIELD_CULTURE" YieldChange="3"/>
    </Building_YieldChanges>
    <!-- 伟人点数 -->
    <Building_GreatPersonPoints>
    <!-- 艺术家 -->
    <Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_ARTIST" PointsPerTurn="1"/>
    <!-- 工程师 -->
    <Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_ENGINEER" PointsPerTurn="1"/>
    <!-- 音乐家 -->
    <Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_MUSICIAN" PointsPerTurn="1"/>
    <!-- 作家 -->
    <Row BuildingType="BUILDING_TestCase" GreatPersonClassType="GREAT_PERSON_CLASS_WRITER" PointsPerTurn="1"/>
    </Building_GreatPersonPoints>
    <Modifiers>
    <Row>
    <!-- 黄金 -->
    <ModifierId>YORHA_CORE_GOLD</ModifierId>
    <ModifierType>MODIFIER_SINGLE_CITY_ADJUST_CITY_YIELD_MODIFIER</ModifierType>
    <OwnerRequirementSetId>PLAYER_IS_HUMAN</OwnerRequirementSetId>
    </Row>
    </Modifiers>
    <ModifierArguments>
    <Row>
    <ModifierId>YORHA_CORE_GOLD</ModifierId>
    <Name>Amount</Name>
    <!-- 城市每回合产出黄金数 -->
    <Value>116</Value>
    </Row>
    <Row>
    <ModifierId>YORHA_CORE_GOLD</ModifierId>
    <Name>YieldType</Name>
    <Value>YIELD_GOLD</Value>
    </Row>
    </ModifierArguments>
    <BuildingModifiers>
    <Row>
    <BuildingType>BUILDING_YORHA_CORE</BuildingType>
    <ModifierId>YORHA_CORE_GOLD</ModifierId>
    </Row>
    </BuildingModifiers>
    </GameData>

    这其中的几行的解释已经在前面解释过了.我想在看一遍每行代码的注解

    从游戏提供的代码抽取需要的代码到模组中

    写到这里你可以看得出每对标签都对应着每一个文件中的内容,比如:..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\Gameplay\Data 目录下的文件 Buildings.xml 对应着代码中的标签 <Buildings></Buildings><Modifiers></Modifiers>

    每对标签中对应的代码其实在原始游戏文件中都已经存在,只不过我们是把我们需要的某个文件中的东西拿出来组成一个新的东西.

    比如:Buildings.xml 文件中第 380 行中建筑提供每回合的 2 科技值的生产

    1
    2
    <!-- 图书馆 生产2科技-->
    <Row BuildingType="BUILDING_LIBRARY" YieldType="YIELD_SCIENCE" YieldChange="2"/>

    ModifierArguments 标签中允许修改建筑属性参数,在Buildings.xml的第 879 行中是对金字塔奇观的建造费用调整,这个值可以是任意的某个值.

    1
    2
    3
    4
    5
    6
    <Row>
    <!-- PYRAMID ADJUST BUILDER CHARGES 金字塔调整建造费 -->
    <ModifierId>PYRAMID_ADJUST_BUILDER_CHARGES</ModifierId>
    <Name>Amount</Name>
    <Value>1</Value>
    </Row>

    示例:在我上文提到的两个模组中,建筑每回合生产都只是写了一个信仰每回合 + 1.

    1
    2
    3
    <Building_YieldChanges>
    <Row BuildingType="BUILDING_YORHA_CORE" YieldType="YIELD_FAITH" YieldChange="1"/>
    </Building_YieldChanges>

    可以根据收益类型看出,肯定不止有信仰这一种.首先打开最有可能出现的文件 Buildings.xml ,尝试找一下金币,文化,科技的加成.

    • 第 338 行,这是一个剧场奇观.每回合 + 2 的文化
    1
    <Row BuildingType="BUILDING_AMPHITHEATER" YieldType="YIELD_CULTURE" YieldChange="2"/>
    • 第 363 行,这是一个银行建筑,每回合 + 5 黄金
    1
    <Row BuildingType="BUILDING_BANK" YieldType="YIELD_GOLD" YieldChange="5"/>
    • 第 380 行,这是一个图书馆,每回合 + 2 科技值
    1
    <Row BuildingType="BUILDING_LIBRARY" YieldType="YIELD_SCIENCE" YieldChange="2"/>

    至此,这个文件下的代码就是把需要的代码添加到我们创建模组中对应的标签下,如果模组中没有对应添加的标签,那就从你抽取的那个代码的文件中把标签照着添加到你的模组中缺少的文件中.不知道你写的对不对就构建运行进游戏看看,有没有生效,如果没有就是你写的有问题.

    xxx_Icons.xml 图标文件编写

    这个文件默认是不用改的,除非你不想使用默认的图标.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- 
    For simplicity, reuse an existing icon.
    为简单起见,重用现有的图标。
    -->
    <IconDefinitions>
    <!--
    Name 是建筑显示的图标名称
    Atlas 是图标所属建筑
    Index 是图标对应所属建筑的序号
    26 是工厂的图标
    -->
    <Row />
    </IconDefinitions>

    这个目录下的所有文件是针对每个分类下的图标显示 ..\SteamLibrary\steamapps\common\Sid Meier's Civilization VI\Base\Assets\UI\Icons ,这个 Index="26" 的来历,就在这个目录下的 Icons_Buildings.xml 文件中的 第 41 行.

    1
    <Row />

    如果你想使用其他图标你只需要更改的属性,只有两个 AltasIndex 务必要保持这两个一致.

    你也可以使用其他类型的单位图标,比如不是建筑的图标.

    xxx_Text.xml 模组建筑描述文件编写

    这个文件主要存放的是建筑的语言信息,看到这里你也发现了.所有的代码都是英文没有包含一个中文.(除注释之外)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <GameData>
    <LocalizedText>
    <!--
    Tag 是建筑的英文名称, 这样方便在模组中的各个文件互相引用
    Language 是语言类型
    en_US 是(英语)美国
    zh_CN 是(简中)中国
    ko_KR 是(韩语)韩国
    Text 是这个建筑模组在游戏中显示的名称
    -->
    <Row Tag="LOC_BUILDING_GAME_STUDIO_NAME" Language="en_US">
    <Text>在游戏中显示的建筑名称</Text>
    </Row>

    <Row Tag="LOC_BUILDING_GAME_STUDIO_DESCRIPTION" Language="en_US">
    <Text>模组的描述</Text>
    </Row>

    <Row Tag="LOC_PEDIA_BUILDINGS_PAGE_BUILDING_GAME_STUDIO_CHAPTER_HISTORY_PARA_1" Language="en_US">
    <Text>模组的小故事或者说历史背景</Text>
    </Row>
    </LocalizedText>
    </GameData>

    xxx_modinfo 模组信息文件编写

    这个文件不需要自己创建 ,它会由编译器自动生成.

    找到菜单栏中的 PROJECT 中的 XXX Properties... 会打开一个界面,选择左侧的 Mod Info 然后填写你的模组名称和描述以及小故事,还有感谢的人.

    在选择左侧的 In-Game Actions 你看到了左边显示的三个Type 和三个 Id 选择第一个 Type 中的 UpdataDatabase ID 为 Gameplay ,这时你会看到右侧会显示一个大框里面显示了一个文件 NewBuilding_Gameplay.xml 文件,选中这个文件然后点击右侧的 Remove 然后在选择 Add 在弹出的新窗口中选择 File 列表框中的你模组对应的那个 xxx_Gameplay.xml 文件,然后点击 OK .然后在依次更新 Icons 文件和 Text 文件.

    完成之后保存所有文件,至此就可以构建解决方案和模组了,并测试运行了.

    生成你的模组

    1. 菜单栏中选择 BUILD然后选择 Build Solution
    2. 菜单栏中选择 BUILD 然后选择 Build xxxx

    此时,你的模组已经成功生成.你可以打开游戏并在"额外内容中启用你的本地模组".

    删除你的模组

    在调试模组中经常出现问题,所以我们不得不删除解决方案和模组重新生成.

    1. 菜单栏中选择 BUILD 然后选择 Clear Solution
    2. 菜单栏中选择 BUILD 然后选择 Clear xxxx

    这只是删除了你生成的模组文件,你的代码并没有被删除.

    至此,你已经学会了如何编写你的第一个模组.

    ]]>
    + + + + + Civ6 + + + + + + + Mod + + + +
    + + + + + 我的世界自定义配方脚本生成器 + + /2020/11/26/2020-11-26-wo-de-shi-jie-zi-ding-yi-pei-fang-jiao-ben-sheng-cheng-qi/ + + 前言

    在 Minecraft 世界中体验各式各样的探险、生存与建造乐趣!

    想在和平模式下玩生存模式,但是和平模式不生成敌对怪物,所以需要一种方式代替在困难度下生成怪物凋落物。

    模组:游戏允许玩家加入自己对游戏某些功能的修改,可以提高游戏的趣味性

    和平模式:没有难度、没有敌对势力生成、玩家受到非致死伤害时自动恢复生命

    怪物的凋落物:一般情况下能够获得怪物的凋落物,意味着玩家困难度设置的是“简单、中等、困难”

    工具介绍

    这个工具名为我的世界自定义配方脚本生成器,它的作用是向游戏中添加新的工作台配方,在和平难度下获得的物品来合成非和平难度下的物品。可以不用手写代码工具会自动生成,只需要你选择生成前后的配方即可。

    工具界面

    基于CraftTweaker手动生成配方

    CraftTweaker 允许你使用简单的脚本语言来自定义你的整合包或服务器,你可以添加或删除配方、更改配方、更改名称、更改熔炉配方、矿物词典、工具描述等。代码和说明必须是英文

    手动写代码自定义几个配方还是很容易的,如果想完整实现我的设想就需要大量的重复性操作来解决这种问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 龙蛋: 黑曜石围绕着鸡蛋, 最坚硬的蛋
    craftingTable.addShapeless("StringOD_LongDan", <item:minecraft:dragon_egg> * 1, [
    <item:minecraft:obsidian>, <item:minecraft:obsidian>, <item:minecraft:obsidian>,
    <item:minecraft:obsidian>, <item:minecraft:egg>, <item:minecraft:obsidian>,
    <item:minecraft:obsidian>, <item:minecraft:obsidian>, <item:minecraft:obsidian>]);

    // 刷怪箱: 铁栅栏围绕着箱子, 网格一样的箱子
    craftingTable.addShapeless("StringOD_ShuaGuaiXiang", <item:minecraft:spawner> * 1, [
    <item:minecraft:iron_bars>, <item:minecraft:iron_bars>, <item:minecraft:iron_bars>,
    <item:minecraft:iron_bars>, <item:minecraft:chest>, <item:minecraft:iron_bars>,
    <item:minecraft:iron_bars>, <item:minecraft:iron_bars>, <item:minecraft:iron_bars>]);

    自动生成配方工具设计思路

    AutoSpawnZScript思路

    工具界面演化

    AutoSpawnZScript - v0.1

    AutoSpawnZScript_v0.2

    AutoSpawnZScript - v0.3

    AutoSpawnZScript - v0.4

    AutoSpawnZScript - v0.5

    AutoSpawnZScript - v0.8

    AutoSpawnZScript - v0.9

    AutoSpawnZScriptEToJava - v1.0

    下载地址

    目前不直接提供下载,防止某些三方网站没有经过允许私自转发售卖,有需要可以发邮件告诉我

    ]]>
    + + + + + + + + + + + + Minecraft + + + +
    + + + + +
    diff --git a/search/index.html b/search/index.html new file mode 100644 index 0000000..fb851ed --- /dev/null +++ b/search/index.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + 搜索... | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + +
    +
    + +
    + +

    search

    + +
    + +
    +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/solution/index.html b/solution/index.html new file mode 100644 index 0000000..759a276 --- /dev/null +++ b/solution/index.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + solution | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + +
    +
    + +
    + +

    solution

    + +
    + +
    +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/Hexo/index.html b/tags/Hexo/index.html new file mode 100644 index 0000000..75c1366 --- /dev/null +++ b/tags/Hexo/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + 标签: Hexo | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     Hexo +
    +
    + +
    + +
    +
    + 2024 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/MCreator/index.html b/tags/MCreator/index.html new file mode 100644 index 0000000..ef8e95a --- /dev/null +++ b/tags/MCreator/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + 标签: MCreator | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     MCreator +
    +
    + +
    + +
    +
    + 2023 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/Minecraft/index.html b/tags/Minecraft/index.html new file mode 100644 index 0000000..a15bdfb --- /dev/null +++ b/tags/Minecraft/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + 标签: Minecraft | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     Minecraft +
    +
    + +
    + +
    +
    + 2022 +
    + +
    + +
    +
    + 2020 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/Mod/index.html b/tags/Mod/index.html new file mode 100644 index 0000000..4fced0f --- /dev/null +++ b/tags/Mod/index.html @@ -0,0 +1,460 @@ + + + + + + + + + + + + 标签: Mod | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     Mod +
    +
    + +
    + +
    +
    + 2021 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/Northgard/index.html b/tags/Northgard/index.html new file mode 100644 index 0000000..33f40ab --- /dev/null +++ b/tags/Northgard/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + 标签: Northgard | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     Northgard +
    +
    + +
    + +
    +
    + 2024 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/QNAP/index.html b/tags/QNAP/index.html new file mode 100644 index 0000000..22cc9e0 --- /dev/null +++ b/tags/QNAP/index.html @@ -0,0 +1,453 @@ + + + + + + + + + + + + 标签: QNAP | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     QNAP +
    +
    + +
    + +
    +
    + 2023 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..ecac54d --- /dev/null +++ b/tags/index.html @@ -0,0 +1,430 @@ + + + + + + + + + + + + 标签 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git "a/tags/\346\244\215\347\211\251\345\244\247\346\210\230\345\203\265\345\260\270/index.html" "b/tags/\346\244\215\347\211\251\345\244\247\346\210\230\345\203\265\345\260\270/index.html" new file mode 100644 index 0000000..cecb0cc --- /dev/null +++ "b/tags/\346\244\215\347\211\251\345\244\247\346\210\230\345\203\265\345\260\270/index.html" @@ -0,0 +1,453 @@ + + + + + + + + + + + + 标签: 植物大战僵尸 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     植物大战僵尸 +
    +
    + +
    + +
    +
    + 2023 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git "a/tags/\346\261\211\345\214\226/index.html" "b/tags/\346\261\211\345\214\226/index.html" new file mode 100644 index 0000000..b2709b2 --- /dev/null +++ "b/tags/\346\261\211\345\214\226/index.html" @@ -0,0 +1,453 @@ + + + + + + + + + + + + 标签: 汉化 | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + +
    +
    +
     汉化 +
    +
    + +
    + +
    +
    + 2022 +
    + +
    + +
    + +
    +
    + + + + +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/tools/index.html b/tools/index.html new file mode 100644 index 0000000..190f50a --- /dev/null +++ b/tools/index.html @@ -0,0 +1,585 @@ + + + + + + + + + + + + Tools | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + +
    +
    + +
    + +

    Geek Tools List


    +

    这些工具全部来自于本人手动整理, 如有侵权请联系我, 我会及时删除.

    +
    +

    uTorrent

    功能: 种子下载和打包
    优点: 免费, 没广告, 不封禁任何文件
    缺点: 国外软件在下载国内人分享的种子文件可能会无人分享流量导致下载缓慢.
    地址: 点我鸭

    +
    +

    7Z

    功能: 解压缩文件
    优点: 免费, 没广告, 更优秀的压缩算法
    地址: 点我鸭

    +
    +

    ACDSeePro

    功能: 浏览图片
    优点: 支持修改图片
    地址: 点我鸭

    +
    +

    Adobe全家桶

    地址: [点我鸭](链接: https://pan.baidu.com/s/18NCvnUQSDUinvfP5QplSYg)
    提取码: 2333

    +
    +

    Android Studio

    功能: Android程序开发
    优点: 自动下载环境 且 具有强大的IDE
    缺点: 本程序非常的庞大运行起来比较慢在HDD硬盘上
    地址: 点我鸭

    +
    +

    AndroidInventor

    功能: Android程序开发
    优点: 面向大众全年龄 且 容易上手
    缺点: 目前仅仅支持Android4.1之前的版本, 功能还比较少
    国外在线版: 点我鸭
    国内在线版: 点我鸭
    国内离线版: 资源没有上传, 有需要的详情百度

    +
    +

    bilibili链接解析

    功能: 解析在线视频地址, 直接下载视频到本地
    优点: 方便快捷, Win10版本外的客户端也能缓存视频
    地址: 点我鸭
    ps: 此链接来自于 吾爱破解 , 如果无法访问的小伙伴请登录账户查看

    +
    +

    CCleaner

    功能: 清理软件
    优点: 支持注册表清理, 自定义清理
    地址: 点我鸭
    ps: 此链接来自于 吾爱破解 , 如果无法访问的小伙伴请登录账户查看

    +
    +

    Cheat Engine 6.7

    功能: 内存修改器
    地址: 点我鸭
    密码: str

    +
    +

    cmder

    功能: Windows版本下的Linux终端模拟器
    优点: Windows下有些文件因权限问题无法删除, 可以使用这个终端删除 并且 兼容Windows指令
    地址: 点我鸭

    +
    +

    Cocos Creator

    功能: 游戏开发引擎
    优点: 2D游戏开发 且 具有强大的生态环境
    地址: 点我鸭

    +
    +

    ColorPix

    功能: 鼠标指针取色
    优点: 内存占用非常小, 方便快捷
    英文原版地址: 点我鸭
    密码: str
    ps: 英文官网找不到了, 这个文件是从本地上传的, 如果担心安全性可以拿去 腾讯哈勃 检测
    英文汉化地址: 点我鸭

    +
    +

    Dashlane

    功能: 密码管理, 支持云同步, 本地导出, 多客户端支持, 密码安全性检测, 密码生成, 自动填充网站密码
    优点: 方便, 安全
    地址: 点我鸭

    +
    +

    devc

    功能: C语言编译器
    优点: 体积小, 启动速度快
    地址: 点我鸭

    +
    +

    DiskGenius

    功能: 磁盘管理
    优点: 目前主流磁盘管理工具 且 功能强大
    地址: 点我鸭
    密码: str

    +
    +

    Dism

    功能: 系统备份恢复, 支持SSD硬盘分区以及引导 兼容HDD 硬盘备份与恢复
    优点: 相比较传统的Ghost更加强大, 因为Ghost不支持SSD硬盘的备份与恢复 且 支持制作启动盘
    地址: 点我鸭

    +
    +

    dllDownloader

    功能: 下载Windows所需的 .dll 文件
    优点: 方便快捷
    描述: Windows下载某些情况中运行程序会提示 XXX.dll文件丢失或损坏, 我们往常都需要自己去百度寻找所需的版本的dll, 现在这个工具很好的解决了这个问题.
    地址: 点我鸭
    密码: str

    +
    +

    E4A

    功能: Andorid程序开发
    优点: 中文编程
    地址: 点我鸭
    密码: 2333

    +
    +

    Eclipse

    功能: 程序开发, 支持Java, Android, C/C++, JavaScript, PHP. 还有一些我也不知道是什么
    地址: 点我鸭

    +
    +

    Everything

    功能: 文件查找
    优点: 快速搜索整个硬盘查找所需的文件
    地址: 点我鸭

    +
    +

    Fiddler

    功能: 抓包工具
    优点: 可以抓取手机的数据包
    地址: 点我鸭
    密码: 2333

    +
    +

    HBuilder

    功能: 程序开发
    优点: 一端开发, 多端生成
    地址: 点我鸭

    +
    +

    HbuilderX

    功能: 代码编辑器
    优点: 目前国内最好用的代码编辑器, 支持Markdown语法并且内置服务器预览
    地址: 点我鸭

    +
    +

    Gfie

    功能: PS级图标编辑器
    优点: 体积小, 运行快
    地址: 点我鸭
    密码: str

    +
    +

    IDA

    功能:

    +
    +

    百度百科: 交互式反汇编器专业版, 人们常称其为IDA Pro.

    +
    +

    地址: 点我鸭
    ps: 此链接来自于 吾爱破解 , 如果无法访问的小伙伴请登录账户查看

    +
    +

    Osprovision

    功能: JAR文件打包EXE
    地址: 点我鸭
    密码: 2333

    +
    +

    LibreOffice

    功能: Office办公软件
    优点: 开源免费, 支持多平台, 支持中文
    地址: 点我鸭

    +
    +

    MDict

    功能: 英文单词查询, 支持发音, 支持自定义字典, 支持自定义发音
    优点: 体积小, 方便快捷, 可扩展
    地址: 点我鸭
    密码: str
    词库: 点我鸭
    密码: 2333

    +
    +

    Microsoft Visio

    功能:

    +
    +

    Microsoft Visio是Windows操作系统下运行的流程图和矢量绘图软件

    +
    +

    地址: 点我鸭

    +
    +

    Microsoft Visual Studio

    功能: 程序开发
    地址: 点我鸭

    +
    +

    Minecraft Launcher EX

    描述: 我的世界正版客户端. 内购破解
    地址: 点我鸭
    密码: str

    +
    +

    PicPick

    功能: 截图软件, 支持修改图片, 鼠标取色, 标尺等.
    正版地址: 点我鸭
    ps: 免费的版本足够 个人用户 使用

    +
    +

    QSS

    功能: 路由器PIN码查看
    地址: 点我鸭
    密码: str

    +
    +

    QSV格式转换器

    功能: Windows下爱奇艺客户端缓存的文件 .QSV 可以通过本程序转换为可以使用的格式
    地址: 点我鸭
    密码: str

    +
    +

    OBS

    功能: 屏幕录制, 开源免费, 跨平台
    地址: 点我鸭

    +
    +

    ScreenToGif

    功能: GIF图片录制编辑软件
    地址: 点我鸭

    +
    +

    StartIsBack++

    功能: Windows10 桌面美化
    优点: 体积小, 占用内存非常小可以忽略不计
    地址: 点我鸭
    密码: str

    +
    +

    TeamViewer

    功能: 远程控制, 远程协助, 虚拟局域网
    优点: 相比较传统的远程协助软件具有足够的有势, 体积小, 占用小, 速度快, 低延迟
    地址: 点我鸭
    ps: 免费的个人版本足够 个人用户 使用

    +
    +

    TorBrower

    功能:

    +
    +

    CSDN用户 bjzhaoxiao : 用最简单的语言来描述, Tor 匿名网络是一个由位于世界各地的志愿者维护的各自的匿名网络组成的大型分布式匿名网络, 类似于分布式的 VPN,但它是免费, 开放的, 而且其规模之大超乎想象
    CSDN用户 bjzhaoxiao : Tor 匿名网络使用的核心技术是美国海军研究室开发的第三代洋葱路由器系统,其初衷用于保护政府机关的数据通信隐私,现在它被广泛应用在任何民间企业,组织,机构,以及家庭,个人的安全数据传输等场合

    +
    +

    地址: 点我鸭
    密码: str

    +
    +

    Typora

    功能: 优秀的Markdown编辑器
    优点: 简洁, 体积小, 美观
    地址: 点我鸭
    ps: 本页面就是采用 Typora 编辑器所写的

    +
    +

    UnInstaller

    功能: Windows程序卸载
    描述: Windows下程序本身自带的卸载程序并不能把自己彻底的卸载干净, Windows下的程序卸载最多也只调用了程序本身目录下的程序卸载程序, 程序中的数据, 注册表, 痕迹等都没有被卸载, 这导致了我们的硬盘存储空间一天天变小.
    优点: 目前优秀的卸载程序
    地址: 点我鸭
    密码: str

    +
    +

    UTAU

    功能:

    +
    +

    百度百科: UTAU是一款免费的歌声合成软件, 是在人力VOCALOID下诞生的产物.

    +
    +

    地址: 点我鸭
    密码: 2333

    +
    +

    VIP视频解析

    功能: 支持各大平台的VIP视频地址链接解析
    地址: 点我鸭
    密码: str

    +
    +

    VNote

    功能: Markdown编辑器
    地址: 点我鸭

    +
    +

    Mincrosoft Visual Studio Code

    功能: 代码编辑器
    优点: 可扩展, 支持代码补全
    地址: 点我鸭

    +
    +

    XYplorer

    功能: Windows文件资源管理器
    优点: 功能相比较Windows自带的资源管理器更加的强大
    缺点: 再打开未知格式文件会崩溃, 在搜索文件时有可能会崩溃.
    地址: 点我鸭
    密码: str

    +
    +

    ZeroNet

    功能:

    +
    +

    ZeroNet使用比特币加密技术和BitTorrent技术构建分散的审查抵制网络。
    用户可以将静态或动态网站发布到ZeroNet,访问者也可以选择自己为网站提供服务。只要一个同伴仍在线,网站就会保持在线状态。
    当站点由其所有者更新时,为该站点提供服务的所有节点(以前的访问者)将仅接收对站点内容所做的增量更新。
    ZeroNet附带内置SQL数据库。这使得内容繁重的站点开发变得容易。然后,数据库通过增量更新同步到托管节点。

    +
    +

    优点: 简单, 安全
    地址: 点我鸭

    +
    +

    PanDownload

    功能: 百度网盘第三方文件下载软件
    优点: 没广告, 体积小, 下载速度快
    地址:点我鸭

    +
    +

    Fish

    功能: 百度文库文章链接解析
    地址: 点我鸭
    密码: str

    +
    +

    IDM

    功能: 多线程文件下载器
    优点: 无广告, 速度快, 体积小
    地址: 点我鸭
    密码: str

    +
    +

    Huorong

    功能:

    +
    +

    病毒查杀
    防护中心
    家长控制
    扩展工具

    +
    +

    优点: 火绒是中国国内一款非常优秀的安全杀毒软件, 免费, 体积小, 无广告, 极速, 而且功能非常强大且不会像X60和XX管家诱导用户下载全家桶等流氓行为.
    地址: 点我鸭
    ps: 其实我个人都在使用这款安全软件

    +
    +

    RunBlock

    功能: 禁止应用启动
    描述: 如果你的电脑在别人使用时打开XX程序, 可以将它添加在这款程序中, 这款程序将组织程序打开已经添加在列表中的程序.
    优点: 无广告, 体积小, 内存占用低
    地址: 点我鸭
    ps: 看不到的用户请登录吾爱破解账户

    +
    +

    DupImageFinder

    功能: 图片重复查找
    概述: 相信许多小伙伴像我一样特别喜欢收藏图片, 但是图片的收藏当达到一定的量的时候, 我们无法保证图片是否重复, 再加上某些程序会将下载的图片和临时图片都会下载下来, 这就对我们收藏图片带来了巨大问题, 硬盘存储空间有将近一半的控件被重复的图片充斥着, 所以我们需要查找且对比数据删除多余的图片文件.
    优点: 体积小, 速度快, 无广告.
    地址: 点我鸭
    密码: 2333

    +
    +

    完美解码

    功能: 视频播放器
    优点: 可以编辑视频, 录制视频, 自定义操作强大
    地址: 点我鸭
    ps: 其实我本人也在用这款视频播放器

    +
    +

    Fileblender

    功能: 文件格式转换
    优点: 体积小, 占用内存小, 速度快
    地址: 点我鸭
    密码: str
    解压密码: StringOD

    +
    +

    文件批量重命名

    优点: 快速, 体积小, 内存占用小
    地址: 点我鸭
    提取密码: str
    解压密码: StringOD

    +
    +

    Support

    功能: 文字转换语音
    优点: 快速, 体积小, 内存占用小, 易上手
    地址: 点我鸭
    密码: str

    +
    +

    xiaowan

    功能: 处理视频
    优点: 快速, 体积小, 内存占用小, 易上手, 无广告, 免费
    地址: 点我鸭
    密码: str

    +
    +

    易语言

    功能: 程序开发
    优点: 中文编程
    地址: 点我鸭
    ps: 无法查看帖子的小伙伴, 请登录吾爱破解账户

    +
    +

    P2PSearcher

    功能: 种子文件搜索
    优点: 单文件便携版, 体积小, 运行内存占用可忽略不计, 无广告, 免费
    地址: 点我鸭
    密码: str

    +
    +

    Vov Sticky Notes

    功能: WIndows桌面便签
    优点: 快速, 无广告, 体积小, 内存占用可忽略不计
    地址: 点我鸭

    +
    +

    Genact

    功能: 在终端模拟器中显示五颜六色的滚动代码流
    优点: 大片级, 可以用来拍摄视频
    地址: 点我鸭
    ps: IamVeryBusy的这个程序的灵感就来自与此

    +
    +

    QQ空间相册批量下载工具

    地址: 点我鸭
    密码: str

    +
    + + +
    + +
    +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + diff --git a/type/index.html b/type/index.html new file mode 100644 index 0000000..95adc0b --- /dev/null +++ b/type/index.html @@ -0,0 +1,432 @@ + + + + + + + + + + + + type | + + 阳光下の少年 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + +
    + + + + + +
    +
    + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + + + + +
    +
    + +
    + +

    type

    + +
    + +
    +
    + + + +
    + +
    + +
    + +
    +
    + + + +
    + 由 Hexo 驱动 & 主题 Keep +
    + + + + +
    + + 本站由 提供部署服务 + +
    + + + +
    + + + + + +
    +
    +
    + +
    +
    + + + + + +
    +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + + + +
    • + +
    • +
    + +
      + + + + +
    • + +
    • + +
    • + + +
    • +
    +
    + +
    + + +
    + +
    + + + + +
    + +
    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + +