commit fadeeb89136de67fce374dc1bf9b71ed88c3c347 Author: kushidou Date: Fri Jul 17 10:20:54 2026 +0800 调整到老博客的程度 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2be6b0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +hugo.exe +themes/* +how_to_use_theme +move_old_to_now +.mimocode +.vscode \ No newline at end of file diff --git a/.hugo_build.lock b/.hugo_build.lock new file mode 100644 index 0000000..e69de29 diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..b0e1a5d --- /dev/null +++ b/Readme.md @@ -0,0 +1,9 @@ +# 个人博客主站 + +hugo = v0.164.0+Extend + +theme = Hextra + +主页提供日常站和影像站跳转功能。 + +个性化内容由mimo开发。 \ No newline at end of file diff --git a/archetypes/about-card.md b/archetypes/about-card.md new file mode 100644 index 0000000..667d1a4 --- /dev/null +++ b/archetypes/about-card.md @@ -0,0 +1,10 @@ +--- +# 主页自我介绍卡片配置 +title: '{{ replace .File.ContentBaseName "-" " " | title }}' +bio: '' +avatar: /resources/头像.jpg +links: + - name: GitHub + url: https://github.com/ + icon: github +--- diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..1508f87 --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,13 @@ +--- +title: '{{ replace .File.ContentBaseName "-" " " | title }}' +date: '{{ .Date }}' +lastmod: '{{ .Date }}' +draft: true +description: '' +authors: + - Kushidou +tags: [] +categories: [] +toc: true +comments: true +--- diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..639180b --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,8 @@ +:root { + --hextra-navbar-height: 80px; +} + +.hextra-nav-container nav { + min-height: 80px !important; + height: 80px !important; +} diff --git a/assets/resources/blog_main.jpg b/assets/resources/blog_main.jpg new file mode 100644 index 0000000..a55da1e Binary files /dev/null and b/assets/resources/blog_main.jpg differ diff --git a/assets/resources/daily_main.png b/assets/resources/daily_main.png new file mode 100644 index 0000000..71ea96c Binary files /dev/null and b/assets/resources/daily_main.png differ diff --git a/assets/resources/photograph_main.jpg b/assets/resources/photograph_main.jpg new file mode 100644 index 0000000..d8efb67 Binary files /dev/null and b/assets/resources/photograph_main.jpg differ diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..adc5e90 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,47 @@ +--- +title: 香风家的火柴盒 +layout: hextra-home +--- + +{{< hextra/hero-headline >}} + 香风家的火柴盒 +{{< /hextra/hero-headline >}} + +
+{{< hextra/hero-subtitle >}} + 技术博客 / 日记 / 摄影 +{{< /hextra/hero-subtitle >}} +
+ +{{< hextra/feature-grid >}} + {{< hextra/feature-card + title="Blog" + subtitle="技术文章与学习笔记" + icon="document-text" + link="/posts" + class="hx:aspect-auto hx:md:aspect-[1.1/1] hx:max-md:min-h-[340px]" + image="/resources/blog_main.jpg" + imageStyle="position:absolute;top:30%;left:24px;width:110%;max-width:none;opacity:0.8" + style="background: radial-gradient(ellipse at 50% 80%,rgba(59,130,246,0.15),hsla(0,0%,100%,0));" + >}} + {{< hextra/feature-card + title="Daily" + subtitle="日常生活记录" + icon="calendar" + link="/daily" + class="hx:aspect-auto hx:md:aspect-[1.1/1] hx:max-lg:min-h-[340px]" + image="/resources/daily_main.png" + imageStyle="position:absolute;top:30%;left:24px;width:110%;max-width:none;opacity:0.8" + style="background: radial-gradient(ellipse at 50% 80%,rgba(34,197,94,0.15),hsla(0,0%,100%,0));" + >}} + {{< hextra/feature-card + title="Photograph" + subtitle="摄影作品" + icon="photograph" + link="/photograph" + class="hx:aspect-auto hx:md:aspect-[1.1/1] hx:max-md:min-h-[340px]" + image="/resources/photograph_main.jpg" + imageStyle="position:absolute;top:30%;left:24px;width:110%;max-width:none;opacity:0.8" + style="background: radial-gradient(ellipse at 50% 80%,rgba(168,85,247,0.15),hsla(0,0%,100%,0));" + >}} +{{< /hextra/feature-grid >}} diff --git a/content/about-card.md b/content/about-card.md new file mode 100644 index 0000000..051d657 --- /dev/null +++ b/content/about-card.md @@ -0,0 +1,12 @@ +--- +title: Kushidou +bio: 极客 / 嵌入式 / 会拍照 / 二次元 +avatar: /resources/头像.jpg +links: + - name: GitHub + url: https://github.com/ + icon: github + - name: Bilibili + url: https://github.com/ + icon: bilibili +--- diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..39cefcb --- /dev/null +++ b/content/about.md @@ -0,0 +1,43 @@ +--- +title: 关于我 +toc: false +--- + +
+
+ Kushidou +
+
+

Kushidou

+

什么都学,却什么都不精通的一个小白 :P

+
+ + + GitHub + + + + Bilibili + +
+
+
+ +--- + +## 自我介绍 + + + +我是一名嵌入式工程师,平时喜欢折腾各种开源软件和硬件。这个博客用来记录技术探索过程中的笔记和心得,也包括一些日常生活和摄影的分享。 + +--- + +## 技能与方向 + + + +- **嵌入式开发** — Linux 系统开发、驱动调试、交叉编译 +- **开源软件** — Docker、Hugo、各类 CLI 工具 +- **网络与服务** — 自建 NAS、内网穿透、家庭服务器 +- **摄影** — 风光 / 街拍 / 生活记录 diff --git a/content/posts/.mdpic/image-20210526173013413.png b/content/posts/.mdpic/image-20210526173013413.png new file mode 100644 index 0000000..01075a0 Binary files /dev/null and b/content/posts/.mdpic/image-20210526173013413.png differ diff --git a/content/posts/.mdpic/image-20210719133419073.png b/content/posts/.mdpic/image-20210719133419073.png new file mode 100644 index 0000000..59185ee Binary files /dev/null and b/content/posts/.mdpic/image-20210719133419073.png differ diff --git a/content/posts/.mdpic/image-20211019094048011.png b/content/posts/.mdpic/image-20211019094048011.png new file mode 100644 index 0000000..7853b4f Binary files /dev/null and b/content/posts/.mdpic/image-20211019094048011.png differ diff --git a/content/posts/210218-TeamspeakWithDocker.md b/content/posts/210218-TeamspeakWithDocker.md new file mode 100644 index 0000000..f9a742c --- /dev/null +++ b/content/posts/210218-TeamspeakWithDocker.md @@ -0,0 +1,62 @@ +--- +title: 使用Docker安装TeamSpeak +date: 2021-02-18T00:00:00+08:00 +lastmod: 2021-02-18T00:00:00+08:00 +draft: false +description: "install teamspeak with docker" +authors: + - Kushidou +tags: + - teamspeak + - Linux + - Tools + - Docker +categories: [] +toc: true +comments: true +--- + +TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。 + + + +## Teamspeak + +TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。 + +所以,选择docker安装ts就是一个很不错的选择。 + +## Docker及其安装 + +docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。 + +我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker: + +```shell +sudo yum install docker #安装docker程序,会自动选择合适的 +sudo systemctl start docker #启动docker服务 +sudo systemctl enable docker #允许docker开机自启 +``` + +## Docker安装TS + +`docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak` + +直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。 + +## 端口 + +从指令可以看出,docker需要使用到**9987,10011,30033**三个端口,所以需要到服务器后台开启防火墙上对应端口。 + +## 管理员密钥 + +TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。 + +`docker ps -a`查找所有容器。 + +`docker logs -f [CONTATINER ID]`查看log,找到token即可。客户端设置之后再说。 + +密钥只会在第一次运行时打印,注意保存。 + + +*参考:[https://woolio.cn/683/](https://woolio.cn/683/)* diff --git a/content/posts/210311-SerialModeandUDEVinLinux.md b/content/posts/210311-SerialModeandUDEVinLinux.md new file mode 100644 index 0000000..f7cd689 --- /dev/null +++ b/content/posts/210311-SerialModeandUDEVinLinux.md @@ -0,0 +1,100 @@ +--- +title: Linux串口和udev理解 +date: 2021-03-11T00:00:00+08:00 +lastmod: 2021-03-11T00:00:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux + - 串口 +categories: [] +toc: true +comments: true +--- + +配置串口权限,顺便看一下linux的udev规则。 + + + +## 串口的问题 + +在现场开发的同事反映UOS工控机没有配置串口,每次都需要sudo才能使用。(明明在家里你们都会自己配置好环境与权限,为什么到了现场反而不会了需要我提供协助呢?) + +我之前没考虑过这个问题,认为串口就是得root权限操作才安全。但是既然提出了需求,就赶紧百度并测试一下吧。 + +## 赋予普通用户串口权限的两种方法 + +在百度上,给普通用户串口权限有两种主流方法(可能2会了,Google上不去),一种是将用户放入特殊的用户组,从而获得权限。另一种是加入udev规则,在串口插入时自动更改权限。 + +我个人更倾向于使用第二种方案,因为公司中不同的人装系统就可能起不同的名字,之后进入生产环境可能又会起新的名字,所以采用第二种设置规则的办法,便于之后制作恢复镜像和一键部署。 + +## xxx.rules + +`sudo vi /etc/udev/rules.d/70-tty-usb-rules`,写入以下内容: + +```shell +KERNEL=="ttyS*",MODE="0666" +KERNEL=="ttyUSB*",MODE="0666" +``` + +这一段的意思是匹配所有ttyS和ttyUSB,插入时都赋予0666的权限,即所有用户都可读可写。重新加载一下设备,就可以按规则配置权限进行操作了。 + +## udev规则 + +udev是什么?udev是Linux下的设备文件管理方式,运行在用户侧(devfs在内核侧管理设备文件)。有了udev,`/dev`目录下的文件就都是真实存在的已连接的设备了。 + +udev的规则都保存在`/etc/udev/rules.d`目录下,文件名的格式为`数字-名称.rules`,数字表示加载顺序,一般是从小往大加载;名称按功能自定义,最后以.rules结尾。这些规则会在设备接入时生效。 + +规则文件按行执行,每行分为两个部分:匹配和赋值,每个参数使用逗号,分割。 + +> (这段复制自引用2,仅供参考)。 +> **1、udev 规则的所有操作符** +> **==**  比较键、值,若等于,则该条件满足; +> **!=**  比较键、值,若不等于,则该条件满足; +> **=**   对一个键赋值; +> **+=**  为一个表示多个条目的键赋值。 +> **:=**  对一个键赋值,并拒绝之后所有对该键的改动,防止后面的规则文件对该键重赋值。 +> +> **2、udev 规则的匹配键** +> **ACTION**        事件 (uevent) 的行为,例如:add、remove。 +> **KERNEL**         内核设备名称,例如:sda, cdrom。 +> **DEVPATH**        设备的 devpath 路径。 +> **SUBSYSTEM**       设备的子系统名称,例如:sda 的子系统为 block。 +> **BUS**           设备在 devpath 里的总线名称,例如:usb。 +> **DRIVER**         设备在 devpath 里的设备驱动名称,例如:ide-cdrom。 +> **ID**            设备在 devpath 里的识别号。 +> **SYSFS{filename}**    设备的 devpath 路径下,设备的属性文件“filename”里的内容。 +> **ENV{key}**       环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。 +> **PROGRAM**        调用外部命令。 +> **RESULT**         外部命令 PROGRAM 的返回结果。 +> +> **3、udev 的重要赋值键** +> **NAME**           /dev目录下显示的文件名重命名 +> **SYMLINK**          为 /dev下文件进行符号链接,防止覆盖系统默认产生的文件 +> **OWNER, GROUP, MODE**  为设备设定权限。 +> **ENV{key}**         导入一个环境变量。 +> +> **4、udev 的值和可调用的替换操作符** +> Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符: +> **$kernel, %k**        设备的内核设备名称,例如:sda、cdrom。 +> **$number, %n**        设备的内核号码,例如:sda3 的内核号码是 3。 +> **$devpath, %p**       设备的 devpath路径。 +> **$id, %b**          设备在 devpath里的 ID 号。 +> **$sysfs{file}, %s{file}**   设备的 sysfs里 file 的内容。其实就是设备的属性值。 +> **$env{key}, %E{key}**   一个环境变量的值。 +> **$major, %M**        设备的 major 号。 +> **$minor %m**        设备的 minor 号。 +> **$result, %c**        PROGRAM 返回的结果。 +> **$parent, %P**       父设备的设备文件名。 +> **$root, %r**         udev_root的值,默认是 /dev/。 +> **$tempnode, %N**      临时设备名。 +> **%%**            符号 % 本身。 +> **\$\$**             符号 $ 本身。 + + + +## 参考 +[1]https://www.cnblogs.com/qixianyu/p/6869744.html +[2]https://www.cnblogs.com/cslunatic/p/3171837.html diff --git a/content/posts/210311-multiTouchscreeninLinux.md b/content/posts/210311-multiTouchscreeninLinux.md new file mode 100644 index 0000000..4a6c4ec --- /dev/null +++ b/content/posts/210311-multiTouchscreeninLinux.md @@ -0,0 +1,117 @@ +--- +title: Linux多显示器触屏调试与暂行解决方案 +date: 2021-03-11T00:00:00+08:00 +lastmod: 2021-09-14T00:00:00+08:00 +draft: false +description: "Linux with multi touch screen" +authors: + - Kushidou +tags: + - Linux + - Deepin/UOS + - 触摸屏 +categories: [] +toc: true +comments: true +--- + +由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。 + + + +*2021-03-22更新部分内容* +*2021-04-28再次更新内容* + +**2021-09-14 文章已有新版本,请访问《[Linux处理多触屏的终极解决方案](https://www.small09.top/posts/210824-linuxtouchscreenfin/)》** + +## 问题来源 + +最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为 + +`xinput map-to-output $触屏设备id $显示屏名称` + +> 2021-04-28更新 +> +> `xinput map-to-output`指令支持触摸屏名称作为参数,比如我通过`xinput`指令看到有一个触摸屏的设备名称为`ILITEK XXXX`,那么我映射的时候直接输入 +> > `xinput map-to-output "ILITEK XXX" HDMI-1` +> + +## 尝试解决 +- 首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。 +- ~~然后`xinput list-props $id`查看设备的VID,这个在后续自动化处理有用。~~ +- `xrandr`查看`$显示器名称` + +## 自动化处理 + +```bash +# 2021-04-28更新: +# 本部分后面的都可以不看了! +xinput map-to-output "DEVICE_NAME" DISPLAY +# 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, +# 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~ +``` + +**仅作归档!下面这些方法过于繁琐!** + +利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。`bash this.sh VID1 VID2 ScreenID` + +```shell +#!/bin/bash +# 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。 +# 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。 +MYID_FIRST=$1 +MYID_SECOND=$2 +OUT_PUT=$3 + +for I in $(xinput list --id-only) +do +#[1] + CUR_FIRST=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}') + CUR_SECOND=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}') + if [ "$MYID_FIRST" == "$CUR_FIRST" ] && [ "$MYID_SECOND" == "$CUR_SECOND" ]; + then + MY_INDEX=$I + break; + fi +done + +xinput map-to-output $MY_INDEX $OUT_PUT + +#./this.sh VID VID HDMI2 +#./this.sh VID VID HDMI1 +``` + +```shell +#!/bin/bash +# 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。 +TOUCH_ID=$(xinput | grep -iw DEVICE_NAME) +TOUCH_ID=$(echo ${TOUCH_ID#*id=}) +TOUCH_ID=$(echo ${TOUCH_ID%% *}) +xinput map-to-output $TOUCH_ID SCREEN_NAME +``` + + + +此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),~~使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id~~。 + +经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在`~/.bashrc`中,这样只需要打开一次终端,就可以实现屏幕重新映射。 + +## 后续问题 + +1. 当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。 +2. 当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数 +3. 如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。 + +## 触屏校准(如果需要) + +网上说的利用校准来区分多触摸屏的方法是**完全错误**的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录) + +- `sudo apt install xinput-calibrator`,需要更新源 +- `sudo xinput_calibrator` +- 将校准信息保存到`/usr/share/X11/xorg.conf.d/10-evdev.conf`的"Driver evdev”下面一行,重启生效。 + +## 参考: +[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077 diff --git a/content/posts/210328-LinuxChangeMacIP.md b/content/posts/210328-LinuxChangeMacIP.md new file mode 100644 index 0000000..d3e3952 --- /dev/null +++ b/content/posts/210328-LinuxChangeMacIP.md @@ -0,0 +1,76 @@ +--- +title: Linux修改mac地址 +date: 2021-03-28T00:00:00+08:00 +lastmod: 2021-03-28T00:00:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux + - 网络 +categories: [] +toc: true +comments: true +--- + +当Linux的图形界面没有或失效的时候,该如何更改mac地址? + + + +# Linux调整网卡MAC地址(指令) +有的时候需要使用欺骗的方法为电脑设置新的mac,比如存在上网网卡限制,或者想要隐藏自己的真实mac地址。这里列举三种修改mac的方法,适用于Debian系的Linux,其他的应该类似。 + +## 方法一:ifconfig + +```bash +# ifconfig 查看网卡名称$NAME +# 需要sudo权限 +ifconfig $NAME down +ifconfig $NAME hw ether $NEW_MAC +ifconfig $NAME up +# 使用ifconfig检查有没有改变 +``` + +## 方法二:macchanger + +`sudo apt install macchanger`,安装过程提示是否自启,随便。 + +```bash +ifconfig $NAME down +macchanger -m $NEW_MAC $NAME +ifconfig $NAME up +``` + +ps:macchanger可以附加多种参数,如-m指定mac地址,-s查看状态,-r随机地址 + +## 备注: +以上两种方法是软件层面的修改,不会对硬件造成损伤,重启之后就能恢复如初。如果是要长期有效,可以把这些命令写入脚本开机自启。除了这两个,还有另一种软修改的方法,因为我总是提示失败就遗忘了。 + +有些方法会提到修改/etc目录下的文件实现MAC地址的修改,但是我一个文件都没找到(UOS),这个是因系统而异的,不同的发行版可以选择性的编译这些文件,就会造成有人有有人没有,因为没有实践,所以就不写在这里了。 + + +## 方法三:ethtool(慎用) + +以上方法都失效,可以直接修改网卡的物理MAC地址,修改后无法恢复,慎用 + +```bash +# 首先检查mac是否支持修改 +ethtool -i $NAME +# 如果 supports-eeprom-access: no,大概率不支持修改 + +# 查看网卡的mac寄存器 +ethtool -e $NAME +# 如果无法读取,大概率不支持修改 + +# 修改mac,按字节修改 +ethtool -E $NAME offset 0 value 0xAA +ethtool -E $NAME offset 1 value 0xBB +ethtool -E $NAME offset 2 value 0xCC +...共六行 +# 如果修改无效,则驱动不支持修改 +``` + +## 如果以上方法都无法修改,则建议申请新的ip。。。 + +*文章首发于香风家的小别墅,转载请携带出处。* diff --git a/content/posts/210420-LinuxMoreThan4SerialPort.md b/content/posts/210420-LinuxMoreThan4SerialPort.md new file mode 100644 index 0000000..af51f24 --- /dev/null +++ b/content/posts/210420-LinuxMoreThan4SerialPort.md @@ -0,0 +1,65 @@ +--- +title: Linux启用四个以上的串口 +date: 2021-04-20T00:00:00+08:00 +lastmod: 2021-04-20T00:00:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux + - 串口 +categories: [] +toc: true +comments: true +--- + +工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。 + + + +## 先说结论 + +```shell +# 1.修改开机grub文件 +sudo vi /etc/default/grub +# 2.在"GRUB_CMDLINE_LINUX_DEFAULT="一行的结尾空格,添加内容 + 8250.nr_uarts=8 +# 3.更新开机引导并重启 +sudo update-grub +``` + +## 再说细节 + +给带有多串口的工控机安装国产Linux操作系统UOS,发现`/dev/ttyS`只有四个,而且一个都不通,其实这是Linux 的通病,如果发行商没有提前配置,我们只能看到四个不能用的串口。但是工控机都有8-16个串口,不得不想办法让他们能够正常使用,而许多串口芯片只需要基本的驱动就能运行,也不提供专有驱动的源代码编译(一般安装专用驱动后,串口名称会改变,比如xr17v358芯片的驱动安装后名称变成ttyXR0),我们只有从linux本身下手了。 + +经过多番查找资料,虽然不知道为什么,但是我们应该修改`/etc/default/grub`文件,其中有一行是这样的: + +```shell +GRUB_CMDLINE_LINUX_DEFAULT="这里面 的内容 先不管 它" +``` + +我看了UOS(含Deepin)、Ubuntu和服务器的Centos7都有,在这一行的最后添加`8250.nr_uart=32`,其中数字<=32都行,效果如下: + +```shell +GRUB_CMDLINE_LINUX_DEFAULT="这里面 的内容 先不管 它 8250.nr_uarts=32" +``` + +然后更新一下系统的引导 + +```shell +sudo update-gurb +reboot +``` + +重启完了你就能看到更多的串口啦!配合[修改udev规则](https://www.small09.top/?p=24),你就可以用普通用户的权限使用串口通信了。但是请注意其他隐患,比如这个芯片配合基本驱动只能完成基础的收发功能,流控的RTS、CTS等还是需要专用驱动实现的! + +## 最后吐槽 + +为了串口问题我找了一下午的资料,最后才在外网找到了一个能用的。国内的教程都是说要修改`/boot/gurb/gurb2`,宝友,这可不兴改啊,这个改坏了系统可就没了啊!这些文件普通用户连浏览权限都没就可见其重要性。 + +## 参考: + +https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/ + +核心步骤就参考了这个,在Linux方面大家还是多用google,多用英文去搜索吧! diff --git a/content/posts/210429-DeepinAutoRun.md b/content/posts/210429-DeepinAutoRun.md new file mode 100644 index 0000000..c245e6c --- /dev/null +++ b/content/posts/210429-DeepinAutoRun.md @@ -0,0 +1,54 @@ +--- +title: deepin和UOS,快捷方式,开机自启动 +date: 2021-04-29T00:00:00+08:00 +lastmod: 2021-09-14T00:00:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux + - Deepin/UOS +categories: [] +toc: true +comments: true +--- + +修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。 + + + +## 桌面快捷方式 + +deepin的桌面快捷方式是以`*.desktop`文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容 + +``` +[Desktop Entry] +Name=ddd +Exec="/usr/sbin/xx.sh" +Type=Application +``` + +其中`Desktop Entry`表示声明该文件为快捷方式文件。`Name`是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。`Exec`是程序的启动脚本,或者启动指令。`Type`是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。 + +把这个文件放在`~/Desktop`,就可以看到桌面上多了一个快捷方式了。 + +## 开始菜单 + +`sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications`,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。 + +## 开机自动执行 + +### UOS/Deepin和Ubuntu + +`cp ~/Desktop/刚刚的.desktop ~/.config/autostart` + +需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(`lightdm.service`)执行,导致error从而无法进入桌面,将脚本放进`/etc/profile.d`之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。 + +经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在`~/.config/`下手动创建一个`autostart`目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。 + +### 麒麟和其他使用X11桌面的Linux + +本条目包含UOS和Ubuntu、deepin系统 + +自启动目录为`/etc/xdg/autostart`,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。 diff --git a/content/posts/210630-SudowithoutPasswd.md b/content/posts/210630-SudowithoutPasswd.md new file mode 100644 index 0000000..668e341 --- /dev/null +++ b/content/posts/210630-SudowithoutPasswd.md @@ -0,0 +1,58 @@ +--- +title: 免密码使用sudo +date: 2021-06-30T15:14:40+08:00 +lastmod: 2021-07-13T08:43:40+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux +categories: [] +toc: true +comments: true +--- + +记录一种可以免密码使用sudo的方法。 + + + +每次使用sudo提权,常常要输密码太烦了,所以想给自己赋予免密码的特权。 + +1. 首先登录root用户: `sudo -i` + +2. 这个操作的核心文件是`/etc/sudoers`,可以看到这个文件是只读的,要修改他需要先赋予写入权限。 + +```bash +chmod 666 /etc/sudoers +``` + +![文件是只读权限](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210705092601.png) + +3. 使用vim修改文件,`vi /etc/sudoers`,把当前用户和用户组加入,如图 + +```bash +# 表示为该用户免密码 +yourname ALL=(ALL:ALL) NOPASSWD:ALL +# 表示为该用户组免密码 +%yourname ALL=(ALL:ALL) NOPASSWD:ALL +# 不用严格按照次形式,与你的这个文件中root的格式对应即可。 +``` + +4. `:wq`保存,把sodoers改回只读 + +```bash +chmod 440 /etc/sudoers +``` + +![文件修改的内容](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210705092637.png) + +5. 完成\~\~\~ 把终端关掉再次打开就应该能看到效果了。 + +> **PS. 不可使用sudo chmod** +> +> 一定要直接sudo -i进入root用户,如果使用sudo chmod以普通用户的身份修改sudoers文件的话,改完会发现无法使用sudo指令把权限改回去了,提示“任何人可读写,无法找到有效的sudoers”。其实还有一种更加安全的操作,但是那个编辑器用起来没有vim顺手, +> +> + +参考:https://blog.csdn.net/wxqee/article/details/72718869 diff --git a/content/posts/210719-GpuInfoinLinux.md b/content/posts/210719-GpuInfoinLinux.md new file mode 100644 index 0000000..f7f56a1 --- /dev/null +++ b/content/posts/210719-GpuInfoinLinux.md @@ -0,0 +1,56 @@ +--- +title: Linux中获得AMD显卡的状态信息 +date: 2021-07-19T11:00:07+08:00 +lastmod: 2021-07-19T13:35:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux + - 显卡 +categories: [] +toc: true +comments: true +--- + +在Linux下获得AMD GPU的状态和温度信息。 + + + +在Linux下如果要查看Nvidia GPU的状态信息,可以使用Nvidia驱动自带的工具。而AMD显卡一般都是使用的开源驱动,这种情况下如何获得显卡的状态和占用呢? + +*文章部分图片之后再附* + +## 1、获取GPU信息 + +如需获得GPU的信息(型号、驱动)等,如果系统提供类似*“设备管理器”*的工具可以打开查看。或者就是用`lspci`指令查看。 + +```bash +# 获得所有PCIe设备,在其中找到AMD GPU +[exam@ple]:~$ lspci +00:02.0 VGA compatible controller: xxxxxxxx + +[exam@ple]:~$ lspci -v -s 00:02.0 +# 这里就会显示显卡的具体信息了,包括型号、驱动等,之后补充 +``` + +## 2、GPU占用率 + +显卡占用率是一个能反映负载的直观指标,AMD显卡可以通过安装`radeontop`软件查看。 + +![radeontop界面](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210719133426.png) + +最上面是GPU管道使用,大概类似于总体使用率的东西;最下面是内存使用率,使用sudo权限能看到更多信息。其他项目可以到radeontop的[Github页面](https://github.com/clbr/radeontop)查看。 + +## 3、温度 +特指GPU温度。查阅这篇文章所涉及的知识点的最初目的,也是确认机箱过热是否是GPU的问题。 + +GPU温度使用第三方软件查看,并使其实时更新。查看温度使用`sensors`指令,通过安装`lm-sensors`实现。执行sensors时只会显示一次,我们可以利用watch指令定时刷新温度显示: + +```bash +watch -n 2 sensors +# 每隔2秒执行sensors指令显示结果 +``` + +!\[watch执行结果\](图片占位) diff --git a/content/posts/210824-LinuxTouchScreenFIN.md b/content/posts/210824-LinuxTouchScreenFIN.md new file mode 100644 index 0000000..152aacd --- /dev/null +++ b/content/posts/210824-LinuxTouchScreenFIN.md @@ -0,0 +1,132 @@ +--- +title: Linux处理多触屏的终极解决方案 +date: 2021-08-24T00:00:00+08:00 +lastmod: 2021-09-14T00:00:00+08:00 +draft: false +description: "Linux with mutli touch screen" +authors: + - Kushidou +tags: + - Linux + - Deepin/UOS + - 触摸屏 +categories: [] +toc: true +comments: true +--- + +一般Linux操作系统接入多个触摸屏的最终解! + + + +从[21年3月中旬开始](https://www.small09.top/posts/210311-multitouchscreeninlinux/),我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。 + +*重要:* 本文全文基于软件`xinput` + +*更重要:* 如简介所说,仍有一种方法无法对应,具体为`【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】`,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。 + +## 0、核心:xinput + +xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的) + +> xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级 +> +> xinput list --id-only : 仅仅列出设备ID(序号,每次开机都可能改变) +> +> xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕 +> +> xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。 + +## 1、简约:两个完全不同的触摸显示器 + +`xinput map-to-output "$DEVICE NAME A" PORT-A` + +这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。 + +首先通过xinput查看所有的输入设备: + +```shell +~$ xinput +┌ Virtual core pointer id=2 [master pointer (3)] +| ILITEK TLITEK-TP id=9 [slave pointer (2)] +| ILITEK Multi-Touch id=7 [slave pointer (2)] +(下略) +``` + +可以看到`ILITEK ILITEK-TP`和`ILITEK Multi-Touch`显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射: + +`xinput map-to-output "ILITEK ILITEK-TP" HDMI-0` + +`xinput map-to-output "ILITEK Multi-Touch" HDMI-1` + +这种方式的要点是1.必须带英文`""`,2.引号内名称须完整。`map-to-output`所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。 + +## 2、复杂但通用:VID:PID不同的显示器 + +不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。 + +通过`xinput list-props [id]`可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。`list-props`的结果示例如下: + +```shell +(后续再补) +``` + +其中`Device Product ID`一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子: + +```bash +#!/bin/bash +# 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息: +# 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段 +VIDA=222a +PIDA=0001 +PORTA=VGA-0 + +# 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行 +# 或者这里直接填写Device Product ID里面的结果,先VID后PID。 +# 字母O,不是数字0,多屏重复本段 +VIDA_O=$((16#$VIDA)) +PIDA_O=$((16#$PIDA)) + +# =========自动处理步骤,请勿随意修改========== +for I in $(xinput list --id-only) +do +# 根据设备ID取其序号 + CUR_FIRST=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}') + CUR_SECOND=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}') +# 3、屏映射,多屏重复本段 + if [ "$VIDA_O" == "$CUR_FIRST" ] && [ "$PIDA_O" == "$CUR_SECOND" ]; + then + xinput map-to-output $I $PORTA + fi +done +exit 0 +``` + +这个脚本使用`xinput list --id-only`仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用`map-to-output`进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。 + +## 3、[2]的进阶版 + +前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。 + +在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是`pyudev`库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装`python3+pyudev`,并且使用desktop文件在开机时启动,即可。 + +```python +#!/usr/bin/python3 +# -- coding: UTF-8 -- +# -- Author: Blues -- + +import pyudev +import os + +context = pyudev.Context() +monitor = pyudev.Monitor.from_netlink(context) +monitor.filter_by(subsystem='hid') +for device in iter(monitor.poll, None): + if device.driver != "hid-multitouch": + continue + if device.action == "add" or device.action == "change": + os.system("/var/my-touchscreen/remap-screen") + +``` + +上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照*设备名、VID、UID、物理接口*四大特征,自动映射到对应的显示屏上了。 diff --git a/content/posts/210914-LinuxBackupDD.md b/content/posts/210914-LinuxBackupDD.md new file mode 100644 index 0000000..9067c46 --- /dev/null +++ b/content/posts/210914-LinuxBackupDD.md @@ -0,0 +1,60 @@ +--- +title: 使用dd指令备份和恢复Linux系统(硬盘级) +date: 2021-09-14T13:51:16+08:00 +lastmod: 2021-09-14T13:51:16+08:00 +draft: false +description: "Backup Linux with dd command." +authors: + - Kushidou +tags: + - Linux + - Deepin/UOS +categories: [] +toc: true +comments: true +--- + +害,谁不是个DD (誰でも 大好き)了呢! + +但是这玩意儿太慢了。。。 + + + +## 起因 + +使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。 + +## DD说明 + +dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。 + +dd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。 + +实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。 + +## DD备份 + +```bash +dd if=/dev/sda of=/dev/sdb => 备份整个磁盘到另外一个磁盘 +dd if=/dev/sdb of=sda.img => 备份整个磁盘为某个文件 +dd if=/dev/sda | gzip > sda.img => 备份并且压缩 +dd if=/dev/sda bs=1M | gzip > sda.img => 指定块大小,备份并压缩(据说能提速) +``` + +## DD复原 + +```bash +dd if=/dev/sdb of=/dev/sda =>从另一个磁盘恢复回来 +gzip -dc sda.img | dd of=/dev/sda =>从压缩的文件恢复出来 +gzip -dc sda.img | dd of=/dev/sda bs=1M =>前面指定了块大小,这里也需要 +``` + +## 参考: + +本文参考(抄)了以下内容: + +[Linux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像](http://vpsxyz.com/archives/87) + +[linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)](https://blog.csdn.net/weixin_33971130/article/details/85662628) + +还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ ) diff --git a/content/posts/210914-mdadm.md b/content/posts/210914-mdadm.md new file mode 100644 index 0000000..7cd61ce --- /dev/null +++ b/content/posts/210914-mdadm.md @@ -0,0 +1,48 @@ +--- +title: Linux恢复操作系统后mdadm导致开机变慢的方法 +date: 2021-09-14T15:16:13+08:00 +lastmod: 2021-09-14T15:16:13+08:00 +draft: false +description: "mdadm no arrays found in config file or automatically" +authors: + - Kushidou +tags: + - Linux +categories: [] +toc: true +comments: true +--- + +解决Linux系统异地恢复后开机mdadm报错导致开机时间变长。 + + + +## 现象 + +使用deepin-clone恢复UOS镜像后,开机会不断跳一条日志: + +```bash +mdadm: no arrays found in config file or automatically +``` + +大概蹦个15条后,系统才真正开机。mdadm好像是一个磁盘raid管理工具,当磁盘变化后其配置也不适配了,必须等到超时才会加载默认配置。那么我们就为其生成一个新的配置即可解决问题。 + +## 方案 + +步骤一:删除mdadm.conf + +```bash +sudo rm /etc/mdadm/mdadm.conf +``` + +步骤二:生成新的mdadm.conf + +```bash +sudo update-initramfs -u +``` + +ps:如果是swap分区问题导致等待90s开机慢,不归这儿管。 + +## 从这儿抄的: + +[W: mdadm: no arrays found in config file or automatically导致开机变慢的解决方案(deepin)](https://blog.csdn.net/KuXiaoQuShiHuai/article/details/99686620) diff --git a/content/posts/210928-rpm_package.md b/content/posts/210928-rpm_package.md new file mode 100644 index 0000000..6aa2406 --- /dev/null +++ b/content/posts/210928-rpm_package.md @@ -0,0 +1,19 @@ +--- +title: RPM安装包制作方法 +date: 2021-09-28T11:06:54+08:00 +lastmod: 2021-09-28T11:06:54+08:00 +draft: false +description: "制作一个rpm安装包。" +authors: + - Kushidou +tags: [] +categories: [] +toc: true +comments: true +--- + +Linux如Centos使用的RPM打包方法。本文仅涉及二进制打包,且采用取巧的方法简化操作。(笔记,而非教程) + + + +*正文开始* diff --git a/content/posts/211019-dpkg_package_guide.md b/content/posts/211019-dpkg_package_guide.md new file mode 100644 index 0000000..01bffc6 --- /dev/null +++ b/content/posts/211019-dpkg_package_guide.md @@ -0,0 +1,85 @@ +--- +title: DEB打包方法简介 +date: 2021-10-19T10:12:23+08:00 +lastmod: 2021-10-19T10:12:23+08:00 +draft: false +description: "Debian系Linux,如ubuntu, deepin等系统的deb打包教程" +authors: + - Kushidou +tags: + - Linux + - Deepin/UOS +categories: [] +toc: true +comments: true +--- + +简单介绍deb打包的方法,包括目录树格式和打包注意事项。 + + + +## 文件结构 + +首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录`/`。一个工作空间的目录树如下: + +``` + PackageDIR + ├─DEBIAN + ├─opt + │ └─kushidou-touchscreen + │ └─desktops + ├─usr + │ ├─bin + │ └─share + └─var +``` + +其中,`DEBIAN`目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。 + +## DEBIAN + +`DEBIAN`目录下有一个control文件和至多四个脚本文件。 + +### control + +`control`文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。 + +| NAME | Description | +| ------------ | ------------------------------------------- | +| Package | 软件名,`dpkg -l` 列出的名字 | +| Version | 版本号-修订号 | +| Section | 软件类型,比如mail, test, x11等 | +| Priority | 优先级/重要性,比如optional 可选的 | +| Architecture | 软件架构,比如x86, arm64 | +| Depends | 依赖环境,用 `,` 隔开,用`(>=< )`指定版本号 | +| Suggests | 推荐安装,指非必要依赖,可以提供更多功能 | +| Maintainer | 作者 <邮箱或者其他联系方式> | +| Descritprion | 简介,可以换行书写 | + +![image-20211019094048011](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20211019101901.png) + +### 额外脚本 + +`DEBIAN`目录下之多放置四个可执行脚本,包括**安装前`preinst`**、**安装后`postinst`**、**卸载前`prerm`**、**卸载前`postrm`**,四个脚本都需要以`#!/bin/bash`开头,内容与普通脚本无异,文件名不需要后缀。 + +这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以`root`身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。 + +此外`postinst`可以用来启动已安装的程序,`prerm`常用来停止正在运行的程序。 + +### md5sums + +为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。 + +## 打包 + +所有文件配置完成后,进入工作空间上级目录,执行指令 + +```bash +dpkg -b ./PackageDIR packagename.deb +``` + +deb包命名的一般规则为 **软件名\_版本-修订号_架构.deb** + +## 其他信息 + +一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。 diff --git a/content/posts/211115-fixNASDriver.md b/content/posts/211115-fixNASDriver.md new file mode 100644 index 0000000..1cf9b49 --- /dev/null +++ b/content/posts/211115-fixNASDriver.md @@ -0,0 +1,98 @@ +--- +title: 记一次拯救NAS硬盘 +date: 2021-11-15T10:02:30+08:00 +lastmod: 2021-11-15T10:02:30+08:00 +draft: false +description: "黑群晖拿下来的硬盘不小心删除分区后进行数据拯救。" +authors: + - Kushidou +tags: + - NAS + - 群晖 + - 数据恢复 +categories: [] +toc: true +comments: true +--- + +更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据? + + + + + +*图片稍后补充。* + +## v 0 起因 + +双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0. + +考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。 + +为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。 + +## v-1 杯具 + +当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。 + +## v-2 尝试 + +DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内**写入任何内容**,也**没有格式化**。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。 + +为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。 + +经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。 + +搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。 + +难道就这么放弃了么? + +## v 1 曙光 + +群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。 + +我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?! + +开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。 + +把小硬盘用易驱线连上树莓派,`lsblk`看到这个硬盘有三个分区,其中`/dev/sda3`下面还有一个`md127`,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。 + +## v 2 得到数据 + +安装`mdadm`软件包,我的树莓派不带这个,然后就能挂载raid盘了。 + +```bash +sudo mount /dev/md127 ~/disk +``` + +disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个`NasData`目录,这正是我当时保存文件的地方。cd进去,在!都还在! + +## v 3 移动数据 + +我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。 + +在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs + +```bash +sudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata +``` + +主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。 + +```bash +rsync -vrultD --exclude '文件名' --exclude '目录名' ~/disk/NasData/* ~/Nasdata/临时目录 +# -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息 +# 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败 +``` + +经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。 + +## v 4 结论 + +现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。 + +这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。 + +总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢? + +![image-20211115095410121](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202111150957877.png) diff --git a/content/posts/211203-LinuxChangeUsername.md b/content/posts/211203-LinuxChangeUsername.md new file mode 100644 index 0000000..2ca8c53 --- /dev/null +++ b/content/posts/211203-LinuxChangeUsername.md @@ -0,0 +1,68 @@ +--- +title: 在Linux环境下修改用户名和主目录 +date: 2021-12-03T09:01:23+08:00 +lastmod: 2021-12-03T09:01:23+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux +categories: [] +toc: true +comments: true +--- + +如何在Linux下完整修改当前用户名和主目录,还不影响其他文件? + + + +Linux的用户名是一个用户的字符串表示,是可以修改的。只要用户的id不改变就不会被系统认作新用户,所有的权限都可以继承。下面介绍如何修改用户的名称、所属组,并更改主目录的名称与对应关系。 + +## 一、进入“单用户模式” + +为了修改用户名时不影响程序运行,需要把当前用户打开的所有程序都关闭。该方法可以快速注销当前用户。 + +```bash +init 1 +执行后输入当前用户密码 +进入黑屏终端后需要输入root密码,可以使用sudo passwd 提前设置 +``` + +对于屏蔽init指令的系统,一般情况下注销用户就可以了,但是有些GUI桌面环境就是以当前用户的身份运行的,所以提供如下几个方式关闭当前用户,挨个尝试即可。 + +```bash +# 1 桌面环境中点击注销 + +# 2 杀掉当前用户所有进程 +pkill -kill -u old_name +# 3 杀掉进程后可能桌面重启,关闭Lightdm管理器 +# Ctrl + Alt + F2 进入第二页面,以root用户登录(可能需要提前设置root密码) +systemctl stop lightdm +# 然后再次执行第二步 +``` + +## 二、修改信息 + +`init 0`可以直接进入黑屏的终端,或者使用 `ctrl + alt + f2`进入(远程连接可以),以root用户登录。 + +```bash +# 1 修改组的名称 +groupmod -n new_name old_name +# 2 修改用户名 -l 改名字 -d 改目录 -g 改组 +usermod -l new_name -d /home/new_name -g new_name old_name +# 3 修改主目录 +mv /home/old_name /home/new_name +``` + +最后回到图形化界面,输入`init 5` ,或者重启电脑。 + +修改完主目录后记得检查是不是有其他软连接指向旧目录,需要进行更新,否则还会遇到问题。 + +## 参考: + +\# [linux用户家目录怎么修改,Linux 更改用户名,用户组和主目录](https://blog.csdn.net/weixin_30016961/article/details/116543982) + +\# [菜鸟教程-usermod指令](https://www.runoob.com/linux/linux-comm-usermod.html) + +\# [linux组的管理:修改组名字,删除组,使用sudo权限](https://blog.csdn.net/weixin_40001125/article/details/88903115) diff --git a/content/posts/220104-DiyLinuxIso.md b/content/posts/220104-DiyLinuxIso.md new file mode 100644 index 0000000..52691ef --- /dev/null +++ b/content/posts/220104-DiyLinuxIso.md @@ -0,0 +1,62 @@ +--- +title: 定制自己的Linux ISO镜像 +date: 2022-01-04T09:17:38+08:00 +lastmod: 2022-01-04T09:17:38+08:00 +draft: false +description: "为已有的Linux ISO镜像添加自己的功能。" +authors: + - Kushidou +tags: + - Linux +categories: [] +toc: true +comments: true +--- + +想要在原版的Linux镜像上添加自定义的软件,但是不想、不会编译,那么就可以基于Linux的iso文件进行修改,定制自己的安装试用镜像。 + + + +起因是因为对付信创操作系统的备份恢复时,UOS、麒麟自带的工具都超级不好用,两个系统的镜像不统一,导致实际使用的时候各种问题和不方便。所以使用clonezilla再生龙工具自己做一个PE镜像。这个镜像不仅可以备份、安装系统,还能够修改硬盘大小、应急拯救系统等多种功能。并且它是一个完整的系统可以安装使用。 + +## 基本计划 + +之前尝试过使用[deepin魔改lub工具](http://bbs.wuyou.net/forum.php?mod=viewthread&tid=409980)制作系统的完整镜像,但是启动总是失败。[LUB](https://forum.ubuntu.org.cn/viewtopic.php?t=206287)全称是*Live Ubuntu Backup*,是Ubuntu的一个工具,到了同为Debian系的deepin魔改的UOS上就水土不服了。(好绕,套娃呢搁这儿 + +所以采取另一种办法,用已有的Linux iso,通过解压其系统文件filesystem.squashfs,然后替换关键文件,最后实现自定义系统。 + +## 解压、修改、打包Linux文件系统 + +首先把Linux iso挂载到系统中。把里面的filesystem.squashfs文件复制出来(因为iso是只读的,复制出来后也方便管理和备份)。我这里以UOS1040为例,其文件系统在`./live`目录下 + +```bash +sudo mount -o loop /xxx/xxx.iso /media/usrname/isofolder +cd /media/usrname/isofolder/live/ +cp ./filesystem.squashfs ~ +``` + +安装`squashfs-tools`工具,用来压缩解压归档文件。 + +```bash +sudo apt install squashfs-tools +#回到主目录后,解压filesystem.squashfs文件 +sudo unsquashfs filesystem.squashfs +``` + +解压后可以看到主目录多一个`squashfs-root`目录,里面存放的就是一个Linux的文件系统了。这时候把你需要的文件拷贝进去,比如我替换了/usr、/opt、/etc三个目录,这三个目录保存了我安装的clonezilla应用及其配置程序,以及我编写的辅助脚本和自启动脚本。 + +修改好了就可以打包了。第一个参数是刚刚解压出来修改好的文件系统,第二个参数是输出的名称。建议把之前拷贝出来的squashfs文件改名进行保存。 + +```bash +sudo mksquashfs squashfs-root/ filesystem.squashfs +``` + +## 重建Linux iso + +最后我们需要重建ISO文件。由于我们是基于原来的ISO修改的,所以只需要替换iso里面的filesystem.squashfs文件即可。否则还需要更换系统内核img/lz、重写引导。 + +把新生成的filesystem.squashfs文件拷贝到Windows系统里,打开UltraISO软件,直接替换Live目录下的filesystem.squashfs文件。同时我们也可以修改`EFI/grub.cfg`文件,毕竟这个系统主要是使用而不是安装。我调换了安装和试用的次序,并把等待时间增加到60s。 + +最后进行修改ISO名称等小操作,保存为ISO文件就可以了。 + +新生成的ISO文件既可以用rufus等工具烧录到U盘中制作PE盘,也可以试用ventoy工具制作成多系统启动盘,甚至可以烧录一张光盘来使用。 diff --git a/content/posts/220223-Deepin_PartsInddefilemanager.md b/content/posts/220223-Deepin_PartsInddefilemanager.md new file mode 100644 index 0000000..00531b2 --- /dev/null +++ b/content/posts/220223-Deepin_PartsInddefilemanager.md @@ -0,0 +1,53 @@ +--- +title: Deepin/UOS文件管理器隐藏不需要的盘符 +date: 2022-02-23T09:18:55+08:00 +lastmod: 2022-02-23T09:18:55+08:00 +draft: false +description: "处理UOS出现其他盘符的问题" +authors: + - Kushidou +tags: + - Linux + - Deepin/UOS +categories: [] +toc: true +comments: true +--- + +Deepin/UOS 里处理文件管理器显示太多盘符的问题。 + + + +## 现象 + +在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在`/etc/fstab`文件添加或删除“”并没有效果。 + +![image-20220223090517363](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230924406.png) + +## 原理/原因 + +这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。 + +## 处理方法 + +1. 打开`/etc/udev/rules.d`目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。 + +2. 打开终端,使用root权限打开编辑 + +```bash +sudo deepin-editor /etc/udev/rules.d/80-udisk2.rules +``` + +3. **打开新的终端**,输入执行 `blkid -f` ,记录LABLE、UUID两列 + + ​ ![截图录屏_选择区域_20220223090208](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230928320.png) + +4. 对比`80-udisks2.rules`的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。 + + (#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换) + + ​ ![截图录屏_选择区域_20220223090606](https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230927888.png) + +5. 保存文件并重启计算机,即可看到效果。 + +6. 该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。 diff --git a/content/posts/220714-unpackClonezillaImage.md b/content/posts/220714-unpackClonezillaImage.md new file mode 100644 index 0000000..0b69318 --- /dev/null +++ b/content/posts/220714-unpackClonezillaImage.md @@ -0,0 +1,66 @@ +--- +title: 从再生龙(clonezilla)镜像直接提取文件 +date: 2022-07-14T13:05:51+08:00 +lastmod: 2022-07-14T13:05:51+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - clonezilla + - Linux +categories: [] +toc: true +comments: true +--- + +从clonezilla的镜像直接提取文件,免于先全盘全区恢复。 + + + +再生龙(clonezilla)是一个常用的Linux系统备份工具,适用于多个版本、多种CPU架构的电脑。 + +再生龙生成的镜像是比较特殊的格式,如果想要从里面提取文件,通常只能进行镜像恢复后进入系统提取。但是往往我们没有那么多硬盘来恢复系统。 + +再生龙的原理简单来说就是把硬盘分区使用partclone工具打包成img文件,然后使用gzip进行分卷压缩。我们只需要逆向操作,利用工具,把镜像解包挂载,直接在当前系统挂载,就可以得到原分区的文件了。 + +## 挂载方法: + +### 1. 环境需求 + +我们操作再生龙镜像时,最好是使用Linux环境。这个环境中,需要安装partclone(也就是再生龙生成镜像的核心工具)。 + +```bash +sudo apt install partclone +# 以Debian系为例 +``` + +并且我们需要准备一个足够大的硬盘,用来存储镜像包和解包出的img文件。硬盘/分区的空间需要大于镜像本来的**分区**空间。 + +### 2. 合并镜像 + +再生龙默认情况下会把一个硬盘分区分按4GB的大小进行分包,通常以`gz.aa`、`gz.ab`等结尾,我们需要把这些包重新整合成一个gz包。 + +整合成gz包后,我们就可以解压gz来得到镜像img了。我们可以把两个步骤用流的方式写入一条命令,节省硬盘空间。 + +```bash +cat sda1.ext4-ptcl-img.gz.* | gzip -d -c > sda1.img +``` + +这一步生成的img文件不能直接使用(google上也有说能用的,但是我试下来不能),需要进行一次转换。经过实际操作,这个文件所占用的空间是镜像原分区的大小,但是统计硬盘使用量时却只计算实际大小,希望了解的人可以帮忙解释一下。 + +```bash +partclone.extfs -r -s sda1.img -o sda1-ex.img --restore_raw_file +``` + +当然上面两条指令也可以使用流的方式再次封装到一条,但是消耗的时间、失败后重新来过的时间,都会更长。 + +## 3.挂载镜像 + +将最后生成的img镜像挂载到我们的/mnt(或其他)目录,进入这个目录就可以看到原来全部的文件了。 + +```bash +sudo mount -o loop -t ext4 sda1-ex.img /mnt +``` + +最后我们只需要进入 `/mnt` 目录,就可以想直接操作硬盘一样把文件复制出来了。 diff --git a/content/posts/230508-remake-deb.md b/content/posts/230508-remake-deb.md new file mode 100644 index 0000000..7c4a750 --- /dev/null +++ b/content/posts/230508-remake-deb.md @@ -0,0 +1,63 @@ +--- +title: 讨论在Debian系操作系统中重新提取已安装的软件并重新打包 +date: 2023-05-08T14:43:41+08:00 +lastmod: 2023-05-08T14:43:41+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - linux + - deepin +categories: [] +toc: true +comments: true +--- + +Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用? + + + +之前有一个需求,要从UOS里面提取一个软件,并且将其安装到另一台设备中。当时找了一圈都没有找到相关的提取教程。但是从逻辑上来讲,dpkg安装的软件会保存deb包的相关信息,所以理论上讲是可以提取出软件包的所有内容,然后我们重新打包的。 + +今天在一个机缘巧合下(其实是懒得研究dpkg原理,瞎猫撞上死耗子),发现了dpkg保存文件信息的地点,于是接下来可以展开畅想:提取已经安装的软件并重新打包。 + +## 文件的保存地点 + +我在找fcitx有关的配置文件进行全盘搜索的时候,找到了一条目录`/usr/lib/dpkg-db/info`,这个目录里面保留了fcitx的"conffiles", "md5sums", "postinst", "prerm"四个文件,以及一个list文件。恰好fcitx.deb解包出来的DEBIAN文件夹内也是这四个文件加上额外的control文件,。 + +那么合理猜测,dpkg安装软件的时候,不仅把软件本身的文件移动到对应位置,还把DEBIAN里面的信息和脚本也转移到`/usr/lib/dpkg-db/`里面保存了。 + +而`fcitx.list`文件中记录的是所有的文件及其路径。想必这里面就是全部的文件了。 + +至于control文件,则都被保存到了`/usr/lib/dpkg-db/status`这个文本文件中。 + +## 流程设想 + +既然deb包所有必要的文件都找到了,那么就可以尝试一下还原过程了。在[这篇文章](https://www.small09.top/posts/211019-dpkg_package_guide/)里我研究过deb包的组成、格式和打包方法。 + +比如对于软件包A: + +1. 创建一个工作目录,并在其中新建`DEBIAN`目录。 + +2. 将`/usr/lib/dpkg-db/info`中所有该软件的脚本和信息(除了A.list)转移到`DEBIAN`中,并重命名成规范名称。 + +3. 将`/usr/lib/dpkg-db/status`中与软件A有关的部分提取出来,保存到`DEBIAN/control`文件中。 + +4. 根据A.list文件,找到软件包含的所有文件,将其按原样整理到工作目录内。 + +5. 使用`dpkg -b`指令重新打包。 + +## 局限性 + +以上毕竟只是设想,没有进行试验,所以估计会出现这些问题,希望有需要的人尝试后可以解决。 + +- 提取信息的难度: 由于`/usr/lib/dpkg-db/status`文件是一个包含几乎所有deb信息的文本文件,体积大内容乱,对于自动化脚本来说有些难度。加上一些软件的衍生品会在软件名上直接加后缀,对于提取的脚本来讲可能造成混淆,提取出不必要的东西。 + +- 获取源文件的难度: 不排除一些软件安装时会删除、修改自身的一些文件,可能打包会后会缺失内容。一些软件安装过程中会进行手动配置从而造成内容改变,重做出来的软件包不一定完整。 + +- 签名问题: 我们打包的软件没有原作者的数字签名,可能无法安装,也可能时候安装官方软件时冲突。 + +- 这个方案只能处理普通的软件,一些deb打包源码,安装时进行编译的,应该是不能用这种方案复原的。(apt的deb-src源) + +最后,希望有大佬可以验证一下这个方案。 diff --git a/content/posts/230908-py-post.md b/content/posts/230908-py-post.md new file mode 100644 index 0000000..ce50ba1 --- /dev/null +++ b/content/posts/230908-py-post.md @@ -0,0 +1,130 @@ +--- +title: Python socket 收取POST协议内容时没有payload +date: 2023-09-08T00:00:00+08:00 +lastmod: 2024-01-04T00:00:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - python + - http + - server +categories: [] +toc: true +comments: true +--- + +使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况 + + + +最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。 + +服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。 + +```python +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.bind((self.host, self.port)) +sock.listen(128) + +(client, address) = self.sock.accept() +th = Thread(target=self.accept_request, args=(client, address)) + +def accept_request(client, address): + data = client_sock.recv(8192) + req = data.decode("utf-8") + # 处理数据的具体方法 + response = process_response(req, client_addr) + client_sock.send(response) + + # clean up + client_sock.shutdown(1) + client_sock.close() +``` + +上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。 + +**=====2024.01.04更新=======** + +下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。 + +其实用flask接收和处理数据会更加方便快捷: + +```python +from flask import Flask, request + +# 实例化flask +app = Flask(__name__) + +# 接收和处理post的方法 +@app.route('/test', methods=['POST']) +def recv_file_www(): + # post的传入参数 + print(request.args.to_dict()) + # post所承载的数据 + print(request.data) + return 'OK' + +if __name__ == '__main__': + app.run(port=31408, host="0.0.0.0",debug=True) +``` + +上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。 + +**======以下是原来的内容==========** + +经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,**必须不断的recv才能完整接收数据**。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。 + +但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案: + +POST的报文头会包含一个`Content-length`的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。 + +那么将上面代码的`client_sock.recv()`部分改写成下面的方法: + +```python +def recv(self, client_sock): + lrecved = 8192 # 记录每次接收的长度 + lrecv_all=0 # 记录数据总长度 + data_body = bytes() # 接收的所有数据存入这里 + payload_len = 0 # 解析的payload长度 + payload_flag = 0 # 用于记录是否接收到了payload,以及实时记录接收到payload的长度 + while True : + part_body= client_sock.recv(8192) # 循环接收数据 + data_body += part_body # 将接收的数据写入data_body + lrecved = len(part_body) # 获取本次接收长度 + lrecv_all += lrecved # 计算总长度 + + if payload_flag == 0: # 没有接收到payload部分时,才进入下面的判断流程 + if data_body[0:3].decode() == 'GET': # 由于我get方法没出现过断开的问题,因此直接返回即可。 + break + elif data_body[0:4].decode() == 'POST': + payload_len = count_payload_len(data_body) # 解析头部,计算payload部分的长度 + + if lrecved < payload_len: + # 偶尔会出现能接收到payload的情况,所以多加了一个判断。 + # 能接收payload肯定总长度大于payload_len的 + print("no payloads, keep recving...") + # 计算post报文的总长度,借用payload_flag变量 + payload_flag = lrecved + payload_len + # 当接收到的总长度 >= POST的计算长度,表示所有数据接收完成,退出 + if payload_flag <= lrecv_all: + break + + return data_body + +``` + +里面解析headers的代码如下: + +```python +def count_payload_len(data): + for d in data.split(b'\r\n'): # 通过\r\n来分隔报文头 + if "LENGTH" in d.upper().decode(): # 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了 + return int(d.decode().split(":")[1]) # 分隔出payload长度并返回 + return 0 +``` + +上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。 + +对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。 diff --git a/content/posts/231127-vncserver.md b/content/posts/231127-vncserver.md new file mode 100644 index 0000000..3e394e6 --- /dev/null +++ b/content/posts/231127-vncserver.md @@ -0,0 +1,59 @@ +--- +title: Linux安装VNC服务端 +date: 2023-11-27T09:33:32+08:00 +lastmod: 2023-11-27T09:33:32+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - Linux + - 软件 +categories: [] +toc: true +comments: true +--- + +*(笔记)在Linux系统里安装远程桌面VNC客户端并连接。* + + + +### 1. 安装NVC服务端 + +```bash +sudo apt install x11vnc +``` + +### 2. 设置远程连接密码 + +```bash +x11vnc --storepasswd +# 输入两次连接密码以设定 +# 询问 Write password to /home/xxx/.vnc/passwd +# 时,输入y并回车保存密码。 +``` + +* 该步骤不需要使用sudo! + +### 3. 使VNC服务开机自启动 + +使用service的方法,将vnc作为一个服务开机启动 + +创建`/lib/systemd/system/x11vnc.service`文件并填入下面的内容 + +```ini +[Unit] +Description=Start x11vnc at startup. +After=multi-user.target +[Service] +Type=simple +ExecStart=/usr/bin/x11vnc -auth guess -rfbauth /home/xxx/.vnc/passwd +[Install] +WantedBy=multi-user.target +``` + +* 注意,/home/xxx 需要替换成实际的用户名! + +### 使用vnc客户端连接 + +我使用Windows上的VncViewer,官网[RealVNC](https://www.realvnc.com/en/),这个软件假冒的很多,下载需要注意甄别。 diff --git a/content/posts/231225-dockernote.md b/content/posts/231225-dockernote.md new file mode 100644 index 0000000..c0c1e88 --- /dev/null +++ b/content/posts/231225-dockernote.md @@ -0,0 +1,163 @@ +--- +title: docker简单笔记 +date: 2023-12-25T09:06:25+08:00 +lastmod: 2023-12-25T09:06:25+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: + - linux + - docker + - 容器和虚拟化 +categories: [] +toc: true +comments: true +--- + +学习docker,使用docker,编写docker + + +## 前言 + +祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。 + +不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。 + +由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。 + +## 安装docker和docker compose + +虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。 + +apt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。 + +```bash +# 添加docker官方的gpg +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + +# 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加 +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +# 我这边添加的结果是 +deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu jammy stable +``` + +然后可以通过apt源安装docker了 + +```bash +sudo apt update +sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin +``` + + **注意,上面的指令同时安装了docker-compose*。如果不安装docker,直接安装docker-compose也是可以的。 + +```bash +wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 +sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose +``` + +更加具体的步骤和细节可以到官网查看:[docker安装](https://docs.docker.com/engine/install/ubuntu/)、[docker-compose安装](https://docs.docker.com/compose/install/standalone/) + +## docker 基本使用方法 + +使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。 + +```bash +# 拉取一个镜像 +docker pull xxx/yyy +# 创建容器并运行 +docker run --name aaa xxx/yyy +# 停止容器 +docker stop docker-id +``` + +关于运行容器,有很多常用的参数: + +> -d 容器转入后台运行 +> +> -e 设置容器的环境变量 +> +> --name 容器名称 +> +> -p 80:80 将暴露的端口映射到容器内端口,外部:内部 +> +> -v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内 +> +> -e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。 + +停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。 + +查询容器id,可以使用指令`docker ps`。docker还有其他几个查询和控制指令 + +> `docker exec 容器 指令` 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。 +> +> `docker ps` 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。 +> +> `docker images` 列出已经下载的镜像列表。与 `docker image` 指令不同。 +> +> `docker kill 容器名/id` 杀死一个无法stop的容器 +> +> `docker rm 容器` 删除一个**容器** +> +> `docker rmi 镜像名` 删除一个**镜像** +> +> `docker restart 容器ID` 重启一个容器,毕竟我们不需要每次都创建新容器。 + +关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。 + +## 更换docker镜像源 + +和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。 + +修改`/etc/docker/daemon.json`文件,添加下面的内容: + +```json +{ + "registry-mirrors": [ + "https://docker.mirrors.ustc.edu.cn" + ] +} +``` + +编辑完成后需要重启docker服务。 + +具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。 + +## docker-compose + +docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。 + +首先创建一个工作目录,并创建一个`docker-compose.yml`的文件,默认情况下docker compose指令只认这个文件名。 + +```ymal +version: '3' +services: + [NAME1]: + image: "image/name" + ports: + - 81:80 + - 8080:8080 + environment: + HOSTNAME: "cc" + PORT: 8181 + volumes: + - nginx-data:/etc/nginx + [NAME2]: + image: 'xxx' + +``` + +docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,`ports`字段指定了内外端口的映射,即'外部:内部',卷映射同理。`environment`不需要 - 开头。 + +编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动 + +> `docker compose up` 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。 +> +> `docker compose down` 停止容器,并删除对应的容器和网络 +> +> `docker compose exec NAME command` 在ymal文件内其中一个容器里运行指令 + +docker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。 + +目前浅学了一下,只有学一点更新一点吧。 diff --git a/content/posts/_index.md b/content/posts/_index.md new file mode 100644 index 0000000..13ddf81 --- /dev/null +++ b/content/posts/_index.md @@ -0,0 +1,5 @@ +--- +title: Posts +cascade: + type: blog +--- diff --git a/content/posts/about_hugo_website.md b/content/posts/about_hugo_website.md new file mode 100644 index 0000000..13f0e34 --- /dev/null +++ b/content/posts/about_hugo_website.md @@ -0,0 +1,52 @@ +--- +title: 关于使用HUGO建立博客站点 +date: 2021-03-23T09:15:09+08:00 +lastmod: 2021-05-24T00:00:00+08:00 +draft: false +description: '' +authors: + - Kushidou +tags: [] +categories: [] +toc: true +comments: true +--- + +使用hugo建立本站的规划和记录。 + + + +# 关于使用HUGO建立博客 + +在经历了worldpress的性能消耗和慢速加载后,我现在决定换用hugo+caddy的建站模式。昨天已经完成了caddy2的基础设置,现在开始测试使用hugo。 + +## hugo的安装与首次创建 + +安装使用*chocolatey*,一个windows下的仿apt、yum的软件包管理器。安装好chocolatey后,根据hugo官网的内容,`choco install hugo -confirm`安装基础的hugo(好处是不用再配置环境变量了)。 + +为hugo创建目录,进入后`hugo site new website`,hugo会自动建立它所需要的层级结构。 + +添加第一个页面,`hugo new xxx/xxx.md',创建的内容会出现在content下,创建多级目录也适合将来归类。 + +## 编写文章 + +hugo的文章是基于markdown文档的,创建的文章会自动加入以下抬头 + +```TOML +--- +title: "$文章标题" +date: $时间 +draft: true +--- +``` + +这个可以修改为json或ymal格式,理应可以添加更多项目,这个之后再研究。 + +## 主题/皮肤 + +主题放在themes下,我在知乎上找到了一个高赞且顺眼的,[有哪些好看的Hugo主题? - 飞雪无情的回答 - 知乎]( +https://www.zhihu.com/question/266175192/answer/460456938),文章中附了github连接,那么我们直接git过来就完事了。 + +## 生成网站 + +文章+主题都有了,现在可以生成第一个网站了(虽然写这些字的时候,网站还没生成)。`hugo`就能直接产生网页,保存至public文件夹。`hugo server`可以启动一个服务器预览静态网页。 diff --git a/content/posts/assents/image-20220223090517363.png b/content/posts/assents/image-20220223090517363.png new file mode 100644 index 0000000..fbd6283 Binary files /dev/null and b/content/posts/assents/image-20220223090517363.png differ diff --git a/content/posts/assents/截图录屏_选择区域_20220223090208.png b/content/posts/assents/截图录屏_选择区域_20220223090208.png new file mode 100644 index 0000000..d85b9ff Binary files /dev/null and b/content/posts/assents/截图录屏_选择区域_20220223090208.png differ diff --git a/content/posts/assents/截图录屏_选择区域_20220223090606.png b/content/posts/assents/截图录屏_选择区域_20220223090606.png new file mode 100644 index 0000000..bff1e57 Binary files /dev/null and b/content/posts/assents/截图录屏_选择区域_20220223090606.png differ diff --git a/data/icons.yaml b/data/icons.yaml new file mode 100644 index 0000000..1034da8 --- /dev/null +++ b/data/icons.yaml @@ -0,0 +1,4 @@ +bilibili: > + + + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..468b40c --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module techblog + +go 1.25.0 + +require github.com/imfing/hextra v0.12.3 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..afa8680 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/imfing/hextra v0.12.3 h1:DZHY2rUWYteyzjlHi9r4n7Bb5e2Q+6LXe4C1Dqn0ZjM= +github.com/imfing/hextra v0.12.3/go.mod h1:vi+yhpq8YPp/aghvJlNKVnJKcPJ/VyAEcfC1BSV9ARo= diff --git a/hugo.yaml b/hugo.yaml new file mode 100644 index 0000000..15889cb --- /dev/null +++ b/hugo.yaml @@ -0,0 +1,52 @@ +baseURL: https://www.small09.top/ +locale: zh-cn +title: 香风家的火柴盒 + +module: + imports: + - path: github.com/imfing/hextra + +markup: + goldmark: + renderer: + unsafe: true + +params: + navbar: + displayTitle: true + displayLogo: true + logo: + path: resources/icon_180x180.png + dark: resources/icon_180x180.png + link: / + width: 40 + height: 40 + displayUpdatedDate: true + dateFormat: "2006/01/02" + quickLinks: + - name: Blog + url: /posts + - name: Daily + url: /daily + - name: Photograph + url: /photograph + + blog: + list: + sortBy: lastmod + sortOrder: desc + pagerSize: 10 + + comments: + enable: true + type: remark42 + remark42: + host: "https://www.small09.top/comment" + site_id: "blog" + + footer: + author: "Kushidou" + startYear: "2021" + icp: "苏ICP备2021006778号" + police: "苏公网安备 32040502000157号" + policeCode: "32040502000157" diff --git a/i18n/zh-cn.yaml b/i18n/zh-cn.yaml new file mode 100644 index 0000000..c19c60b --- /dev/null +++ b/i18n/zh-cn.yaml @@ -0,0 +1 @@ +onThisPage: 目录 diff --git a/layouts/_partials/components/comments.html b/layouts/_partials/components/comments.html new file mode 100644 index 0000000..7fd86b9 --- /dev/null +++ b/layouts/_partials/components/comments.html @@ -0,0 +1,13 @@ +{{- $enableComments := site.Params.comments.enable | default false -}} + +{{ if not (eq .Params.comments nil) }} + {{ $enableComments = .Params.comments }} +{{ end }} + +{{- if $enableComments -}} + {{- if eq site.Params.comments.type "giscus" -}} + {{ partial "components/giscus.html" . }} + {{- else if eq site.Params.comments.type "remark42" -}} + {{ partial "components/remark42.html" . }} + {{- end -}} +{{- end -}} diff --git a/layouts/_partials/components/remark42.html b/layouts/_partials/components/remark42.html new file mode 100644 index 0000000..d4c2157 --- /dev/null +++ b/layouts/_partials/components/remark42.html @@ -0,0 +1,59 @@ +{{- with site.Params.comments.remark42 -}} +
+ + + +{{- else -}} + {{ warnf "remark42 is not configured" }} +{{- end -}} diff --git a/layouts/_partials/custom/content-begin.html b/layouts/_partials/custom/content-begin.html new file mode 100644 index 0000000..e69de29 diff --git a/layouts/_partials/custom/content-end.html b/layouts/_partials/custom/content-end.html new file mode 100644 index 0000000..c5faa6f --- /dev/null +++ b/layouts/_partials/custom/content-end.html @@ -0,0 +1,55 @@ +
+ +
+

子站动态

+
+ +
+ +
+ +
+

+ + + + Daily 最新 +

+

查看日记最新更新

+
+
+ +
+

+ + + + Photograph 最新 +

+

查看摄影最新作品

+
+
+
+ +
+ +
+

关于我

+
+ +
+ +{{- $card := site.GetPage "about-card.md" -}} +{{- if $card }} + +
+
+ {{ $card.Title }} +
+
+

{{ $card.Title }}

+

{{ $card.Params.bio }}

+
+
+
+{{- end }} diff --git a/layouts/_partials/custom/head-end.html b/layouts/_partials/custom/head-end.html new file mode 100644 index 0000000..91a1fb9 --- /dev/null +++ b/layouts/_partials/custom/head-end.html @@ -0,0 +1 @@ + diff --git a/layouts/_partials/custom/page-begin.html b/layouts/_partials/custom/page-begin.html new file mode 100644 index 0000000..e69de29 diff --git a/layouts/_partials/custom/page-end.html b/layouts/_partials/custom/page-end.html new file mode 100644 index 0000000..e69de29 diff --git a/layouts/_partials/favicons.html b/layouts/_partials/favicons.html new file mode 100644 index 0000000..637fa67 --- /dev/null +++ b/layouts/_partials/favicons.html @@ -0,0 +1,3 @@ + + + diff --git a/layouts/_partials/footer.html b/layouts/_partials/footer.html new file mode 100644 index 0000000..f87ac3d --- /dev/null +++ b/layouts/_partials/footer.html @@ -0,0 +1,16 @@ + diff --git a/layouts/_partials/navbar.html b/layouts/_partials/navbar.html new file mode 100644 index 0000000..021e3b3 --- /dev/null +++ b/layouts/_partials/navbar.html @@ -0,0 +1,95 @@ +{{- $navWidth := "hx:max-w-[90rem]" -}} +{{- with .Site.Params.navbar.width -}} + {{ if eq . "normal" -}} + {{ $navWidth = "hx:max-w-screen-xl" -}} + {{ else if eq . "full" -}} + {{ $navWidth = "max-w-full" -}} + {{ end -}} +{{- end -}} + +{{- $page := . -}} +{{- $iconHeight := 24 -}} + +
+
+ + +
+ diff --git a/layouts/_partials/toc.html b/layouts/_partials/toc.html new file mode 100644 index 0000000..159f046 --- /dev/null +++ b/layouts/_partials/toc.html @@ -0,0 +1,106 @@ +{{/* Table of Contents */}} +{{- $toc := .Params.toc | default true -}} +{{- $onThisPage := (T "onThisPage") | default "On this page"}} +{{- $tags := (T "tags") | default "Tags"}} +{{- $editThisPage := (T "editThisPage") | default "Edit this page"}} +{{- $backToTop := (T "backToTop") | default "Scroll to top" -}} + + + +{{/* TOC subheadings component. This is a recursive component that renders a list of headings. */}} +{{- define "toc-subheading" -}} + {{- $headings := .headings -}} + {{- $level := .level -}} + {{- if ge $level 6 -}} + {{ return }} + {{- end -}} + + {{- $padding := (mul $level 4) -}} + {{- $class := cond (eq $level 0) "hx:font-medium" (printf "hx:ltr:pl-%d hx:rtl:pr-%d" $padding $padding) -}} + + {{- range $headings }} + {{- if .Title }} +
  • + + {{- .Title | safeHTML | plainify | htmlUnescape }} + +
  • + {{- end -}} + {{- with .Headings -}} + {{ template "toc-subheading" (dict "headings" . "level" (add $level 1)) }} + {{- end -}} + + {{- end -}} +{{- end -}} diff --git a/layouts/_shortcodes/hextra/feature-card.html b/layouts/_shortcodes/hextra/feature-card.html new file mode 100644 index 0000000..26139c3 --- /dev/null +++ b/layouts/_shortcodes/hextra/feature-card.html @@ -0,0 +1,37 @@ +{{- $title := .Get "title" -}} +{{- $subtitle := .Get "subtitle" -}} +{{- $class := .Get "class" -}} +{{- $image := .Get "image" -}} +{{- $imageClass := .Get "imageClass" -}} +{{- $imageStyle := .Get "imageStyle" -}} +{{- $style := .Get "style" -}} +{{- $icon := .Get "icon" -}} +{{- $link := .Get "link" -}} + +{{- $external := hasPrefix $link "http" -}} +{{- $href := cond (strings.HasPrefix $link "/") ($link | relURL) $link -}} + +{{- if hasPrefix $image "/" -}} + {{- $image = relURL (strings.TrimPrefix "/" $image) -}} +{{- end -}} + + +
    +

    + {{ with $icon -}} + + {{- partial "utils/icon.html" (dict "name" . "attributes" "height=1.5rem") -}} + + {{ end -}} + {{ $title }} +

    +

    {{ $subtitle | markdownify }}

    +
    + {{- with $image -}} + {{ $title }} + {{- end -}} +
    diff --git a/layouts/blog/list.html b/layouts/blog/list.html new file mode 100644 index 0000000..6e14d83 --- /dev/null +++ b/layouts/blog/list.html @@ -0,0 +1,47 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} +
    +
    + {{ partial "custom/page-begin.html" . }} + {{ partial "breadcrumb.html" (dict "page" . "enable" false) }} +
    + {{ if .Title }}

    {{ .Title }}

    {{ end }} +
    + {{ partial "custom/content-begin.html" . }} + {{ .Content }} +
    + {{- $pages := partial "utils/sort-pages" (dict "page" . "by" site.Params.blog.list.sortBy "order" site.Params.blog.list.sortOrder) -}} + {{- $pagerSize := site.Params.blog.list.pagerSize | default 10 -}} + {{- $paginator := .Paginate $pages $pagerSize -}} + {{- $pageCount := len $paginator.Pages -}} + {{- range $index, $page := $paginator.Pages }} + +

    {{ .Title }}

    + {{ if site.Params.blog.list.displayTags }} +
    + {{ partial "tags.html" (dict "context" .) }} +
    + {{ end }} +

    {{- partial "utils/page-description" . -}}

    +

    + {{ partial "utils/format-date" .Date }} + {{ if and .Lastmod (ne (.Date.Format "2006-01-02") (.Lastmod.Format "2006-01-02")) }} + 修改于 {{ partial "utils/format-date" .Lastmod }} + {{ end }} +

    +
    + {{- if lt (add $index 1) $pageCount }} +
    + {{- end -}} + {{ end -}} + + {{- if gt $paginator.TotalPages 1 -}} + {{ partial "components/blog-pager.html" $paginator }} + {{- end -}} + {{ partial "custom/page-end.html" . }} +
    +
    +
    +
    +{{- end -}} diff --git a/layouts/blog/single.html b/layouts/blog/single.html new file mode 100644 index 0000000..1ea9efa --- /dev/null +++ b/layouts/blog/single.html @@ -0,0 +1,58 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true "displayPlaceholder" true) }} + {{ partial "toc.html" . }} +
    +
    + {{ partial "custom/page-begin.html" . }} + {{ partial "breadcrumb.html" (dict "page" . "enable" true) }} + {{ if .Title }} +
    +

    {{ .Title }}

    + {{ partial "components/page-context-menu" . }} +
    + {{ end }} +
    + {{- with $date := .Date }}{{ partial "utils/format-date" $date }}{{ end -}} + {{- $lazyLoading := site.Params.enableImageLazyLoading | default true -}} + {{ if and .Date .Params.authors }}·{{ end -}} + {{- with $.Params.authors -}} + {{- range $i, $author := . -}} + {{- if reflect.IsMap $author -}} + {{- if and $i (not $author.image) }},{{ end -}} + + {{- with $image := $author.image }} + {{- $isLocal := not (urls.Parse $image).Scheme -}} + {{- $startsWithSlash := hasPrefix $image "/" -}} + {{- if and $isLocal $startsWithSlash }} + {{- $image = (relURL (strings.TrimPrefix "/" $image)) -}} + {{ end -}} + {{ $author.name }} + {{ end -}} +
    {{ $author.name }}
    +
    + {{- else -}} + {{- if $i }},{{ end -}}{{ $author }} + {{- end -}} + {{- end -}} + {{- end -}} +
    +
    + {{ partial "custom/content-begin.html" . }} + {{ .Content }} +
    + {{- partial "components/last-updated.html" . -}} + {{- if (site.Params.blog.article.displayPagination | default true) -}} + {{- .Store.Set "reversePagination" (.Params.reversePagination | default true) -}} + {{- partial "components/pager.html" . -}} + {{ end }} + {{- partial "components/comments.html" . -}} + {{ partial "custom/page-end.html" . }} +
    +
    +
    +{{ end }} diff --git a/layouts/hextra-home.html b/layouts/hextra-home.html new file mode 100644 index 0000000..8689071 --- /dev/null +++ b/layouts/hextra-home.html @@ -0,0 +1,42 @@ +{{ define "main" }} +
    + {{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }} +
    + {{ partial "custom/page-begin.html" . }} +
    + {{ partial "custom/content-begin.html" . }} + {{ .Content }} + + {{/* Blog posts section */}} + {{ $posts := where site.RegularPages "Section" "posts" }} + {{ if $posts }} +
    +
    +

    最近文章

    +
    +
    + + {{ end }} + + {{ partial "custom/content-end.html" . }} +
    + {{ partial "custom/page-end.html" . }} +
    +
    +{{ end }} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..a993585 --- /dev/null +++ b/public/404.html @@ -0,0 +1,40 @@ + + + +
    + +

    + 404 +

    +
    +

    This page could not be found.

    +
    +
    + + diff --git a/public/about-card/index.html b/public/about-card/index.html new file mode 100644 index 0000000..06499e2 --- /dev/null +++ b/public/about-card/index.html @@ -0,0 +1,568 @@ + + + + + + + +Kushidou – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容
    +
    + + +
    + + +
    + + + + +
    +
    + +
    +

    Kushidou

    +
    +
    + +
    +
    +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/about/index.html b/public/about/index.html new file mode 100644 index 0000000..adb6fa4 --- /dev/null +++ b/public/about/index.html @@ -0,0 +1,614 @@ + + + + + + + +关于我 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容
    +
    + + +
    + + +
    + + + + +
    +
    + +
    +

    关于我

    +
    +
    +
    +
    + Kushidou +
    +
    +

    Kushidou

    +

    什么都学,却什么都不精通的一个小白 :P

    + +
    +
    +
    +

    自我介绍 +

    +

    我是一名嵌入式工程师,平时喜欢折腾各种开源软件和硬件。这个博客用来记录技术探索过程中的笔记和心得,也包括一些日常生活和摄影的分享。

    +
    +

    技能与方向 +

    +
      +
    • 嵌入式开发 — Linux 系统开发、驱动调试、交叉编译
    • +
    • 开源软件 — Docker、Hugo、各类 CLI 工具
    • +
    • 网络与服务 — 自建 NAS、内网穿透、家庭服务器
    • +
    • 摄影 — 风光 / 街拍 / 生活记录
    • +
    + +
    +
    +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 0000000..7f0493c Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 0000000..faea4c2 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..eb281cb Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/categories/index.html b/public/categories/index.html new file mode 100644 index 0000000..9600dfc --- /dev/null +++ b/public/categories/index.html @@ -0,0 +1,498 @@ + + + + + + + +Categories – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容
    +
    + + +
    + + +
    + + + + +
    +
    +
    +

    Categories

    +
    +
    + +
    +
    + +
    +
    +
    +
    + + + + + + + + diff --git a/public/categories/index.xml b/public/categories/index.xml new file mode 100644 index 0000000..a23d2fe --- /dev/null +++ b/public/categories/index.xml @@ -0,0 +1,18 @@ + + + 香风家的火柴盒 – Categories + https://www.small09.top/categories/ + Recent content in Categories on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + + + + + + + + + + + diff --git a/public/css/compiled/main.css b/public/css/compiled/main.css new file mode 100644 index 0000000..4e314b4 --- /dev/null +++ b/public/css/compiled/main.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--hx-font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--hx-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--hx-color-red-100:oklch(93.6% .032 17.717);--hx-color-red-200:oklch(88.5% .062 18.334);--hx-color-red-900:oklch(39.6% .141 25.723);--hx-color-orange-50:oklch(98% .016 73.684);--hx-color-orange-100:oklch(95.4% .038 75.164);--hx-color-orange-300:oklch(83.7% .128 66.29);--hx-color-orange-400:oklch(75% .183 55.934);--hx-color-orange-800:oklch(47% .157 37.304);--hx-color-amber-100:oklch(96.2% .059 95.617);--hx-color-amber-200:oklch(92.4% .12 95.746);--hx-color-amber-900:oklch(41.4% .112 45.904);--hx-color-yellow-50:oklch(98.7% .026 102.212);--hx-color-yellow-100:oklch(97.3% .071 103.193);--hx-color-yellow-200:oklch(94.5% .129 101.54);--hx-color-yellow-700:oklch(55.4% .135 66.442);--hx-color-yellow-900:oklch(42.1% .095 57.708);--hx-color-green-100:oklch(96.2% .044 156.743);--hx-color-green-200:oklch(92.5% .084 155.995);--hx-color-green-900:oklch(39.3% .095 152.535);--hx-color-blue-100:oklch(93.2% .032 255.585);--hx-color-blue-200:oklch(88.2% .059 254.128);--hx-color-blue-900:oklch(37.9% .146 265.522);--hx-color-indigo-100:oklch(93% .034 272.788);--hx-color-indigo-200:oklch(87% .065 274.039);--hx-color-indigo-900:oklch(35.9% .144 278.697);--hx-color-purple-100:oklch(94.6% .033 307.174);--hx-color-purple-200:oklch(90.2% .063 306.703);--hx-color-purple-900:oklch(38.1% .176 304.987);--hx-color-slate-50:oklch(98.4% .003 247.858);--hx-color-slate-100:oklch(96.8% .007 247.896);--hx-color-slate-900:oklch(20.8% .042 265.755);--hx-color-gray-50:oklch(98.5% .002 247.839);--hx-color-gray-100:oklch(96.7% .003 264.542);--hx-color-gray-200:oklch(92.8% .006 264.531);--hx-color-gray-300:oklch(87.2% .01 258.338);--hx-color-gray-400:oklch(70.7% .022 261.325);--hx-color-gray-500:oklch(55.1% .027 264.364);--hx-color-gray-600:oklch(44.6% .03 256.802);--hx-color-gray-700:oklch(37.3% .034 259.733);--hx-color-gray-800:oklch(27.8% .033 256.848);--hx-color-gray-900:oklch(21% .034 264.665);--hx-color-neutral-50:oklch(98.5% 0 0);--hx-color-neutral-200:oklch(92.2% 0 0);--hx-color-neutral-300:oklch(87% 0 0);--hx-color-neutral-400:oklch(70.8% 0 0);--hx-color-neutral-500:oklch(55.6% 0 0);--hx-color-neutral-600:oklch(43.9% 0 0);--hx-color-neutral-700:oklch(37.1% 0 0);--hx-color-neutral-800:oklch(26.9% 0 0);--hx-color-neutral-900:oklch(20.5% 0 0);--hx-color-black:#000;--hx-color-white:#fff;--hx-spacing:.25rem;--hx-breakpoint-xl:80rem;--hx-text-xs:.75rem;--hx-text-xs--line-height:calc(1/.75);--hx-text-sm:.875rem;--hx-text-sm--line-height:calc(1.25/.875);--hx-text-base:1rem;--hx-text-base--line-height:calc(1.5/1);--hx-text-lg:1.125rem;--hx-text-lg--line-height:calc(1.75/1.125);--hx-text-xl:1.25rem;--hx-text-xl--line-height:calc(1.75/1.25);--hx-text-2xl:1.5rem;--hx-text-2xl--line-height:calc(2/1.5);--hx-text-3xl:1.875rem;--hx-text-3xl--line-height:calc(2.25/1.875);--hx-text-4xl:2.25rem;--hx-text-4xl--line-height:calc(2.5/2.25);--hx-text-5xl:3rem;--hx-text-5xl--line-height:1;--hx-font-weight-normal:400;--hx-font-weight-medium:500;--hx-font-weight-semibold:600;--hx-font-weight-bold:700;--hx-font-weight-extrabold:800;--hx-tracking-tighter:-.05em;--hx-tracking-tight:-.025em;--hx-leading-tight:1.25;--hx-radius-xs:.125rem;--hx-radius-sm:.25rem;--hx-radius-md:.375rem;--hx-radius-lg:.5rem;--hx-radius-xl:.75rem;--hx-radius-3xl:1.5rem;--hx-ease-in:cubic-bezier(.4,0,1,1);--hx-ease-out:cubic-bezier(0,0,.2,1);--hx-ease-in-out:cubic-bezier(.4,0,.2,1);--hx-blur-md:12px;--hx-default-transition-duration:.15s;--hx-default-transition-timing-function:cubic-bezier(.4,0,.2,1);--hx-default-font-family:var(--hx-font-sans);--hx-default-mono-font-family:var(--hx-font-mono);--hx-color-primary-100:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*44));--hx-color-primary-200:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*36));--hx-color-primary-300:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*27));--hx-color-primary-400:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*16));--hx-color-primary-500:hsl(var(--primary-hue)var(--primary-saturation)var(--primary-lightness));--hx-color-primary-600:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*45));--hx-color-primary-700:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*39));--hx-color-primary-800:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*32));--hx-color-primary-900:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*24));--hx-color-dark:#111}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--hx-default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--hx-default-font-feature-settings,normal);font-variation-settings:var(--hx-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--hx-default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--hx-default-mono-font-feature-settings,normal);font-variation-settings:var(--hx-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}abbr:where([title]){cursor:help}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible:where(.dark,.dark *){--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}}@layer components;@layer utilities{.hx\:pointer-events-none{pointer-events:none}.hx\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hx\:absolute{position:absolute}.hx\:relative{position:relative}.hx\:sticky{position:sticky}.hx\:inset-x-0{inset-inline:calc(var(--hx-spacing)*0)}.hx\:inset-y-0{inset-block:calc(var(--hx-spacing)*0)}.hx\:top-0{top:calc(var(--hx-spacing)*0)}.hx\:top-1\/2{top:50%}.hx\:top-8{top:calc(var(--hx-spacing)*8)}.hx\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:top-\[40\%\]{top:40%}.hx\:top-full{top:100%}.hx\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:bottom-0{bottom:calc(var(--hx-spacing)*0)}.hx\:left-0{left:calc(var(--hx-spacing)*0)}.hx\:left-\[24px\]{left:24px}.hx\:left-\[36px\]{left:36px}.hx\:z-20{z-index:20}.hx\:z-\[-1\]{z-index:-1}.hx\:order-last{order:9999}.hx\:m-\[11px\]{margin:11px}.hx\:mx-1{margin-inline:calc(var(--hx-spacing)*1)}.hx\:mx-4{margin-inline:calc(var(--hx-spacing)*4)}.hx\:mx-auto{margin-inline:auto}.hx\:my-1{margin-block:calc(var(--hx-spacing)*1)}.hx\:my-1\.5{margin-block:calc(var(--hx-spacing)*1.5)}.hx\:my-2{margin-block:calc(var(--hx-spacing)*2)}.hx\:-mt-20{margin-top:calc(var(--hx-spacing)*-20)}.hx\:mt-1{margin-top:calc(var(--hx-spacing)*1)}.hx\:mt-1\.5{margin-top:calc(var(--hx-spacing)*1.5)}.hx\:mt-2{margin-top:calc(var(--hx-spacing)*2)}.hx\:mt-4{margin-top:calc(var(--hx-spacing)*4)}.hx\:mt-5{margin-top:calc(var(--hx-spacing)*5)}.hx\:mt-6{margin-top:calc(var(--hx-spacing)*6)}.hx\:mt-8{margin-top:calc(var(--hx-spacing)*8)}.hx\:mt-12{margin-top:calc(var(--hx-spacing)*12)}.hx\:mt-16{margin-top:calc(var(--hx-spacing)*16)}.hx\:mt-auto{margin-top:auto}.hx\:-mr-2{margin-right:calc(var(--hx-spacing)*-2)}.hx\:mr-1{margin-right:calc(var(--hx-spacing)*1)}.hx\:mr-2{margin-right:calc(var(--hx-spacing)*2)}.hx\:-mb-0\.5{margin-bottom:calc(var(--hx-spacing)*-.5)}.hx\:mb-0{margin-bottom:calc(var(--hx-spacing)*0)}.hx\:mb-2{margin-bottom:calc(var(--hx-spacing)*2)}.hx\:mb-4{margin-bottom:calc(var(--hx-spacing)*4)}.hx\:mb-6{margin-bottom:calc(var(--hx-spacing)*6)}.hx\:mb-8{margin-bottom:calc(var(--hx-spacing)*8)}.hx\:mb-10{margin-bottom:calc(var(--hx-spacing)*10)}.hx\:mb-12{margin-bottom:calc(var(--hx-spacing)*12)}.hx\:mb-16{margin-bottom:calc(var(--hx-spacing)*16)}.hx\:-ml-2{margin-left:calc(var(--hx-spacing)*-2)}.hx\:ml-2{margin-left:calc(var(--hx-spacing)*2)}.hx\:ml-4{margin-left:calc(var(--hx-spacing)*4)}.hx\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hx\:block{display:block}.hx\:flex{display:flex}.hx\:grid{display:grid}.hx\:hidden{display:none}.hx\:inline{display:inline}.hx\:inline-block{display:inline-block}.hx\:inline-flex{display:inline-flex}.hx\:aspect-auto{aspect-ratio:auto}.hx\:size-4{width:calc(var(--hx-spacing)*4);height:calc(var(--hx-spacing)*4)}.hx\:h-0{height:calc(var(--hx-spacing)*0)}.hx\:h-2{height:calc(var(--hx-spacing)*2)}.hx\:h-3\.5{height:calc(var(--hx-spacing)*3.5)}.hx\:h-4{height:calc(var(--hx-spacing)*4)}.hx\:h-5{height:calc(var(--hx-spacing)*5)}.hx\:h-7{height:calc(var(--hx-spacing)*7)}.hx\:h-10{height:calc(var(--hx-spacing)*10)}.hx\:h-16{height:calc(var(--hx-spacing)*16)}.hx\:h-\[18px\]{height:18px}.hx\:h-full{height:100%}.hx\:h-px{height:1px}.hx\:max-h-\(--menu-height\){max-height:var(--menu-height)}.hx\:max-h-64{max-height:calc(var(--hx-spacing)*64)}.hx\:max-h-\[calc\(100vh-var\(--navbar-height\)-env\(safe-area-inset-bottom\)\)\]{max-height:calc(100vh - var(--navbar-height) - env(safe-area-inset-bottom))}.hx\:max-h-\[min\(calc\(50vh-11rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(50vh - 11rem - env(safe-area-inset-bottom)),400px)}.hx\:min-h-\[100px\]{min-height:100px}.hx\:min-h-\[calc\(100vh-var\(--navbar-height\)\)\]{min-height:calc(100vh - var(--navbar-height))}.hx\:w-2{width:calc(var(--hx-spacing)*2)}.hx\:w-3\.5{width:calc(var(--hx-spacing)*3.5)}.hx\:w-4{width:calc(var(--hx-spacing)*4)}.hx\:w-10{width:calc(var(--hx-spacing)*10)}.hx\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:w-\[110\%\]{width:110%}.hx\:w-\[180\%\]{width:180%}.hx\:w-full{width:100%}.hx\:w-max{width:max-content}.hx\:w-screen{width:100vw}.hx\:max-w-\[50\%\]{max-width:50%}.hx\:max-w-\[90rem\]{max-width:90rem}.hx\:max-w-\[min\(calc\(100vw-2rem\)\,calc\(100\%\+20rem\)\)\]{max-width:min(100vw - 2rem,100% + 20rem)}.hx\:max-w-full{max-width:100%}.hx\:max-w-none{max-width:none}.hx\:max-w-screen-xl{max-width:var(--hx-breakpoint-xl)}.hx\:min-w-0{min-width:calc(var(--hx-spacing)*0)}.hx\:min-w-\[18px\]{min-width:18px}.hx\:min-w-\[24px\]{min-width:24px}.hx\:min-w-full{min-width:100%}.hx\:shrink-0{flex-shrink:0}.hx\:grow{flex-grow:1}.hx\:origin-center{transform-origin:50%}.hx\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hx\:cursor-default{cursor:default}.hx\:cursor-pointer{cursor:pointer}.hx\:scroll-my-6{scroll-margin-block:calc(var(--hx-spacing)*6)}.hx\:scroll-py-6{scroll-padding-block:calc(var(--hx-spacing)*6)}.hx\:list-none{list-style-type:none}.hx\:appearance-none{appearance:none}.hx\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.hx\:flex-col{flex-direction:column}.hx\:flex-wrap{flex-wrap:wrap}.hx\:items-baseline{align-items:baseline}.hx\:items-center{align-items:center}.hx\:items-start{align-items:flex-start}.hx\:justify-between{justify-content:space-between}.hx\:justify-center{justify-content:center}.hx\:justify-end{justify-content:flex-end}.hx\:justify-start{justify-content:flex-start}.hx\:justify-items-start{justify-items:start}.hx\:gap-1{gap:calc(var(--hx-spacing)*1)}.hx\:gap-1\.5{gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-2{gap:calc(var(--hx-spacing)*2)}.hx\:gap-4{gap:calc(var(--hx-spacing)*4)}.hx\:gap-x-1\.5{column-gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-x-2{column-gap:calc(var(--hx-spacing)*2)}.hx\:gap-y-1{row-gap:calc(var(--hx-spacing)*1)}.hx\:gap-y-2{row-gap:calc(var(--hx-spacing)*2)}.hx\:overflow-auto{overflow:auto}.hx\:overflow-hidden{overflow:hidden}.hx\:overflow-x-auto{overflow-x:auto}.hx\:overflow-x-hidden{overflow-x:hidden}.hx\:overflow-y-auto{overflow-y:auto}.hx\:overflow-y-hidden{overflow-y:hidden}.hx\:overscroll-contain{overscroll-behavior:contain}.hx\:overscroll-x-contain{overscroll-behavior-x:contain}.hx\:rounded-3xl{border-radius:var(--hx-radius-3xl)}.hx\:rounded-full{border-radius:3.40282e38px}.hx\:rounded-lg{border-radius:var(--hx-radius-lg)}.hx\:rounded-md{border-radius:var(--hx-radius-md)}.hx\:rounded-sm{border-radius:var(--hx-radius-sm)}.hx\:rounded-xl{border-radius:var(--hx-radius-xl)}.hx\:rounded-xs{border-radius:var(--hx-radius-xs)}.hx\:rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.hx\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:border-0{border-style:var(--tw-border-style);border-width:0}.hx\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.hx\:border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.hx\:border-amber-200{border-color:var(--hx-color-amber-200)}.hx\:border-black\/5{border-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.hx\:border-black\/5{border-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:border-blue-200{border-color:var(--hx-color-blue-200)}.hx\:border-gray-200{border-color:var(--hx-color-gray-200)}.hx\:border-gray-500{border-color:var(--hx-color-gray-500)}.hx\:border-green-200{border-color:var(--hx-color-green-200)}.hx\:border-indigo-200{border-color:var(--hx-color-indigo-200)}.hx\:border-orange-100{border-color:var(--hx-color-orange-100)}.hx\:border-purple-200{border-color:var(--hx-color-purple-200)}.hx\:border-red-200{border-color:var(--hx-color-red-200)}.hx\:border-transparent{border-color:#0000}.hx\:border-yellow-100{border-color:var(--hx-color-yellow-100)}.hx\:bg-amber-100{background-color:var(--hx-color-amber-100)}.hx\:bg-black\/\[\.05\]{background-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.hx\:bg-black\/\[\.05\]{background-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:bg-blue-100{background-color:var(--hx-color-blue-100)}.hx\:bg-gray-100{background-color:var(--hx-color-gray-100)}.hx\:bg-gray-200{background-color:var(--hx-color-gray-200)}.hx\:bg-green-100{background-color:var(--hx-color-green-100)}.hx\:bg-indigo-100{background-color:var(--hx-color-indigo-100)}.hx\:bg-neutral-50{background-color:var(--hx-color-neutral-50)}.hx\:bg-neutral-900{background-color:var(--hx-color-neutral-900)}.hx\:bg-orange-50{background-color:var(--hx-color-orange-50)}.hx\:bg-primary-100{background-color:var(--hx-color-primary-100)}.hx\:bg-primary-400{background-color:var(--hx-color-primary-400)}.hx\:bg-primary-600{background-color:var(--hx-color-primary-600)}.hx\:bg-primary-700\/5{background-color:var(--hx-color-primary-700)}@supports (color:color-mix(in lab, red, red)){.hx\:bg-primary-700\/5{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hx\:bg-purple-100{background-color:var(--hx-color-purple-100)}.hx\:bg-red-100{background-color:var(--hx-color-red-100)}.hx\:bg-transparent{background-color:#0000}.hx\:bg-white{background-color:var(--hx-color-white)}.hx\:bg-yellow-50{background-color:var(--hx-color-yellow-50)}.hx\:bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.hx\:from-gray-900{--tw-gradient-from:var(--hx-color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:to-gray-600{--tw-gradient-to:var(--hx-color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:bg-clip-text{-webkit-background-clip:text;background-clip:text}.hx\:p-0{padding:calc(var(--hx-spacing)*0)}.hx\:p-0\.5{padding:calc(var(--hx-spacing)*.5)}.hx\:p-1{padding:calc(var(--hx-spacing)*1)}.hx\:p-1\.5{padding:calc(var(--hx-spacing)*1.5)}.hx\:p-2{padding:calc(var(--hx-spacing)*2)}.hx\:p-4{padding:calc(var(--hx-spacing)*4)}.hx\:p-6{padding:calc(var(--hx-spacing)*6)}.hx\:px-1\.5{padding-inline:calc(var(--hx-spacing)*1.5)}.hx\:px-2{padding-inline:calc(var(--hx-spacing)*2)}.hx\:px-2\.5{padding-inline:calc(var(--hx-spacing)*2.5)}.hx\:px-3{padding-inline:calc(var(--hx-spacing)*3)}.hx\:px-4{padding-inline:calc(var(--hx-spacing)*4)}.hx\:px-6{padding-inline:calc(var(--hx-spacing)*6)}.hx\:px-8{padding-inline:calc(var(--hx-spacing)*8)}.hx\:py-1{padding-block:calc(var(--hx-spacing)*1)}.hx\:py-1\.5{padding-block:calc(var(--hx-spacing)*1.5)}.hx\:py-2{padding-block:calc(var(--hx-spacing)*2)}.hx\:py-2\.5{padding-block:calc(var(--hx-spacing)*2.5)}.hx\:py-3{padding-block:calc(var(--hx-spacing)*3)}.hx\:py-4{padding-block:calc(var(--hx-spacing)*4)}.hx\:py-12{padding-block:calc(var(--hx-spacing)*12)}.hx\:pt-4{padding-top:calc(var(--hx-spacing)*4)}.hx\:pt-6{padding-top:calc(var(--hx-spacing)*6)}.hx\:pt-8{padding-top:calc(var(--hx-spacing)*8)}.hx\:pr-2{padding-right:calc(var(--hx-spacing)*2)}.hx\:pr-4{padding-right:calc(var(--hx-spacing)*4)}.hx\:pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\]{padding-right:calc(env(safe-area-inset-right) - 1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-left),1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-right\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-right),1.5rem)}.hx\:pb-8{padding-bottom:calc(var(--hx-spacing)*8)}.hx\:pb-\[env\(safe-area-inset-bottom\)\]{padding-bottom:env(safe-area-inset-bottom)}.hx\:pb-px{padding-bottom:1px}.hx\:pl-6{padding-left:calc(var(--hx-spacing)*6)}.hx\:pl-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-left:max(env(safe-area-inset-left),1.5rem)}.hx\:text-center{text-align:center}.hx\:text-left{text-align:left}.hx\:align-\[-2\.5px\]{vertical-align:-2.5px}.hx\:align-baseline{vertical-align:baseline}.hx\:align-middle{vertical-align:middle}.hx\:align-text-bottom{vertical-align:text-bottom}.hx\:font-mono{font-family:var(--hx-font-mono)}.hx\:text-2xl{font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height))}.hx\:text-4xl{font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height))}.hx\:text-base{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height))}.hx\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}.hx\:text-xs{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height))}.hx\:text-\[\.65rem\]{font-size:.65rem}.hx\:text-\[10px\]{font-size:10px}.hx\:leading-5{--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5)}.hx\:leading-6{--tw-leading:calc(var(--hx-spacing)*6);line-height:calc(var(--hx-spacing)*6)}.hx\:leading-7{--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hx\:leading-none{--tw-leading:1;line-height:1}.hx\:leading-tight{--tw-leading:var(--hx-leading-tight);line-height:var(--hx-leading-tight)}.hx\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:font-extrabold{--tw-font-weight:var(--hx-font-weight-extrabold);font-weight:var(--hx-font-weight-extrabold)}.hx\:font-medium{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:font-normal{--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal)}.hx\:font-semibold{--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hx\:tracking-tight{--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight)}.hx\:tracking-tighter{--tw-tracking:var(--hx-tracking-tighter);letter-spacing:var(--hx-tracking-tighter)}.hx\:break-words,.hx\:wrap-break-word{overflow-wrap:break-word}.hx\:text-ellipsis{text-overflow:ellipsis}.hx\:whitespace-nowrap{white-space:nowrap}.hx\:text-\[color\:hsl\(var\(--primary-hue\)\,100\%\,50\%\)\]{color:hsl(var(--primary-hue),100%,50%)}.hx\:text-amber-900{color:var(--hx-color-amber-900)}.hx\:text-blue-900{color:var(--hx-color-blue-900)}.hx\:text-current{color:currentColor}.hx\:text-gray-100{color:var(--hx-color-gray-100)}.hx\:text-gray-500{color:var(--hx-color-gray-500)}.hx\:text-gray-600{color:var(--hx-color-gray-600)}.hx\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:text-green-900{color:var(--hx-color-green-900)}.hx\:text-indigo-900{color:var(--hx-color-indigo-900)}.hx\:text-orange-800{color:var(--hx-color-orange-800)}.hx\:text-primary-800{color:var(--hx-color-primary-800)}.hx\:text-purple-900{color:var(--hx-color-purple-900)}.hx\:text-red-900{color:var(--hx-color-red-900)}.hx\:text-slate-50{color:var(--hx-color-slate-50)}.hx\:text-slate-900{color:var(--hx-color-slate-900)}.hx\:text-transparent{color:#0000}.hx\:text-white{color:var(--hx-color-white)}.hx\:text-yellow-900{color:var(--hx-color-yellow-900)}.hx\:capitalize{text-transform:capitalize}.hx\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.hx\:no-underline{text-decoration-line:none}.hx\:underline{text-decoration-line:underline}.hx\:decoration-from-font{text-decoration-thickness:from-font}.hx\:underline-offset-2{text-underline-offset:2px}.hx\:opacity-0{opacity:0}.hx\:opacity-50{opacity:.5}.hx\:opacity-80{opacity:.8}.hx\:shadow-\[0_-12px_16px_\#fff\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_-12px_16px_white\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,white);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_2px_4px_rgba\(0\,0\,0\,\.02\)\,0_1px_0_rgba\(0\,0\,0\,\.06\)\]{--tw-shadow:0 2px 4px var(--tw-shadow-color,#00000005),0 1px 0 var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hextra-focus-visible:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}.hx\:hextra-focus-visible:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}.hx\:hextra-focus-visible-inset:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}.hx\:hextra-focus-visible-inset:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800)}.hx\:shadow-gray-100{--tw-shadow-color:var(--hx-color-gray-100)}@supports (color:color-mix(in lab, red, red)){.hx\:shadow-gray-100{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}.hx\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:duration-75{--tw-duration:75ms;transition-duration:75ms}.hx\:duration-150{--tw-duration:.15s;transition-duration:.15s}.hx\:duration-200{--tw-duration:.2s;transition-duration:.2s}.hx\:ease-in{--tw-ease:var(--hx-ease-in);transition-timing-function:var(--hx-ease-in)}.hx\:ease-in-out{--tw-ease:var(--hx-ease-in-out);transition-timing-function:var(--hx-ease-in-out)}.hx\:outline-none{--tw-outline-style:none;outline-style:none}.hx\:select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hx\:group-hover\:text-gray-900:is(:where(.hx\:group):hover *){color:var(--hx-color-gray-900)}.hx\:group-hover\:underline:is(:where(.hx\:group):hover *){text-decoration-line:underline}.hx\:group-hover\/code\:opacity-100:is(:where(.hx\:group\/code):hover *){opacity:1}}.hx\:group-data-\[active\=true\]\:text-primary-800:is(:where(.hx\:group)[data-active=true] *){color:var(--hx-color-primary-800)}.hx\:group-data-\[theme\=dark\]\:hidden:is(:where(.hx\:group)[data-theme=dark] *),.hx\:group-data-\[theme\=light\]\:hidden:is(:where(.hx\:group)[data-theme=light] *),.hx\:group-data-\[theme\=system\]\:hidden:is(:where(.hx\:group)[data-theme=system] *){display:none}.hx\:group-\[\.copied\]\/copybtn\:block:is(:where(.hx\:group\/copybtn).copied *){display:block}.hx\:group-\[\.copied\]\/copybtn\:hidden:is(:where(.hx\:group\/copybtn).copied *){display:none}.hx\:placeholder\:text-gray-500::placeholder{color:var(--hx-color-gray-500)}.hx\:before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.hx\:before\:absolute:before{content:var(--tw-content);position:absolute}.hx\:before\:inset-0:before{content:var(--tw-content);inset:calc(var(--hx-spacing)*0)}.hx\:before\:inset-y-1:before{content:var(--tw-content);inset-block:calc(var(--hx-spacing)*1)}.hx\:before\:mr-1:before{content:var(--tw-content);margin-right:calc(var(--hx-spacing)*1)}.hx\:before\:inline-block:before{content:var(--tw-content);display:inline-block}.hx\:before\:w-px:before{content:var(--tw-content);width:1px}.hx\:before\:bg-gray-200:before{content:var(--tw-content);background-color:var(--hx-color-gray-200)}.hx\:before\:opacity-25:before{content:var(--tw-content);opacity:.25}.hx\:before\:transition-transform:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:before\:content-\[\"\"\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\'\#\'\]:before{--tw-content:"#";content:var(--tw-content)}.hx\:before\:content-\[\'\'\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.hx\:group-open\:before\:rotate-90:is(:where(.hx\:group):is([open],:popover-open,:open) *):before{content:var(--tw-content);rotate:90deg}.hx\:first\:mt-0:first-child{margin-top:calc(var(--hx-spacing)*0)}.hx\:last-of-type\:mb-0:last-of-type{margin-bottom:calc(var(--hx-spacing)*0)}@media (hover:hover){.hx\:hover\:border-gray-200:hover{border-color:var(--hx-color-gray-200)}.hx\:hover\:border-gray-300:hover{border-color:var(--hx-color-gray-300)}.hx\:hover\:border-gray-400:hover{border-color:var(--hx-color-gray-400)}.hx\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}.hx\:hover\:bg-gray-100:hover{background-color:var(--hx-color-gray-100)}.hx\:hover\:bg-gray-800\/5:hover{background-color:var(--hx-color-gray-800)}@supports (color:color-mix(in lab, red, red)){.hx\:hover\:bg-gray-800\/5:hover{background-color:color-mix(in oklab,var(--hx-color-gray-800)5%,transparent)}}.hx\:hover\:bg-primary-700:hover{background-color:var(--hx-color-primary-700)}.hx\:hover\:bg-slate-50:hover{background-color:var(--hx-color-slate-50)}.hx\:hover\:text-black:hover{color:var(--hx-color-black)}.hx\:hover\:text-gray-700:hover{color:var(--hx-color-gray-700)}.hx\:hover\:text-gray-800:hover{color:var(--hx-color-gray-800)}.hx\:hover\:text-gray-900:hover{color:var(--hx-color-gray-900)}.hx\:hover\:text-primary-600:hover{color:var(--hx-color-primary-600)}.hx\:hover\:opacity-60:hover{opacity:.6}.hx\:hover\:opacity-75:hover{opacity:.75}.hx\:hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:var(--hx-color-gray-100)}@supports (color:color-mix(in lab, red, red)){.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}}.hx\:focus-visible\:not-sr-only:focus-visible{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.hx\:focus-visible\:fixed:focus-visible{position:fixed}.hx\:focus-visible\:top-2:focus-visible{top:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:left-2:focus-visible{left:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:z-50:focus-visible{z-index:50}.hx\:focus-visible\:rounded-md:focus-visible{border-radius:var(--hx-radius-md)}.hx\:focus-visible\:bg-primary-500:focus-visible{background-color:var(--hx-color-primary-500)}.hx\:focus-visible\:bg-white:focus-visible{background-color:var(--hx-color-white)}.hx\:focus-visible\:px-4:focus-visible{padding-inline:calc(var(--hx-spacing)*4)}.hx\:focus-visible\:py-2:focus-visible{padding-block:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:text-sm:focus-visible{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:focus-visible\:font-medium:focus-visible{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:focus-visible\:text-white:focus-visible{color:var(--hx-color-white)}.hx\:active\:bg-gray-400\/20:active{background-color:var(--hx-color-gray-400)}@supports (color:color-mix(in lab, red, red)){.hx\:active\:bg-gray-400\/20:active{background-color:color-mix(in oklab,var(--hx-color-gray-400)20%,transparent)}}.hx\:active\:opacity-50:active{opacity:.5}.hx\:active\:shadow-sm:active{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:active\:shadow-gray-200:active{--tw-shadow-color:var(--hx-color-gray-200)}@supports (color:color-mix(in lab, red, red)){.hx\:active\:shadow-gray-200:active{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-200)var(--tw-shadow-alpha),transparent)}}.hx\:data-\[state\=closed\]\:hidden[data-state=closed],.hx\:data-\[state\=open\]\:hidden[data-state=open]{display:none}.hx\:data-\[state\=selected\]\:block[data-state=selected]{display:block}.hx\:data-\[state\=selected\]\:border-primary-500[data-state=selected]{border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:text-primary-600[data-state=selected]{color:var(--hx-color-primary-600)}@media (prefers-contrast:more){.hx\:contrast-more\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:contrast-more\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:contrast-more\:border-current{border-color:currentColor}.hx\:contrast-more\:border-gray-800{border-color:var(--hx-color-gray-800)}.hx\:contrast-more\:border-gray-900{border-color:var(--hx-color-gray-900)}.hx\:contrast-more\:border-neutral-400{border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:border-primary-500{border-color:var(--hx-color-primary-500)}.hx\:contrast-more\:border-transparent{border-color:#0000}.hx\:contrast-more\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:contrast-more\:text-current{color:currentColor}.hx\:contrast-more\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:contrast-more\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:contrast-more\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:contrast-more\:underline{text-decoration-line:underline}.hx\:contrast-more\:shadow-\[0_0_0_1px_\#000\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.hx\:contrast-more\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}}}@media not all and (min-width:80rem){.hx\:max-xl\:hidden{display:none}}@media not all and (min-width:64rem){.hx\:max-lg\:min-h-\[340px\]{min-height:340px}}@media not all and (min-width:48rem){.hx\:max-md\:sticky{position:sticky}.hx\:max-md\:hidden{display:none}.hx\:max-md\:min-h-\[340px\]{min-height:340px}.hx\:max-md\:\[transform\:translate3d\(0\,-100\%\,0\)\]{transform:translateY(-100%)}.hx\:max-md\:\[transform\:translate3d\(0\,0\,0\)\]{transform:translate(0)}}@media not all and (min-width:40rem){.hx\:max-sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}}@media (min-width:40rem){.hx\:sm\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:sm\:left-auto{left:auto}.hx\:sm\:block{display:block}.hx\:sm\:flex{display:flex}.hx\:sm\:w-\[110\%\]{width:110%}.hx\:sm\:flex-row{flex-direction:row}.hx\:sm\:items-center{align-items:center}.hx\:sm\:items-start{align-items:flex-start}.hx\:sm\:justify-between{justify-content:space-between}.hx\:sm\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}@media not all and (min-width:64rem){.hx\:sm\:max-lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@media (min-width:48rem){.hx\:md\:sticky{position:sticky}.hx\:md\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:md\:mr-0{margin-right:calc(var(--hx-spacing)*0)}.hx\:md\:hidden{display:none}.hx\:md\:inline-block{display:inline-block}.hx\:md\:inline-flex{display:inline-flex}.hx\:md\:aspect-\[1\.1\/1\]{aspect-ratio:1.1}.hx\:md\:h-\[calc\(100vh-var\(--navbar-height\)-var\(--menu-height\)\)\]{height:calc(100vh - var(--navbar-height) - var(--menu-height))}.hx\:md\:max-h-\[min\(calc\(100vh-5rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(100vh - 5rem - env(safe-area-inset-bottom)),400px)}.hx\:md\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:md\:shrink-0{flex-shrink:0}.hx\:md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.hx\:md\:justify-start{justify-content:flex-start}.hx\:md\:self-start{align-self:flex-start}.hx\:md\:overflow-auto{overflow:auto}.hx\:md\:px-12{padding-inline:calc(var(--hx-spacing)*12)}.hx\:md\:pt-12{padding-top:calc(var(--hx-spacing)*12)}.hx\:md\:text-3xl{font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height))}.hx\:md\:text-5xl{font-size:var(--hx-text-5xl);line-height:var(--tw-leading,var(--hx-text-5xl--line-height))}.hx\:md\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:md\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}}@media (min-width:64rem){.hx\:lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:80rem){.hx\:xl\:block{display:block}.hx\:xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.hx\:ltr\:right-1\.5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*1.5)}.hx\:ltr\:right-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*2)}.hx\:ltr\:right-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*3)}.hx\:ltr\:-mr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:calc(var(--hx-spacing)*-4)}.hx\:ltr\:mr-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:auto}.hx\:ltr\:ml-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.hx\:ltr\:ml-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:ml-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:auto}.hx\:ltr\:rotate-180:where(:dir(ltr),[dir=ltr],[dir=ltr] *){rotate:180deg}.hx\:ltr\:rounded-l-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:ltr\:rounded-r-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:ltr\:border-l:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);border-left-width:1px}.hx\:ltr\:pr-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*0)}.hx\:ltr\:pr-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*1)}.hx\:ltr\:pr-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*2)}.hx\:ltr\:pr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:ltr\:pr-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:ltr\:pr-9:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*9)}.hx\:ltr\:pl-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:pl-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:ltr\:pl-5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*5)}.hx\:ltr\:pl-6:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*6)}.hx\:ltr\:pl-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:ltr\:pl-12:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*12)}.hx\:ltr\:pl-16:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*16)}.hx\:ltr\:text-right:where(:dir(ltr),[dir=ltr],[dir=ltr] *){text-align:right}.hx\:ltr\:before\:left-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);left:calc(var(--hx-spacing)*0)}@media (min-width:48rem){.hx\:ltr\:md\:left-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:auto}}.hx\:rtl\:left-1\.5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*1.5)}.hx\:rtl\:left-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*2)}.hx\:rtl\:left-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}.hx\:rtl\:mr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto}.hx\:rtl\:-ml-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:calc(var(--hx-spacing)*-4)}.hx\:rtl\:ml-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:auto}.hx\:rtl\:-rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:-180deg}.hx\:rtl\:rotate-270:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:270deg}.hx\:rtl\:rounded-l-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:rtl\:rounded-r-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:rtl\:border-r:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:1px}.hx\:rtl\:pr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:pr-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:rtl\:pr-5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*5)}.hx\:rtl\:pr-6:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*6)}.hx\:rtl\:pr-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:rtl\:pr-12:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*12)}.hx\:rtl\:pr-16:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*16)}.hx\:rtl\:pl-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*0)}.hx\:rtl\:pl-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*1)}.hx\:rtl\:pl-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*2)}.hx\:rtl\:pl-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:rtl\:pl-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:rtl\:pl-9:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*9)}.hx\:rtl\:text-left:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:left}.hx\:rtl\:before\:right-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);right:calc(var(--hx-spacing)*0)}.hx\:rtl\:before\:rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);rotate:180deg}@media (min-width:48rem){.hx\:rtl\:md\:right-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:auto}}.hx\:dark\:block:where(.dark,.dark *){display:block}.hx\:dark\:hidden:where(.dark,.dark *){display:none}.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:var(--hx-color-amber-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-amber-200)30%,transparent)}}.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:var(--hx-color-blue-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-blue-200)30%,transparent)}}.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:var(--hx-color-gray-100)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-gray-100)20%,transparent)}}.hx\:dark\:border-gray-400:where(.dark,.dark *){border-color:var(--hx-color-gray-400)}.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:var(--hx-color-green-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-green-200)30%,transparent)}}.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:var(--hx-color-indigo-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-indigo-200)30%,transparent)}}.hx\:dark\:border-neutral-700:where(.dark,.dark *){border-color:var(--hx-color-neutral-700)}.hx\:dark\:border-neutral-800:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:var(--hx-color-orange-400)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-orange-400)30%,transparent)}}.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:var(--hx-color-purple-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-purple-200)30%,transparent)}}.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:var(--hx-color-red-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-red-200)30%,transparent)}}.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:var(--hx-color-yellow-200)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-yellow-200)30%,transparent)}}.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:var(--hx-color-amber-900)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-amber-900)30%,transparent)}}.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:var(--hx-color-blue-900)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-blue-900)30%,transparent)}}.hx\:dark\:bg-dark:where(.dark,.dark *),.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:var(--hx-color-dark)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)50%,transparent)}}.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:var(--hx-color-gray-50)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-50)10%,transparent)}}.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:var(--hx-color-green-900)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-green-900)30%,transparent)}}.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:var(--hx-color-indigo-900)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-indigo-900)30%,transparent)}}.hx\:dark\:bg-neutral-700:where(.dark,.dark *){background-color:var(--hx-color-neutral-700)}.hx\:dark\:bg-neutral-800:where(.dark,.dark *){background-color:var(--hx-color-neutral-800)}.hx\:dark\:bg-neutral-900:where(.dark,.dark *){background-color:var(--hx-color-neutral-900)}.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:var(--hx-color-orange-400)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-orange-400)20%,transparent)}}.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-400)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-400)10%,transparent)}}.hx\:dark\:bg-primary-600:where(.dark,.dark *){background-color:var(--hx-color-primary-600)}.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:var(--hx-color-purple-900)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-purple-900)30%,transparent)}}.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:var(--hx-color-red-900)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-red-900)30%,transparent)}}.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:var(--hx-color-yellow-700)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-yellow-700)30%,transparent)}}.hx\:dark\:from-gray-100:where(.dark,.dark *){--tw-gradient-from:var(--hx-color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:to-gray-400:where(.dark,.dark *){--tw-gradient-to:var(--hx-color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:text-amber-200:where(.dark,.dark *){color:var(--hx-color-amber-200)}.hx\:dark\:text-blue-200:where(.dark,.dark *){color:var(--hx-color-blue-200)}.hx\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:dark\:text-gray-200:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hx\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:dark\:text-gray-400:where(.dark,.dark *){color:var(--hx-color-gray-400)}.hx\:dark\:text-green-200:where(.dark,.dark *){color:var(--hx-color-green-200)}.hx\:dark\:text-indigo-200:where(.dark,.dark *){color:var(--hx-color-indigo-200)}.hx\:dark\:text-neutral-200:where(.dark,.dark *){color:var(--hx-color-neutral-200)}.hx\:dark\:text-neutral-400:where(.dark,.dark *){color:var(--hx-color-neutral-400)}.hx\:dark\:text-orange-300:where(.dark,.dark *){color:var(--hx-color-orange-300)}.hx\:dark\:text-primary-600:where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:text-purple-200:where(.dark,.dark *){color:var(--hx-color-purple-200)}.hx\:dark\:text-red-200:where(.dark,.dark *){color:var(--hx-color-red-200)}.hx\:dark\:text-slate-100:where(.dark,.dark *){color:var(--hx-color-slate-100)}.hx\:dark\:text-white:where(.dark,.dark *){color:var(--hx-color-white)}.hx\:dark\:text-yellow-200:where(.dark,.dark *){color:var(--hx-color-yellow-200)}.hx\:dark\:opacity-80:where(.dark,.dark *){opacity:.8}.hx\:dark\:shadow-\[0_-1px_0_rgba\(255\,255\,255\,\.1\)_inset\]:where(.dark,.dark *){--tw-shadow:0 -1px 0 var(--tw-shadow-color,#ffffff1a)inset;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-\[0_-12px_16px_\#111\]:where(.dark,.dark *){--tw-shadow:0 -12px 16px var(--tw-shadow-color,#111);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.hx\:dark\:group-hover\:text-gray-50:where(.dark,.dark *):is(:where(.hx\:group):hover *){color:var(--hx-color-gray-50)}}.hx\:group-data-\[active\=true\]\:dark\:text-primary-600:is(:where(.hx\:group)[data-active=true] *):where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:placeholder\:text-gray-400:where(.dark,.dark *)::placeholder{color:var(--hx-color-gray-400)}.hx\:dark\:before\:bg-neutral-800:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hx\:dark\:before\:invert:where(.dark,.dark *):before{content:var(--tw-content);--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media (hover:hover){.hx\:dark\:hover\:border-gray-100:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-100)}.hx\:dark\:hover\:border-gray-600:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-600)}.hx\:dark\:hover\:border-neutral-500:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-500)}.hx\:dark\:hover\:border-neutral-700:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:border-neutral-800:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-gray-100)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-gray-100)5%,transparent)}}.hx\:dark\:hover\:bg-neutral-700:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:bg-neutral-800:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-neutral-900:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-900)}.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-100)}@supports (color:color-mix(in lab, red, red)){.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-primary-100)5%,transparent)}}.hx\:dark\:hover\:bg-primary-700:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-700)}.hx\:dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--hx-color-gray-50)}.hx\:dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--hx-color-gray-100)}.hx\:dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--hx-color-gray-200)}.hx\:dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--hx-color-gray-300)}.hx\:dark\:hover\:text-neutral-50:where(.dark,.dark *):hover{color:var(--hx-color-neutral-50)}.hx\:dark\:hover\:text-primary-400:where(.dark,.dark *):hover{color:var(--hx-color-primary-400)}.hx\:dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--hx-color-white)}.hx\:dark\:hover\:shadow-none:where(.dark,.dark *):hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.hx\:dark\:focus-visible\:bg-dark:where(.dark,.dark *):focus-visible{background-color:var(--hx-color-dark)}.hx\:data-\[state\=selected\]\:dark\:border-primary-500[data-state=selected]:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:dark\:text-primary-600[data-state=selected]:where(.dark,.dark *){color:var(--hx-color-primary-600)}@media (prefers-contrast:more){.hx\:contrast-more\:dark\:border-current:where(.dark,.dark *){border-color:currentColor}.hx\:contrast-more\:dark\:border-gray-50:where(.dark,.dark *){border-color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:border-primary-500:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:dark\:contrast-more\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:text-current:where(.dark,.dark *){color:currentColor}.hx\:contrast-more\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:contrast-more\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:contrast-more\:dark\:shadow-\[0_0_0_1px_\#fff\]:where(.dark,.dark *){--tw-shadow:0 0 0 1px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media (hover:hover){.hx\:contrast-more\:dark\:hover\:border-gray-50:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-50)}}}@media print{.hx\:print\:\[display\:none\],.hx\:print\:hidden{display:none}.hx\:print\:bg-transparent{background-color:#0000}}}html{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--hx-color-white);width:100%}body:where(.dark,.dark *){background-color:var(--hx-color-dark);color:var(--hx-color-gray-100)}:root{--primary-hue:212deg;--primary-saturation:100%;--primary-lightness:50%;--navbar-height:4rem;--hextra-banner-height:2rem;--menu-height:3.75rem}.dark{--primary-hue:204deg;--primary-saturation:100%;--primary-lightness:50%}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*2);font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height));--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-neutral-200)}@supports (color:color-mix(in lab, red, red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-neutral-200)70%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){padding-bottom:calc(var(--hx-spacing)*1);font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}@media (prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--hx-color-neutral-400)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports (color:color-mix(in lab, red, red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)10%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}@media (prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--hx-color-primary-600);text-decoration-line:underline;text-decoration-thickness:from-font}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);border-color:var(--hx-color-gray-300);color:var(--hx-color-gray-700);font-style:italic}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);padding-left:calc(var(--hx-spacing)*6);border-left-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);padding-right:calc(var(--hx-spacing)*6);border-right-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-gray-700);color:var(--hx-color-gray-400)}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);overflow-x:auto}@supports (color:color-mix(in lab, red, red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){padding-block:calc(var(--hx-spacing)*4);--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media (prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports (color:color-mix(in lab, red, red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports (color:color-mix(in lab, red, red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media (prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports (color:color-mix(in lab, red, red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*6);border-collapse:collapse;width:100%;padding:calc(var(--hx-spacing)*0);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);display:block;overflow-x:auto}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead{background-color:var(--hx-color-gray-50)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:var(--hx-color-gray-600)}@supports (color:color-mix(in lab, red, red)){.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-600)20%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr{margin:calc(var(--hx-spacing)*0);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:decimal}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:disc}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):has(li input[type=checkbox]){list-style-type:none}.content :where(ul,ol)>li>:where(ul,ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*0)}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:#0000}.content :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-inline:auto;margin-block:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-md)}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption{margin-top:calc(var(--hx-spacing)*2);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-500);display:block}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption:where(.dark,.dark *){color:var(--hx-color-gray-400)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dt{margin-top:calc(var(--hx-spacing)*6);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dd{margin-block:calc(var(--hx-spacing)*2);padding-inline-start:calc(var(--hx-spacing)*6)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*10);border-color:var(--hx-color-gray-200)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):last-child{margin-bottom:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .footnotes{margin-top:calc(var(--hx-spacing)*12);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.content .footnotes hr{border-color:var(--hx-color-gray-200)}.content .footnotes hr:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .subheading-anchor{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.content .subheading-anchor:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.content .subheading-anchor:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}span:target+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor),.content .subheading-anchor:focus-visible{opacity:1}span+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor){text-decoration-line:none!important}.content .subheading-anchor:after{content:var(--tw-content);color:var(--hx-color-gray-300)}.content .subheading-anchor:where(.dark,.dark *):after{content:var(--tw-content);color:var(--hx-color-neutral-700)}.content .subheading-anchor:after{padding-inline:calc(var(--hx-spacing)*1);--tw-content:"#";content:var(--tw-content)}span:target+:is(){color:var(--hx-color-gray-400)}span:target+:is():where(.dark,.dark *){color:var(--hx-color-neutral-500)}article details>summary::-webkit-details-marker{display:none}article details>summary:before{vertical-align:-4px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='hx:h-5 hx:w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E");width:1.2em;height:1.2em;padding:0 .6em}:lang(fa) ol{list-style-type:persian}.highlight .chroma .err{color:#a61717;background-color:#e3d2d2}.highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.highlight .chroma .line{display:flex}.highlight .chroma .k,.highlight .chroma .kc,.highlight .chroma .kd,.highlight .chroma .kn,.highlight .chroma .kp,.highlight .chroma .kr{color:#000;font-weight:700}.highlight .chroma .kt{color:#458;font-weight:700}.highlight .chroma .na{color:teal}.highlight .chroma .nb{color:#0086b3}.highlight .chroma .bp{color:#999}.highlight .chroma .nc{color:#458;font-weight:700}.highlight .chroma .no{color:teal}.highlight .chroma .nd{color:#3c5d5d;font-weight:700}.highlight .chroma .ni{color:purple}.highlight .chroma .ne,.highlight .chroma .nf,.highlight .chroma .nl{color:#900;font-weight:700}.highlight .chroma .nn{color:#555}.highlight .chroma .nt{color:navy}.highlight .chroma .nv,.highlight .chroma .vc,.highlight .chroma .vg,.highlight .chroma .vi{color:teal}.highlight .chroma .s,.highlight .chroma .sa,.highlight .chroma .sb,.highlight .chroma .sc,.highlight .chroma .dl,.highlight .chroma .sd,.highlight .chroma .s2,.highlight .chroma .se,.highlight .chroma .sh,.highlight .chroma .si,.highlight .chroma .sx{color:#d14}.highlight .chroma .sr{color:#009926}.highlight .chroma .s1{color:#d14}.highlight .chroma .ss{color:#990073}.highlight .chroma .m,.highlight .chroma .mb,.highlight .chroma .mf,.highlight .chroma .mh,.highlight .chroma .mi,.highlight .chroma .il,.highlight .chroma .mo{color:#099}.highlight .chroma .o,.highlight .chroma .ow{color:#000;font-weight:700}.highlight .chroma .c,.highlight .chroma .ch,.highlight .chroma .cm,.highlight .chroma .c1{color:#998;font-style:italic}.highlight .chroma .cs,.highlight .chroma .cp,.highlight .chroma .cpf{color:#999;font-style:italic;font-weight:700}.highlight .chroma .gd{color:#000;background-color:#fdd}.highlight .chroma .ge{color:#000;font-style:italic}.highlight .chroma .gr{color:#a00}.highlight .chroma .gh{color:#999}.highlight .chroma .gi{color:#000;background-color:#dfd}.highlight .chroma .go{color:#888}.highlight .chroma .gp{color:#555}.highlight .chroma .gs{font-weight:700}.highlight .chroma .gu{color:#aaa}.highlight .chroma .gt{color:#a00}.highlight .chroma .gl{text-decoration:underline}.highlight .chroma .w{color:#bbb}.dark .highlight .chroma .err{color:#f85149}.dark .highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.dark .highlight .chroma .line{display:flex}.dark .highlight .chroma .k{color:#ff7b72}.dark .highlight .chroma .kc{color:#79c0ff}.dark .highlight .chroma .kd,.dark .highlight .chroma .kn{color:#ff7b72}.dark .highlight .chroma .kp{color:#79c0ff}.dark .highlight .chroma .kr,.dark .highlight .chroma .kt{color:#ff7b72}.dark .highlight .chroma .nc{color:#f0883e;font-weight:700}.dark .highlight .chroma .no{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nd{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .ni{color:#ffa657}.dark .highlight .chroma .ne{color:#f0883e;font-weight:700}.dark .highlight .chroma .nf{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .nl{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nn{color:#ff7b72}.dark .highlight .chroma .py{color:#79c0ff}.dark .highlight .chroma .nt{color:#7ee787}.dark .highlight .chroma .nv{color:#79c0ff}.dark .highlight .chroma .l{color:#a5d6ff}.dark .highlight .chroma .ld{color:#79c0ff}.dark .highlight .chroma .s{color:#a5d6ff}.dark .highlight .chroma .sa{color:#79c0ff}.dark .highlight .chroma .sb,.dark .highlight .chroma .sc{color:#a5d6ff}.dark .highlight .chroma .dl{color:#79c0ff}.dark .highlight .chroma .sd,.dark .highlight .chroma .s2{color:#a5d6ff}.dark .highlight .chroma .se,.dark .highlight .chroma .sh{color:#79c0ff}.dark .highlight .chroma .si,.dark .highlight .chroma .sx{color:#a5d6ff}.dark .highlight .chroma .sr{color:#79c0ff}.dark .highlight .chroma .s1,.dark .highlight .chroma .ss,.dark .highlight .chroma .m,.dark .highlight .chroma .mb,.dark .highlight .chroma .mf,.dark .highlight .chroma .mh,.dark .highlight .chroma .mi,.dark .highlight .chroma .il,.dark .highlight .chroma .mo{color:#a5d6ff}.dark .highlight .chroma .o,.dark .highlight .chroma .ow{color:#ff7b72;font-weight:700}.dark .highlight .chroma .c,.dark .highlight .chroma .ch,.dark .highlight .chroma .cm,.dark .highlight .chroma .c1{color:#8b949e;font-style:italic}.dark .highlight .chroma .cs,.dark .highlight .chroma .cp,.dark .highlight .chroma .cpf{color:#8b949e;font-style:italic;font-weight:700}.dark .highlight .chroma .gd{color:#ffa198;background-color:#490202}.dark .highlight .chroma .ge{color:inherit;font-style:italic}.dark .highlight .chroma .gr{color:#ffa198}.dark .highlight .chroma .gh{color:#79c0ff;font-weight:700}.dark .highlight .chroma .gi{color:#56d364;background-color:#0f5323}.dark .highlight .chroma .go,.dark .highlight .chroma .gp{color:#8b949e}.dark .highlight .chroma .gs{font-weight:700}.dark .highlight .chroma .gu{color:#79c0ff}.dark .highlight .chroma .gt{color:#ff7b72}.dark .highlight .chroma .gl{text-decoration:underline}.dark .highlight .chroma .w{color:#6e7681}.hextra-code-block{--tw-leading:calc(var(--hx-spacing)*5);font-size:.9em;line-height:calc(var(--hx-spacing)*5)}.hextra-code-block pre{background-color:var(--hx-color-primary-700);overflow-x:auto}@supports (color:color-mix(in lab, red, red)){.hextra-code-block pre{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block pre{--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media (prefers-contrast:more){.hextra-code-block pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports (color:color-mix(in lab, red, red)){.hextra-code-block pre{border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.hextra-code-block pre{--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.hextra-code-block pre:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports (color:color-mix(in lab, red, red)){.hextra-code-block pre:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media (prefers-contrast:more){.hextra-code-block pre:where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports (color:color-mix(in lab, red, red)){.hextra-code-block pre:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.hextra-code-block .hextra-code-filename{top:calc(var(--hx-spacing)*0);z-index:1;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:var(--hx-radius-xl);border-top-right-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);width:100%;position:absolute;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.hextra-code-block .hextra-code-filename{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block .hextra-code-filename{padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*2);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));color:var(--hx-color-gray-700)}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports (color:color-mix(in lab, red, red)){.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hextra-code-block .hextra-code-filename+pre:not(.lntable pre){padding-top:calc(var(--hx-spacing)*12)}.hextra-code-block pre:not(.lntable pre){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*4)}.hextra-code-block div:nth-of-type(2) pre{padding-top:calc(var(--hx-spacing)*12);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .lntable{margin:calc(var(--hx-spacing)*0);border-radius:var(--hx-radius-xl);width:auto;display:block;overflow:auto}.chroma .lntable pre{padding-top:calc(var(--hx-spacing)*4);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line){min-width:2.6rem;padding-right:calc(var(--hx-spacing)*4);padding-left:calc(var(--hx-spacing)*4);color:var(--hx-color-neutral-600)}:is(.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line)):where(.dark,.dark *){color:var(--hx-color-neutral-300)}.chroma .lntd{padding:calc(var(--hx-spacing)*0);vertical-align:top}.chroma .lntd:last-of-type{width:100%}.chroma .hl{background-color:var(--hx-color-primary-800);width:100%;display:block}@supports (color:color-mix(in lab, red, red)){.chroma .hl{background-color:color-mix(in oklab,var(--hx-color-primary-800)10%,transparent)}}.hextra-cards{grid-template-columns:repeat(auto-fill,minmax(max(250px,calc((100% - 1rem*2)/var(--hextra-cards-grid-cols))),1fr))}.hextra-card{position:relative}.hextra-card img{-webkit-user-select:none;user-select:none}.hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card .hextra-card-icon svg{color:#0003;width:1.5rem;transition:color .3s}.hextra-card p{margin-top:.5rem;position:relative}.dark .hextra-card .hextra-card-icon svg{color:#fff6}.dark .hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card-tag{z-index:10;position:absolute;top:5px}.hextra-card-tag:where(:dir(ltr)){right:5px}.hextra-card-tag:where(:dir(rtl)){left:5px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker){counter-increment:step}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);margin-left:-41px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);margin-right:-44px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);background-color:var(--hx-color-gray-100)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:4px;border-color:var(--hx-color-white)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);border-color:var(--hx-color-dark)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step);text-align:center;text-indent:-1px;width:33px;height:33px;font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal);color:var(--hx-color-neutral-400);border-radius:3.40282e38px;position:absolute}:lang(fa) .hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step,persian)}.hextra-search-wrapper li{margin-inline:calc(var(--hx-spacing)*2.5);border-radius:var(--hx-radius-md);overflow-wrap:break-word;color:var(--hx-color-gray-800)}@media (prefers-contrast:more){.hextra-search-wrapper li{border-style:var(--tw-border-style);border-width:1px;border-color:#0000}}.hextra-search-wrapper li:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hextra-search-wrapper li a{scroll-margin:calc(var(--hx-spacing)*12);padding-inline:calc(var(--hx-spacing)*2.5);padding-block:calc(var(--hx-spacing)*2);display:block}.hextra-search-wrapper li a:focus-visible{--tw-outline-style:none;outline-style:none}.hextra-search-wrapper li .hextra-search-title{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hextra-search-wrapper li .hextra-search-active{border-radius:var(--hx-radius-md);background-color:var(--hx-color-primary-500)}@supports (color:color-mix(in lab, red, red)){.hextra-search-wrapper li .hextra-search-active{background-color:color-mix(in oklab,var(--hx-color-primary-500)10%,transparent)}}@media (prefers-contrast:more){.hextra-search-wrapper li .hextra-search-active{border-color:var(--hx-color-primary-500)}}.hextra-search-wrapper .hextra-search-no-result{padding:calc(var(--hx-spacing)*8);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-400);-webkit-user-select:none;user-select:none;display:block}.hextra-search-wrapper .hextra-search-prefix{margin-inline:calc(var(--hx-spacing)*2.5);margin-top:calc(var(--hx-spacing)*6);margin-bottom:calc(var(--hx-spacing)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.hextra-search-wrapper .hextra-search-prefix{border-color:color-mix(in oklab,var(--hx-color-black)10%,transparent)}}.hextra-search-wrapper .hextra-search-prefix{padding-inline:calc(var(--hx-spacing)*2.5);padding-bottom:calc(var(--hx-spacing)*1.5);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);color:var(--hx-color-gray-500);text-transform:uppercase;-webkit-user-select:none;user-select:none}.hextra-search-wrapper .hextra-search-prefix:first-child{margin-top:calc(var(--hx-spacing)*0)}@media (prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix{border-color:var(--hx-color-gray-600);color:var(--hx-color-gray-900)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)20%,transparent)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){color:var(--hx-color-gray-300)}@media (prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-gray-50);color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{margin-top:calc(var(--hx-spacing)*1);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:1.35rem;text-overflow:ellipsis;color:var(--hx-color-gray-600);line-height:1.35rem;overflow:hidden}.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-400)}@media (prefers-contrast:more){.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box}.hextra-search-wrapper .hextra-search-match{color:var(--hx-color-primary-600)}@media (max-width:48rem){.hextra-sidebar-container{top:calc(var(--hx-spacing)*0);bottom:calc(var(--hx-spacing)*0);z-index:15;overscroll-behavior:contain;background-color:var(--hx-color-white);width:100%;padding-top:calc(var(--navbar-height) + var(--hextra-banner-height));position:fixed}.hextra-sidebar-container:where(.dark,.dark *){background-color:var(--hx-color-dark)}.hextra-sidebar-container{will-change:transform,opacity;contain:layout style;backface-visibility:hidden;transition:transform .4s cubic-bezier(.52,.16,.04,1)}}.hextra-sidebar-container li>.hextra-sidebar-children{height:calc(var(--hx-spacing)*0)}.hextra-sidebar-container li.open>.hextra-sidebar-children{height:auto;padding-top:calc(var(--hx-spacing)*1)}.hextra-sidebar-container li.open>.hextra-sidebar-item>.hextra-sidebar-collapsible-button>svg>path{rotate:90deg}.hextra-banner-hidden .hextra-banner{display:none}.hextra-banner :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:underline;text-decoration-thickness:from-font}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}nav .hextra-search-wrapper{display:none}@media (min-width:48rem){nav .hextra-search-wrapper{display:inline-block}}@supports ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-nav-container-blur{background-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.hextra-nav-container-blur{background-color:color-mix(in oklab,var(--hx-color-white)85%,transparent)}}.hextra-nav-container-blur{--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-nav-container-blur:where(.dark,.dark *){background-color:var(--hx-color-dark)!important}@supports (color:color-mix(in lab, red, red)){.hextra-nav-container-blur:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)80%,transparent)!important}}}.hextra-hamburger-menu svg g{transform-origin:50%;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg path{opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open path{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:0s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open g{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open>path{opacity:0}.hextra-hamburger-menu svg.open>g:first-of-type{rotate:45deg}.hextra-hamburger-menu svg.open>g:first-of-type path{--tw-translate-y:calc(var(--hx-spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-hamburger-menu svg.open>g:nth-of-type(2){rotate:-45deg}.hextra-hamburger-menu svg.open>g:nth-of-type(2) path{--tw-translate-y:calc(var(--hx-spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-scrollbar,.hextra-scrollbar *{scrollbar-width:thin;scrollbar-color:oklch(55.55% 0 0/.4) transparent;scrollbar-gutter:stable}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar{height:calc(var(--hx-spacing)*3);width:calc(var(--hx-spacing)*3)}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-track{background-color:#0000}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-thumb{border-radius:10px}:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{background-color:var(--tw-shadow-color);--tw-shadow-color:var(--hx-color-neutral-500);background-clip:content-box;border:3px solid #0000}@supports (color:color-mix(in lab, red, red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}@media (hover:hover){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:var(--hx-color-neutral-500)}@supports (color:color-mix(in lab, red, red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)40%,transparent)var(--tw-shadow-alpha),transparent)}}}@supports ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-code-copy-btn{opacity:.85;--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-code-copy-btn:where(.dark,.dark *){opacity:.8}}@media (min-width:1024px){.hextra-feature-grid{grid-template-columns:repeat(var(--hextra-feature-grid-cols),minmax(0,1fr))}}.hextra-jupyter-code-cell{scrollbar-gutter:auto;margin-top:calc(var(--hx-spacing)*6)}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:hidden}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs{max-height:50vh;overflow:auto}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs pre{max-width:100%;font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:auto}.hextra-badge{align-items:center;display:inline-flex}.hextra-toc a.hextra-toc-active{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.2s;transition-duration:.2s;color:var(--hx-color-gray-900)!important}.hextra-toc a.hextra-toc-active:where(.dark,.dark *){color:var(--hx-color-gray-50)!important}.hextra-archive-timeline{border-left-style:var(--tw-border-style);border-left-width:2px;border-color:var(--hx-color-black)}@supports (color:color-mix(in lab, red, red)){.hextra-archive-timeline{border-color:color-mix(in oklab,var(--hx-color-black)15%,transparent)}}.hextra-archive-timeline:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports (color:color-mix(in lab, red, red)){.hextra-archive-timeline:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)15%,transparent)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false} diff --git a/public/css/compiled/main.min.1a431501cdeb52323c920e7761f7d9e327b2bee53160196c4bd1d34a3fd9fbc9.css b/public/css/compiled/main.min.1a431501cdeb52323c920e7761f7d9e327b2bee53160196c4bd1d34a3fd9fbc9.css new file mode 100644 index 0000000..63a6651 --- /dev/null +++ b/public/css/compiled/main.min.1a431501cdeb52323c920e7761f7d9e327b2bee53160196c4bd1d34a3fd9fbc9.css @@ -0,0 +1 @@ +:root{--hextra-max-page-width:80rem;--hextra-max-content-width:72rem;--hextra-max-navbar-width:80rem;--hextra-max-footer-width:80rem}.hextra-max-page-width{max-width:var(--hextra-max-page-width)}.hextra-max-content-width{max-width:var(--hextra-max-content-width)}.hextra-max-navbar-width{max-width:var(--hextra-max-navbar-width)}.hextra-max-footer-width{max-width:var(--hextra-max-footer-width)}/*!tailwindcss v4.1.18 | MIT License | https://tailwindcss.com*/@layer properties{@supports(((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--hx-font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--hx-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--hx-color-red-100:oklch(93.6% .032 17.717);--hx-color-red-200:oklch(88.5% .062 18.334);--hx-color-red-900:oklch(39.6% .141 25.723);--hx-color-orange-50:oklch(98% .016 73.684);--hx-color-orange-100:oklch(95.4% .038 75.164);--hx-color-orange-300:oklch(83.7% .128 66.29);--hx-color-orange-400:oklch(75% .183 55.934);--hx-color-orange-800:oklch(47% .157 37.304);--hx-color-amber-100:oklch(96.2% .059 95.617);--hx-color-amber-200:oklch(92.4% .12 95.746);--hx-color-amber-900:oklch(41.4% .112 45.904);--hx-color-yellow-50:oklch(98.7% .026 102.212);--hx-color-yellow-100:oklch(97.3% .071 103.193);--hx-color-yellow-200:oklch(94.5% .129 101.54);--hx-color-yellow-700:oklch(55.4% .135 66.442);--hx-color-yellow-900:oklch(42.1% .095 57.708);--hx-color-green-100:oklch(96.2% .044 156.743);--hx-color-green-200:oklch(92.5% .084 155.995);--hx-color-green-900:oklch(39.3% .095 152.535);--hx-color-blue-100:oklch(93.2% .032 255.585);--hx-color-blue-200:oklch(88.2% .059 254.128);--hx-color-blue-900:oklch(37.9% .146 265.522);--hx-color-indigo-100:oklch(93% .034 272.788);--hx-color-indigo-200:oklch(87% .065 274.039);--hx-color-indigo-900:oklch(35.9% .144 278.697);--hx-color-purple-100:oklch(94.6% .033 307.174);--hx-color-purple-200:oklch(90.2% .063 306.703);--hx-color-purple-900:oklch(38.1% .176 304.987);--hx-color-slate-50:oklch(98.4% .003 247.858);--hx-color-slate-100:oklch(96.8% .007 247.896);--hx-color-slate-900:oklch(20.8% .042 265.755);--hx-color-gray-50:oklch(98.5% .002 247.839);--hx-color-gray-100:oklch(96.7% .003 264.542);--hx-color-gray-200:oklch(92.8% .006 264.531);--hx-color-gray-300:oklch(87.2% .01 258.338);--hx-color-gray-400:oklch(70.7% .022 261.325);--hx-color-gray-500:oklch(55.1% .027 264.364);--hx-color-gray-600:oklch(44.6% .03 256.802);--hx-color-gray-700:oklch(37.3% .034 259.733);--hx-color-gray-800:oklch(27.8% .033 256.848);--hx-color-gray-900:oklch(21% .034 264.665);--hx-color-neutral-50:oklch(98.5% 0 0);--hx-color-neutral-200:oklch(92.2% 0 0);--hx-color-neutral-300:oklch(87% 0 0);--hx-color-neutral-400:oklch(70.8% 0 0);--hx-color-neutral-500:oklch(55.6% 0 0);--hx-color-neutral-600:oklch(43.9% 0 0);--hx-color-neutral-700:oklch(37.1% 0 0);--hx-color-neutral-800:oklch(26.9% 0 0);--hx-color-neutral-900:oklch(20.5% 0 0);--hx-color-black:#000;--hx-color-white:#fff;--hx-spacing:.25rem;--hx-breakpoint-xl:80rem;--hx-text-xs:.75rem;--hx-text-xs--line-height:calc(1/.75);--hx-text-sm:.875rem;--hx-text-sm--line-height:calc(1.25/.875);--hx-text-base:1rem;--hx-text-base--line-height:calc(1.5/1);--hx-text-lg:1.125rem;--hx-text-lg--line-height:calc(1.75/1.125);--hx-text-xl:1.25rem;--hx-text-xl--line-height:calc(1.75/1.25);--hx-text-2xl:1.5rem;--hx-text-2xl--line-height:calc(2/1.5);--hx-text-3xl:1.875rem;--hx-text-3xl--line-height:calc(2.25/1.875);--hx-text-4xl:2.25rem;--hx-text-4xl--line-height:calc(2.5/2.25);--hx-text-5xl:3rem;--hx-text-5xl--line-height:1;--hx-font-weight-normal:400;--hx-font-weight-medium:500;--hx-font-weight-semibold:600;--hx-font-weight-bold:700;--hx-font-weight-extrabold:800;--hx-tracking-tighter:-.05em;--hx-tracking-tight:-.025em;--hx-leading-tight:1.25;--hx-radius-xs:.125rem;--hx-radius-sm:.25rem;--hx-radius-md:.375rem;--hx-radius-lg:.5rem;--hx-radius-xl:.75rem;--hx-radius-3xl:1.5rem;--hx-ease-in:cubic-bezier(.4,0,1,1);--hx-ease-out:cubic-bezier(0,0,.2,1);--hx-ease-in-out:cubic-bezier(.4,0,.2,1);--hx-blur-md:12px;--hx-default-transition-duration:.15s;--hx-default-transition-timing-function:cubic-bezier(.4,0,.2,1);--hx-default-font-family:var(--hx-font-sans);--hx-default-mono-font-family:var(--hx-font-mono);--hx-color-primary-100:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*44));--hx-color-primary-200:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*36));--hx-color-primary-300:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*27));--hx-color-primary-400:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*16));--hx-color-primary-500:hsl(var(--primary-hue)var(--primary-saturation)var(--primary-lightness));--hx-color-primary-600:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*45));--hx-color-primary-700:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*39));--hx-color-primary-800:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*32));--hx-color-primary-900:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*24));--hx-color-dark:#111}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--hx-default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--hx-default-font-feature-settings,normal);font-variation-settings:var(--hx-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--hx-default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--hx-default-mono-font-feature-settings,normal);font-variation-settings:var(--hx-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports(color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}abbr:where([title]){cursor:help}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible:where(.dark,.dark *){--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}}@layer components;@layer utilities{.hx\:pointer-events-none{pointer-events:none}.hx\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hx\:absolute{position:absolute}.hx\:relative{position:relative}.hx\:sticky{position:sticky}.hx\:inset-x-0{inset-inline:calc(var(--hx-spacing)*0)}.hx\:inset-y-0{inset-block:calc(var(--hx-spacing)*0)}.hx\:top-0{top:calc(var(--hx-spacing)*0)}.hx\:top-1\/2{top:50%}.hx\:top-8{top:calc(var(--hx-spacing)*8)}.hx\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:top-\[40\%\]{top:40%}.hx\:top-full{top:100%}.hx\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:bottom-0{bottom:calc(var(--hx-spacing)*0)}.hx\:left-0{left:calc(var(--hx-spacing)*0)}.hx\:left-\[24px\]{left:24px}.hx\:left-\[36px\]{left:36px}.hx\:z-20{z-index:20}.hx\:z-\[-1\]{z-index:-1}.hx\:order-last{order:9999}.hx\:m-\[11px\]{margin:11px}.hx\:mx-1{margin-inline:calc(var(--hx-spacing)*1)}.hx\:mx-4{margin-inline:calc(var(--hx-spacing)*4)}.hx\:mx-auto{margin-inline:auto}.hx\:my-1{margin-block:calc(var(--hx-spacing)*1)}.hx\:my-1\.5{margin-block:calc(var(--hx-spacing)*1.5)}.hx\:my-2{margin-block:calc(var(--hx-spacing)*2)}.hx\:-mt-20{margin-top:calc(var(--hx-spacing)*-20)}.hx\:mt-1{margin-top:calc(var(--hx-spacing)*1)}.hx\:mt-1\.5{margin-top:calc(var(--hx-spacing)*1.5)}.hx\:mt-2{margin-top:calc(var(--hx-spacing)*2)}.hx\:mt-4{margin-top:calc(var(--hx-spacing)*4)}.hx\:mt-5{margin-top:calc(var(--hx-spacing)*5)}.hx\:mt-6{margin-top:calc(var(--hx-spacing)*6)}.hx\:mt-8{margin-top:calc(var(--hx-spacing)*8)}.hx\:mt-12{margin-top:calc(var(--hx-spacing)*12)}.hx\:mt-16{margin-top:calc(var(--hx-spacing)*16)}.hx\:mt-auto{margin-top:auto}.hx\:-mr-2{margin-right:calc(var(--hx-spacing)*-2)}.hx\:mr-1{margin-right:calc(var(--hx-spacing)*1)}.hx\:mr-2{margin-right:calc(var(--hx-spacing)*2)}.hx\:-mb-0\.5{margin-bottom:calc(var(--hx-spacing)*-.5)}.hx\:mb-0{margin-bottom:calc(var(--hx-spacing)*0)}.hx\:mb-2{margin-bottom:calc(var(--hx-spacing)*2)}.hx\:mb-4{margin-bottom:calc(var(--hx-spacing)*4)}.hx\:mb-6{margin-bottom:calc(var(--hx-spacing)*6)}.hx\:mb-8{margin-bottom:calc(var(--hx-spacing)*8)}.hx\:mb-10{margin-bottom:calc(var(--hx-spacing)*10)}.hx\:mb-12{margin-bottom:calc(var(--hx-spacing)*12)}.hx\:mb-16{margin-bottom:calc(var(--hx-spacing)*16)}.hx\:-ml-2{margin-left:calc(var(--hx-spacing)*-2)}.hx\:ml-2{margin-left:calc(var(--hx-spacing)*2)}.hx\:ml-4{margin-left:calc(var(--hx-spacing)*4)}.hx\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hx\:block{display:block}.hx\:flex{display:flex}.hx\:grid{display:grid}.hx\:hidden{display:none}.hx\:inline{display:inline}.hx\:inline-block{display:inline-block}.hx\:inline-flex{display:inline-flex}.hx\:aspect-auto{aspect-ratio:auto}.hx\:size-4{width:calc(var(--hx-spacing)*4);height:calc(var(--hx-spacing)*4)}.hx\:h-0{height:calc(var(--hx-spacing)*0)}.hx\:h-2{height:calc(var(--hx-spacing)*2)}.hx\:h-3\.5{height:calc(var(--hx-spacing)*3.5)}.hx\:h-4{height:calc(var(--hx-spacing)*4)}.hx\:h-5{height:calc(var(--hx-spacing)*5)}.hx\:h-7{height:calc(var(--hx-spacing)*7)}.hx\:h-10{height:calc(var(--hx-spacing)*10)}.hx\:h-16{height:calc(var(--hx-spacing)*16)}.hx\:h-\[18px\]{height:18px}.hx\:h-full{height:100%}.hx\:h-px{height:1px}.hx\:max-h-\(--menu-height\){max-height:var(--menu-height)}.hx\:max-h-64{max-height:calc(var(--hx-spacing)*64)}.hx\:max-h-\[calc\(100vh-var\(--navbar-height\)-env\(safe-area-inset-bottom\)\)\]{max-height:calc(100vh - var(--navbar-height) - env(safe-area-inset-bottom))}.hx\:max-h-\[min\(calc\(50vh-11rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(50vh - 11rem - env(safe-area-inset-bottom)),400px)}.hx\:min-h-\[100px\]{min-height:100px}.hx\:min-h-\[calc\(100vh-var\(--navbar-height\)\)\]{min-height:calc(100vh - var(--navbar-height))}.hx\:w-2{width:calc(var(--hx-spacing)*2)}.hx\:w-3\.5{width:calc(var(--hx-spacing)*3.5)}.hx\:w-4{width:calc(var(--hx-spacing)*4)}.hx\:w-10{width:calc(var(--hx-spacing)*10)}.hx\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:w-\[110\%\]{width:110%}.hx\:w-\[180\%\]{width:180%}.hx\:w-full{width:100%}.hx\:w-max{width:max-content}.hx\:w-screen{width:100vw}.hx\:max-w-\[50\%\]{max-width:50%}.hx\:max-w-\[90rem\]{max-width:90rem}.hx\:max-w-\[min\(calc\(100vw-2rem\)\,calc\(100\%\+20rem\)\)\]{max-width:min(100vw - 2rem,100% + 20rem)}.hx\:max-w-full{max-width:100%}.hx\:max-w-none{max-width:none}.hx\:max-w-screen-xl{max-width:var(--hx-breakpoint-xl)}.hx\:min-w-0{min-width:calc(var(--hx-spacing)*0)}.hx\:min-w-\[18px\]{min-width:18px}.hx\:min-w-\[24px\]{min-width:24px}.hx\:min-w-full{min-width:100%}.hx\:shrink-0{flex-shrink:0}.hx\:grow{flex-grow:1}.hx\:origin-center{transform-origin:50%}.hx\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hx\:cursor-default{cursor:default}.hx\:cursor-pointer{cursor:pointer}.hx\:scroll-my-6{scroll-margin-block:calc(var(--hx-spacing)*6)}.hx\:scroll-py-6{scroll-padding-block:calc(var(--hx-spacing)*6)}.hx\:list-none{list-style-type:none}.hx\:appearance-none{appearance:none}.hx\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.hx\:flex-col{flex-direction:column}.hx\:flex-wrap{flex-wrap:wrap}.hx\:items-baseline{align-items:baseline}.hx\:items-center{align-items:center}.hx\:items-start{align-items:flex-start}.hx\:justify-between{justify-content:space-between}.hx\:justify-center{justify-content:center}.hx\:justify-end{justify-content:flex-end}.hx\:justify-start{justify-content:flex-start}.hx\:justify-items-start{justify-items:start}.hx\:gap-1{gap:calc(var(--hx-spacing)*1)}.hx\:gap-1\.5{gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-2{gap:calc(var(--hx-spacing)*2)}.hx\:gap-4{gap:calc(var(--hx-spacing)*4)}.hx\:gap-x-1\.5{column-gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-x-2{column-gap:calc(var(--hx-spacing)*2)}.hx\:gap-y-1{row-gap:calc(var(--hx-spacing)*1)}.hx\:gap-y-2{row-gap:calc(var(--hx-spacing)*2)}.hx\:overflow-auto{overflow:auto}.hx\:overflow-hidden{overflow:hidden}.hx\:overflow-x-auto{overflow-x:auto}.hx\:overflow-x-hidden{overflow-x:hidden}.hx\:overflow-y-auto{overflow-y:auto}.hx\:overflow-y-hidden{overflow-y:hidden}.hx\:overscroll-contain{overscroll-behavior:contain}.hx\:overscroll-x-contain{overscroll-behavior-x:contain}.hx\:rounded-3xl{border-radius:var(--hx-radius-3xl)}.hx\:rounded-full{border-radius:340282e33px}.hx\:rounded-lg{border-radius:var(--hx-radius-lg)}.hx\:rounded-md{border-radius:var(--hx-radius-md)}.hx\:rounded-sm{border-radius:var(--hx-radius-sm)}.hx\:rounded-xl{border-radius:var(--hx-radius-xl)}.hx\:rounded-xs{border-radius:var(--hx-radius-xs)}.hx\:rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.hx\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:border-0{border-style:var(--tw-border-style);border-width:0}.hx\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.hx\:border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.hx\:border-amber-200{border-color:var(--hx-color-amber-200)}.hx\:border-black\/5{border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:border-black\/5{border-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:border-blue-200{border-color:var(--hx-color-blue-200)}.hx\:border-gray-200{border-color:var(--hx-color-gray-200)}.hx\:border-gray-500{border-color:var(--hx-color-gray-500)}.hx\:border-green-200{border-color:var(--hx-color-green-200)}.hx\:border-indigo-200{border-color:var(--hx-color-indigo-200)}.hx\:border-orange-100{border-color:var(--hx-color-orange-100)}.hx\:border-purple-200{border-color:var(--hx-color-purple-200)}.hx\:border-red-200{border-color:var(--hx-color-red-200)}.hx\:border-transparent{border-color:#0000}.hx\:border-yellow-100{border-color:var(--hx-color-yellow-100)}.hx\:bg-amber-100{background-color:var(--hx-color-amber-100)}.hx\:bg-black\/\[\.05\]{background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-black\/\[\.05\]{background-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:bg-blue-100{background-color:var(--hx-color-blue-100)}.hx\:bg-gray-100{background-color:var(--hx-color-gray-100)}.hx\:bg-gray-200{background-color:var(--hx-color-gray-200)}.hx\:bg-green-100{background-color:var(--hx-color-green-100)}.hx\:bg-indigo-100{background-color:var(--hx-color-indigo-100)}.hx\:bg-neutral-50{background-color:var(--hx-color-neutral-50)}.hx\:bg-neutral-900{background-color:var(--hx-color-neutral-900)}.hx\:bg-orange-50{background-color:var(--hx-color-orange-50)}.hx\:bg-primary-100{background-color:var(--hx-color-primary-100)}.hx\:bg-primary-400{background-color:var(--hx-color-primary-400)}.hx\:bg-primary-600{background-color:var(--hx-color-primary-600)}.hx\:bg-primary-700\/5{background-color:var(--hx-color-primary-700)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-primary-700\/5{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hx\:bg-purple-100{background-color:var(--hx-color-purple-100)}.hx\:bg-red-100{background-color:var(--hx-color-red-100)}.hx\:bg-transparent{background-color:#0000}.hx\:bg-white{background-color:var(--hx-color-white)}.hx\:bg-yellow-50{background-color:var(--hx-color-yellow-50)}.hx\:bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.hx\:from-gray-900{--tw-gradient-from:var(--hx-color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:to-gray-600{--tw-gradient-to:var(--hx-color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:bg-clip-text{-webkit-background-clip:text;background-clip:text}.hx\:p-0{padding:calc(var(--hx-spacing)*0)}.hx\:p-0\.5{padding:calc(var(--hx-spacing)*.5)}.hx\:p-1{padding:calc(var(--hx-spacing)*1)}.hx\:p-1\.5{padding:calc(var(--hx-spacing)*1.5)}.hx\:p-2{padding:calc(var(--hx-spacing)*2)}.hx\:p-4{padding:calc(var(--hx-spacing)*4)}.hx\:p-6{padding:calc(var(--hx-spacing)*6)}.hx\:px-1\.5{padding-inline:calc(var(--hx-spacing)*1.5)}.hx\:px-2{padding-inline:calc(var(--hx-spacing)*2)}.hx\:px-2\.5{padding-inline:calc(var(--hx-spacing)*2.5)}.hx\:px-3{padding-inline:calc(var(--hx-spacing)*3)}.hx\:px-4{padding-inline:calc(var(--hx-spacing)*4)}.hx\:px-6{padding-inline:calc(var(--hx-spacing)*6)}.hx\:px-8{padding-inline:calc(var(--hx-spacing)*8)}.hx\:py-1{padding-block:calc(var(--hx-spacing)*1)}.hx\:py-1\.5{padding-block:calc(var(--hx-spacing)*1.5)}.hx\:py-2{padding-block:calc(var(--hx-spacing)*2)}.hx\:py-2\.5{padding-block:calc(var(--hx-spacing)*2.5)}.hx\:py-3{padding-block:calc(var(--hx-spacing)*3)}.hx\:py-4{padding-block:calc(var(--hx-spacing)*4)}.hx\:py-12{padding-block:calc(var(--hx-spacing)*12)}.hx\:pt-4{padding-top:calc(var(--hx-spacing)*4)}.hx\:pt-6{padding-top:calc(var(--hx-spacing)*6)}.hx\:pt-8{padding-top:calc(var(--hx-spacing)*8)}.hx\:pr-2{padding-right:calc(var(--hx-spacing)*2)}.hx\:pr-4{padding-right:calc(var(--hx-spacing)*4)}.hx\:pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\]{padding-right:calc(env(safe-area-inset-right) - 1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-left),1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-right\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-right),1.5rem)}.hx\:pb-8{padding-bottom:calc(var(--hx-spacing)*8)}.hx\:pb-\[env\(safe-area-inset-bottom\)\]{padding-bottom:env(safe-area-inset-bottom)}.hx\:pb-px{padding-bottom:1px}.hx\:pl-6{padding-left:calc(var(--hx-spacing)*6)}.hx\:pl-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-left:max(env(safe-area-inset-left),1.5rem)}.hx\:text-center{text-align:center}.hx\:text-left{text-align:left}.hx\:align-\[-2\.5px\]{vertical-align:-2.5px}.hx\:align-baseline{vertical-align:baseline}.hx\:align-middle{vertical-align:middle}.hx\:align-text-bottom{vertical-align:text-bottom}.hx\:font-mono{font-family:var(--hx-font-mono)}.hx\:text-2xl{font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height))}.hx\:text-4xl{font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height))}.hx\:text-base{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height))}.hx\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}.hx\:text-xs{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height))}.hx\:text-\[\.65rem\]{font-size:.65rem}.hx\:text-\[10px\]{font-size:10px}.hx\:leading-5{--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5)}.hx\:leading-6{--tw-leading:calc(var(--hx-spacing)*6);line-height:calc(var(--hx-spacing)*6)}.hx\:leading-7{--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hx\:leading-none{--tw-leading:1;line-height:1}.hx\:leading-tight{--tw-leading:var(--hx-leading-tight);line-height:var(--hx-leading-tight)}.hx\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:font-extrabold{--tw-font-weight:var(--hx-font-weight-extrabold);font-weight:var(--hx-font-weight-extrabold)}.hx\:font-medium{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:font-normal{--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal)}.hx\:font-semibold{--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hx\:tracking-tight{--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight)}.hx\:tracking-tighter{--tw-tracking:var(--hx-tracking-tighter);letter-spacing:var(--hx-tracking-tighter)}.hx\:break-words,.hx\:wrap-break-word{overflow-wrap:break-word}.hx\:text-ellipsis{text-overflow:ellipsis}.hx\:whitespace-nowrap{white-space:nowrap}.hx\:text-\[color\:hsl\(var\(--primary-hue\)\,100\%\,50\%\)\]{color:hsl(var(--primary-hue),100%,50%)}.hx\:text-amber-900{color:var(--hx-color-amber-900)}.hx\:text-blue-900{color:var(--hx-color-blue-900)}.hx\:text-current{color:currentColor}.hx\:text-gray-100{color:var(--hx-color-gray-100)}.hx\:text-gray-500{color:var(--hx-color-gray-500)}.hx\:text-gray-600{color:var(--hx-color-gray-600)}.hx\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:text-green-900{color:var(--hx-color-green-900)}.hx\:text-indigo-900{color:var(--hx-color-indigo-900)}.hx\:text-orange-800{color:var(--hx-color-orange-800)}.hx\:text-primary-800{color:var(--hx-color-primary-800)}.hx\:text-purple-900{color:var(--hx-color-purple-900)}.hx\:text-red-900{color:var(--hx-color-red-900)}.hx\:text-slate-50{color:var(--hx-color-slate-50)}.hx\:text-slate-900{color:var(--hx-color-slate-900)}.hx\:text-transparent{color:#0000}.hx\:text-white{color:var(--hx-color-white)}.hx\:text-yellow-900{color:var(--hx-color-yellow-900)}.hx\:capitalize{text-transform:capitalize}.hx\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.hx\:no-underline{text-decoration-line:none}.hx\:underline{text-decoration-line:underline}.hx\:decoration-from-font{text-decoration-thickness:from-font}.hx\:underline-offset-2{text-underline-offset:2px}.hx\:opacity-0{opacity:0}.hx\:opacity-50{opacity:.5}.hx\:opacity-80{opacity:.8}.hx\:shadow-\[0_-12px_16px_\#fff\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_-12px_16px_white\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,white);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_2px_4px_rgba\(0\,0\,0\,\.02\)\,0_1px_0_rgba\(0\,0\,0\,\.06\)\]{--tw-shadow:0 2px 4px var(--tw-shadow-color,#00000005),0 1px 0 var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hextra-focus-visible:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}.hx\:hextra-focus-visible:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}.hx\:hextra-focus-visible-inset:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}.hx\:hextra-focus-visible-inset:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800)}.hx\:shadow-gray-100{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:shadow-gray-100{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}.hx\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:duration-75{--tw-duration:75ms;transition-duration:75ms}.hx\:duration-150{--tw-duration:.15s;transition-duration:.15s}.hx\:duration-200{--tw-duration:.2s;transition-duration:.2s}.hx\:ease-in{--tw-ease:var(--hx-ease-in);transition-timing-function:var(--hx-ease-in)}.hx\:ease-in-out{--tw-ease:var(--hx-ease-in-out);transition-timing-function:var(--hx-ease-in-out)}.hx\:outline-none{--tw-outline-style:none;outline-style:none}.hx\:select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hx\:group-hover\:text-gray-900:is(:where(.hx\:group):hover *){color:var(--hx-color-gray-900)}.hx\:group-hover\:underline:is(:where(.hx\:group):hover *){text-decoration-line:underline}.hx\:group-hover\/code\:opacity-100:is(:where(.hx\:group\/code):hover *){opacity:1}}.hx\:group-data-\[active\=true\]\:text-primary-800:is(:where(.hx\:group)[data-active=true] *){color:var(--hx-color-primary-800)}.hx\:group-data-\[theme\=dark\]\:hidden:is(:where(.hx\:group)[data-theme=dark] *),.hx\:group-data-\[theme\=light\]\:hidden:is(:where(.hx\:group)[data-theme=light] *),.hx\:group-data-\[theme\=system\]\:hidden:is(:where(.hx\:group)[data-theme=system] *){display:none}.hx\:group-\[\.copied\]\/copybtn\:block:is(:where(.hx\:group\/copybtn).copied *){display:block}.hx\:group-\[\.copied\]\/copybtn\:hidden:is(:where(.hx\:group\/copybtn).copied *){display:none}.hx\:placeholder\:text-gray-500::placeholder{color:var(--hx-color-gray-500)}.hx\:before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.hx\:before\:absolute:before{content:var(--tw-content);position:absolute}.hx\:before\:inset-0:before{content:var(--tw-content);inset:calc(var(--hx-spacing)*0)}.hx\:before\:inset-y-1:before{content:var(--tw-content);inset-block:calc(var(--hx-spacing)*1)}.hx\:before\:mr-1:before{content:var(--tw-content);margin-right:calc(var(--hx-spacing)*1)}.hx\:before\:inline-block:before{content:var(--tw-content);display:inline-block}.hx\:before\:w-px:before{content:var(--tw-content);width:1px}.hx\:before\:bg-gray-200:before{content:var(--tw-content);background-color:var(--hx-color-gray-200)}.hx\:before\:opacity-25:before{content:var(--tw-content);opacity:.25}.hx\:before\:transition-transform:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:before\:content-\[\"\"\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\'\#\'\]:before{--tw-content:"#";content:var(--tw-content)}.hx\:before\:content-\[\'\'\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.hx\:group-open\:before\:rotate-90:is(:where(.hx\:group):is([open],:popover-open,:open) *):before{content:var(--tw-content);rotate:90deg}.hx\:first\:mt-0:first-child{margin-top:calc(var(--hx-spacing)*0)}.hx\:last-of-type\:mb-0:last-of-type{margin-bottom:calc(var(--hx-spacing)*0)}@media(hover:hover){.hx\:hover\:border-gray-200:hover{border-color:var(--hx-color-gray-200)}.hx\:hover\:border-gray-300:hover{border-color:var(--hx-color-gray-300)}.hx\:hover\:border-gray-400:hover{border-color:var(--hx-color-gray-400)}.hx\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}.hx\:hover\:bg-gray-100:hover{background-color:var(--hx-color-gray-100)}.hx\:hover\:bg-gray-800\/5:hover{background-color:var(--hx-color-gray-800)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:bg-gray-800\/5:hover{background-color:color-mix(in oklab,var(--hx-color-gray-800)5%,transparent)}}.hx\:hover\:bg-primary-700:hover{background-color:var(--hx-color-primary-700)}.hx\:hover\:bg-slate-50:hover{background-color:var(--hx-color-slate-50)}.hx\:hover\:text-black:hover{color:var(--hx-color-black)}.hx\:hover\:text-gray-700:hover{color:var(--hx-color-gray-700)}.hx\:hover\:text-gray-800:hover{color:var(--hx-color-gray-800)}.hx\:hover\:text-gray-900:hover{color:var(--hx-color-gray-900)}.hx\:hover\:text-primary-600:hover{color:var(--hx-color-primary-600)}.hx\:hover\:opacity-60:hover{opacity:.6}.hx\:hover\:opacity-75:hover{opacity:.75}.hx\:hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}}.hx\:focus-visible\:not-sr-only:focus-visible{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.hx\:focus-visible\:fixed:focus-visible{position:fixed}.hx\:focus-visible\:top-2:focus-visible{top:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:left-2:focus-visible{left:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:z-50:focus-visible{z-index:50}.hx\:focus-visible\:rounded-md:focus-visible{border-radius:var(--hx-radius-md)}.hx\:focus-visible\:bg-primary-500:focus-visible{background-color:var(--hx-color-primary-500)}.hx\:focus-visible\:bg-white:focus-visible{background-color:var(--hx-color-white)}.hx\:focus-visible\:px-4:focus-visible{padding-inline:calc(var(--hx-spacing)*4)}.hx\:focus-visible\:py-2:focus-visible{padding-block:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:text-sm:focus-visible{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:focus-visible\:font-medium:focus-visible{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:focus-visible\:text-white:focus-visible{color:var(--hx-color-white)}.hx\:active\:bg-gray-400\/20:active{background-color:var(--hx-color-gray-400)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:bg-gray-400\/20:active{background-color:color-mix(in oklab,var(--hx-color-gray-400)20%,transparent)}}.hx\:active\:opacity-50:active{opacity:.5}.hx\:active\:shadow-sm:active{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:active\:shadow-gray-200:active{--tw-shadow-color:var(--hx-color-gray-200)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:shadow-gray-200:active{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-200)var(--tw-shadow-alpha),transparent)}}.hx\:data-\[state\=closed\]\:hidden[data-state=closed],.hx\:data-\[state\=open\]\:hidden[data-state=open]{display:none}.hx\:data-\[state\=selected\]\:block[data-state=selected]{display:block}.hx\:data-\[state\=selected\]\:border-primary-500[data-state=selected]{border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:text-primary-600[data-state=selected]{color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:contrast-more\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:contrast-more\:border-current{border-color:initial}.hx\:contrast-more\:border-gray-800{border-color:var(--hx-color-gray-800)}.hx\:contrast-more\:border-gray-900{border-color:var(--hx-color-gray-900)}.hx\:contrast-more\:border-neutral-400{border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:border-primary-500{border-color:var(--hx-color-primary-500)}.hx\:contrast-more\:border-transparent{border-color:#0000}.hx\:contrast-more\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:contrast-more\:text-current{color:currentColor}.hx\:contrast-more\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:contrast-more\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:contrast-more\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:contrast-more\:underline{text-decoration-line:underline}.hx\:contrast-more\:shadow-\[0_0_0_1px_\#000\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}}}@media not all and (min-width:80rem){.hx\:max-xl\:hidden{display:none}}@media not all and (min-width:64rem){.hx\:max-lg\:min-h-\[340px\]{min-height:340px}}@media not all and (min-width:48rem){.hx\:max-md\:sticky{position:sticky}.hx\:max-md\:hidden{display:none}.hx\:max-md\:min-h-\[340px\]{min-height:340px}.hx\:max-md\:\[transform\:translate3d\(0\,-100\%\,0\)\]{transform:translateY(-100%)}.hx\:max-md\:\[transform\:translate3d\(0\,0\,0\)\]{transform:translate(0)}}@media not all and (min-width:40rem){.hx\:max-sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}}@media(min-width:40rem){.hx\:sm\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:sm\:left-auto{left:auto}.hx\:sm\:block{display:block}.hx\:sm\:flex{display:flex}.hx\:sm\:w-\[110\%\]{width:110%}.hx\:sm\:flex-row{flex-direction:row}.hx\:sm\:items-center{align-items:center}.hx\:sm\:items-start{align-items:flex-start}.hx\:sm\:justify-between{justify-content:space-between}.hx\:sm\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}@media not all and (min-width:64rem){.hx\:sm\:max-lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@media(min-width:48rem){.hx\:md\:sticky{position:sticky}.hx\:md\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:md\:mr-0{margin-right:calc(var(--hx-spacing)*0)}.hx\:md\:hidden{display:none}.hx\:md\:inline-block{display:inline-block}.hx\:md\:inline-flex{display:inline-flex}.hx\:md\:aspect-\[1\.1\/1\]{aspect-ratio:1.1}.hx\:md\:h-\[calc\(100vh-var\(--navbar-height\)-var\(--menu-height\)\)\]{height:calc(100vh - var(--navbar-height) - var(--menu-height))}.hx\:md\:max-h-\[min\(calc\(100vh-5rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(100vh - 5rem - env(safe-area-inset-bottom)),400px)}.hx\:md\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:md\:shrink-0{flex-shrink:0}.hx\:md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.hx\:md\:justify-start{justify-content:flex-start}.hx\:md\:self-start{align-self:flex-start}.hx\:md\:overflow-auto{overflow:auto}.hx\:md\:px-12{padding-inline:calc(var(--hx-spacing)*12)}.hx\:md\:pt-12{padding-top:calc(var(--hx-spacing)*12)}.hx\:md\:text-3xl{font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height))}.hx\:md\:text-5xl{font-size:var(--hx-text-5xl);line-height:var(--tw-leading,var(--hx-text-5xl--line-height))}.hx\:md\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:md\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}}@media(min-width:64rem){.hx\:lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:80rem){.hx\:xl\:block{display:block}.hx\:xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.hx\:ltr\:right-1\.5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*1.5)}.hx\:ltr\:right-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*2)}.hx\:ltr\:right-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*3)}.hx\:ltr\:-mr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:calc(var(--hx-spacing)*-4)}.hx\:ltr\:mr-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:auto}.hx\:ltr\:ml-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.hx\:ltr\:ml-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:ml-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:auto}.hx\:ltr\:rotate-180:where(:dir(ltr),[dir=ltr],[dir=ltr] *){rotate:180deg}.hx\:ltr\:rounded-l-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:ltr\:rounded-r-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:ltr\:border-l:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);border-left-width:1px}.hx\:ltr\:pr-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*0)}.hx\:ltr\:pr-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*1)}.hx\:ltr\:pr-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*2)}.hx\:ltr\:pr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:ltr\:pr-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:ltr\:pr-9:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*9)}.hx\:ltr\:pl-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:pl-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:ltr\:pl-5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*5)}.hx\:ltr\:pl-6:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*6)}.hx\:ltr\:pl-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:ltr\:pl-12:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*12)}.hx\:ltr\:pl-16:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*16)}.hx\:ltr\:text-right:where(:dir(ltr),[dir=ltr],[dir=ltr] *){text-align:right}.hx\:ltr\:before\:left-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);left:calc(var(--hx-spacing)*0)}@media(min-width:48rem){.hx\:ltr\:md\:left-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:auto}}.hx\:rtl\:left-1\.5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*1.5)}.hx\:rtl\:left-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*2)}.hx\:rtl\:left-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}.hx\:rtl\:mr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto}.hx\:rtl\:-ml-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:calc(var(--hx-spacing)*-4)}.hx\:rtl\:ml-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:auto}.hx\:rtl\:-rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:-180deg}.hx\:rtl\:rotate-270:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:270deg}.hx\:rtl\:rounded-l-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:rtl\:rounded-r-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:rtl\:border-r:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:1px}.hx\:rtl\:pr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:pr-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:rtl\:pr-5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*5)}.hx\:rtl\:pr-6:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*6)}.hx\:rtl\:pr-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:rtl\:pr-12:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*12)}.hx\:rtl\:pr-16:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*16)}.hx\:rtl\:pl-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*0)}.hx\:rtl\:pl-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*1)}.hx\:rtl\:pl-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*2)}.hx\:rtl\:pl-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:rtl\:pl-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:rtl\:pl-9:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*9)}.hx\:rtl\:text-left:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:left}.hx\:rtl\:before\:right-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);right:calc(var(--hx-spacing)*0)}.hx\:rtl\:before\:rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);rotate:180deg}@media(min-width:48rem){.hx\:rtl\:md\:right-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:auto}}.hx\:dark\:block:where(.dark,.dark *){display:block}.hx\:dark\:hidden:where(.dark,.dark *){display:none}.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:var(--hx-color-amber-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-amber-200)30%,transparent)}}.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:var(--hx-color-blue-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-blue-200)30%,transparent)}}.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-gray-100)20%,transparent)}}.hx\:dark\:border-gray-400:where(.dark,.dark *){border-color:var(--hx-color-gray-400)}.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:var(--hx-color-green-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-green-200)30%,transparent)}}.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:var(--hx-color-indigo-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-indigo-200)30%,transparent)}}.hx\:dark\:border-neutral-700:where(.dark,.dark *){border-color:var(--hx-color-neutral-700)}.hx\:dark\:border-neutral-800:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-orange-400)30%,transparent)}}.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:var(--hx-color-purple-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-purple-200)30%,transparent)}}.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:var(--hx-color-red-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-red-200)30%,transparent)}}.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:var(--hx-color-yellow-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-yellow-200)30%,transparent)}}.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:var(--hx-color-amber-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-amber-900)30%,transparent)}}.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:var(--hx-color-blue-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-blue-900)30%,transparent)}}.hx\:dark\:bg-dark:where(.dark,.dark *),.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:var(--hx-color-dark)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)50%,transparent)}}.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:var(--hx-color-gray-50)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-50)10%,transparent)}}.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:var(--hx-color-green-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-green-900)30%,transparent)}}.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:var(--hx-color-indigo-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-indigo-900)30%,transparent)}}.hx\:dark\:bg-neutral-700:where(.dark,.dark *){background-color:var(--hx-color-neutral-700)}.hx\:dark\:bg-neutral-800:where(.dark,.dark *){background-color:var(--hx-color-neutral-800)}.hx\:dark\:bg-neutral-900:where(.dark,.dark *){background-color:var(--hx-color-neutral-900)}.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-orange-400)20%,transparent)}}.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-400)10%,transparent)}}.hx\:dark\:bg-primary-600:where(.dark,.dark *){background-color:var(--hx-color-primary-600)}.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:var(--hx-color-purple-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-purple-900)30%,transparent)}}.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:var(--hx-color-red-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-red-900)30%,transparent)}}.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:var(--hx-color-yellow-700)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-yellow-700)30%,transparent)}}.hx\:dark\:from-gray-100:where(.dark,.dark *){--tw-gradient-from:var(--hx-color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:to-gray-400:where(.dark,.dark *){--tw-gradient-to:var(--hx-color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:text-amber-200:where(.dark,.dark *){color:var(--hx-color-amber-200)}.hx\:dark\:text-blue-200:where(.dark,.dark *){color:var(--hx-color-blue-200)}.hx\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:dark\:text-gray-200:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hx\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:dark\:text-gray-400:where(.dark,.dark *){color:var(--hx-color-gray-400)}.hx\:dark\:text-green-200:where(.dark,.dark *){color:var(--hx-color-green-200)}.hx\:dark\:text-indigo-200:where(.dark,.dark *){color:var(--hx-color-indigo-200)}.hx\:dark\:text-neutral-200:where(.dark,.dark *){color:var(--hx-color-neutral-200)}.hx\:dark\:text-neutral-400:where(.dark,.dark *){color:var(--hx-color-neutral-400)}.hx\:dark\:text-orange-300:where(.dark,.dark *){color:var(--hx-color-orange-300)}.hx\:dark\:text-primary-600:where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:text-purple-200:where(.dark,.dark *){color:var(--hx-color-purple-200)}.hx\:dark\:text-red-200:where(.dark,.dark *){color:var(--hx-color-red-200)}.hx\:dark\:text-slate-100:where(.dark,.dark *){color:var(--hx-color-slate-100)}.hx\:dark\:text-white:where(.dark,.dark *){color:var(--hx-color-white)}.hx\:dark\:text-yellow-200:where(.dark,.dark *){color:var(--hx-color-yellow-200)}.hx\:dark\:opacity-80:where(.dark,.dark *){opacity:.8}.hx\:dark\:shadow-\[0_-1px_0_rgba\(255\,255\,255\,\.1\)_inset\]:where(.dark,.dark *){--tw-shadow:0 -1px 0 var(--tw-shadow-color,#ffffff1a)inset;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-\[0_-12px_16px_\#111\]:where(.dark,.dark *){--tw-shadow:0 -12px 16px var(--tw-shadow-color,#111);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:dark\:group-hover\:text-gray-50:where(.dark,.dark *):is(:where(.hx\:group):hover *){color:var(--hx-color-gray-50)}}.hx\:group-data-\[active\=true\]\:dark\:text-primary-600:is(:where(.hx\:group)[data-active=true] *):where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:placeholder\:text-gray-400:where(.dark,.dark *)::placeholder{color:var(--hx-color-gray-400)}.hx\:dark\:before\:bg-neutral-800:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hx\:dark\:before\:invert:where(.dark,.dark *):before{content:var(--tw-content);--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media(hover:hover){.hx\:dark\:hover\:border-gray-100:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-100)}.hx\:dark\:hover\:border-gray-600:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-600)}.hx\:dark\:hover\:border-neutral-500:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-500)}.hx\:dark\:hover\:border-neutral-700:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:border-neutral-800:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-gray-100)5%,transparent)}}.hx\:dark\:hover\:bg-neutral-700:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:bg-neutral-800:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-neutral-900:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-900)}.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-primary-100)5%,transparent)}}.hx\:dark\:hover\:bg-primary-700:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-700)}.hx\:dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--hx-color-gray-50)}.hx\:dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--hx-color-gray-100)}.hx\:dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--hx-color-gray-200)}.hx\:dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--hx-color-gray-300)}.hx\:dark\:hover\:text-neutral-50:where(.dark,.dark *):hover{color:var(--hx-color-neutral-50)}.hx\:dark\:hover\:text-primary-400:where(.dark,.dark *):hover{color:var(--hx-color-primary-400)}.hx\:dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--hx-color-white)}.hx\:dark\:hover\:shadow-none:where(.dark,.dark *):hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.hx\:dark\:focus-visible\:bg-dark:where(.dark,.dark *):focus-visible{background-color:var(--hx-color-dark)}.hx\:data-\[state\=selected\]\:dark\:border-primary-500[data-state=selected]:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:dark\:text-primary-600[data-state=selected]:where(.dark,.dark *){color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:dark\:border-current:where(.dark,.dark *){border-color:initial}.hx\:contrast-more\:dark\:border-gray-50:where(.dark,.dark *){border-color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:border-primary-500:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:dark\:contrast-more\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:text-current:where(.dark,.dark *){color:currentColor}.hx\:contrast-more\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:contrast-more\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:contrast-more\:dark\:shadow-\[0_0_0_1px_\#fff\]:where(.dark,.dark *){--tw-shadow:0 0 0 1px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:dark\:hover\:border-gray-50:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-50)}}}@media print{.hx\:print\:\[display\:none\],.hx\:print\:hidden{display:none}.hx\:print\:bg-transparent{background-color:#0000}}}html{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--hx-color-white);width:100%}body:where(.dark,.dark *){background-color:var(--hx-color-dark);color:var(--hx-color-gray-100)}:root{--primary-hue:212deg;--primary-saturation:100%;--primary-lightness:50%;--navbar-height:4rem;--hextra-banner-height:2rem;--menu-height:3.75rem}.dark{--primary-hue:204deg;--primary-saturation:100%;--primary-lightness:50%}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*2);font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height));--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-neutral-200)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-neutral-200)70%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){padding-bottom:calc(var(--hx-spacing)*1);font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--hx-color-neutral-400)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)10%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--hx-color-primary-600);text-decoration-line:underline;text-decoration-thickness:from-font}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);border-color:var(--hx-color-gray-300);color:var(--hx-color-gray-700);font-style:italic}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);padding-left:calc(var(--hx-spacing)*6);border-left-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);padding-right:calc(var(--hx-spacing)*6);border-right-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-gray-700);color:var(--hx-color-gray-400)}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){padding-block:calc(var(--hx-spacing)*4);--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*6);border-collapse:collapse;width:100%;padding:calc(var(--hx-spacing)*0);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);display:block;overflow-x:auto}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead{background-color:var(--hx-color-gray-50)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:var(--hx-color-gray-600)}@supports(color:color-mix(in lab,red,red)){.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-600)20%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr{margin:calc(var(--hx-spacing)*0);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:decimal}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:disc}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):has(li input[type=checkbox]){list-style-type:none}.content :where(ul,ol)>li>:where(ul,ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*0)}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:#0000}.content :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-inline:auto;margin-block:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-md)}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption{margin-top:calc(var(--hx-spacing)*2);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-500);display:block}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption:where(.dark,.dark *){color:var(--hx-color-gray-400)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dt{margin-top:calc(var(--hx-spacing)*6);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dd{margin-block:calc(var(--hx-spacing)*2);padding-inline-start:calc(var(--hx-spacing)*6)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*10);border-color:var(--hx-color-gray-200)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):last-child{margin-bottom:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .footnotes{margin-top:calc(var(--hx-spacing)*12);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.content .footnotes hr{border-color:var(--hx-color-gray-200)}.content .footnotes hr:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .subheading-anchor{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.content .subheading-anchor:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.content .subheading-anchor:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}span:target+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor),.content .subheading-anchor:focus-visible{opacity:1}span+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor){text-decoration-line:none!important}.content .subheading-anchor:after{content:var(--tw-content);color:var(--hx-color-gray-300)}.content .subheading-anchor:where(.dark,.dark *):after{content:var(--tw-content);color:var(--hx-color-neutral-700)}.content .subheading-anchor:after{padding-inline:calc(var(--hx-spacing)*1);--tw-content:"#";content:var(--tw-content)}span:target+:is(){color:var(--hx-color-gray-400)}span:target+:is():where(.dark,.dark *){color:var(--hx-color-neutral-500)}article details>summary::-webkit-details-marker{display:none}article details>summary:before{vertical-align:-4px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='hx:h-5 hx:w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E");width:1.2em;height:1.2em;padding:0 .6em}:lang(fa) ol{list-style-type:persian}.highlight .chroma .err{color:#a61717;background-color:#e3d2d2}.highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.highlight .chroma .line{display:flex}.highlight .chroma .k,.highlight .chroma .kc,.highlight .chroma .kd,.highlight .chroma .kn,.highlight .chroma .kp,.highlight .chroma .kr{color:#000;font-weight:700}.highlight .chroma .kt{color:#458;font-weight:700}.highlight .chroma .na{color:teal}.highlight .chroma .nb{color:#0086b3}.highlight .chroma .bp{color:#999}.highlight .chroma .nc{color:#458;font-weight:700}.highlight .chroma .no{color:teal}.highlight .chroma .nd{color:#3c5d5d;font-weight:700}.highlight .chroma .ni{color:purple}.highlight .chroma .ne,.highlight .chroma .nf,.highlight .chroma .nl{color:#900;font-weight:700}.highlight .chroma .nn{color:#555}.highlight .chroma .nt{color:navy}.highlight .chroma .nv,.highlight .chroma .vc,.highlight .chroma .vg,.highlight .chroma .vi{color:teal}.highlight .chroma .s,.highlight .chroma .sa,.highlight .chroma .sb,.highlight .chroma .sc,.highlight .chroma .dl,.highlight .chroma .sd,.highlight .chroma .s2,.highlight .chroma .se,.highlight .chroma .sh,.highlight .chroma .si,.highlight .chroma .sx{color:#d14}.highlight .chroma .sr{color:#009926}.highlight .chroma .s1{color:#d14}.highlight .chroma .ss{color:#990073}.highlight .chroma .m,.highlight .chroma .mb,.highlight .chroma .mf,.highlight .chroma .mh,.highlight .chroma .mi,.highlight .chroma .il,.highlight .chroma .mo{color:#099}.highlight .chroma .o,.highlight .chroma .ow{color:#000;font-weight:700}.highlight .chroma .c,.highlight .chroma .ch,.highlight .chroma .cm,.highlight .chroma .c1{color:#998;font-style:italic}.highlight .chroma .cs,.highlight .chroma .cp,.highlight .chroma .cpf{color:#999;font-style:italic;font-weight:700}.highlight .chroma .gd{color:#000;background-color:#fdd}.highlight .chroma .ge{color:#000;font-style:italic}.highlight .chroma .gr{color:#a00}.highlight .chroma .gh{color:#999}.highlight .chroma .gi{color:#000;background-color:#dfd}.highlight .chroma .go{color:#888}.highlight .chroma .gp{color:#555}.highlight .chroma .gs{font-weight:700}.highlight .chroma .gu{color:#aaa}.highlight .chroma .gt{color:#a00}.highlight .chroma .gl{text-decoration:underline}.highlight .chroma .w{color:#bbb}.dark .highlight .chroma .err{color:#f85149}.dark .highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.dark .highlight .chroma .line{display:flex}.dark .highlight .chroma .k{color:#ff7b72}.dark .highlight .chroma .kc{color:#79c0ff}.dark .highlight .chroma .kd,.dark .highlight .chroma .kn{color:#ff7b72}.dark .highlight .chroma .kp{color:#79c0ff}.dark .highlight .chroma .kr,.dark .highlight .chroma .kt{color:#ff7b72}.dark .highlight .chroma .nc{color:#f0883e;font-weight:700}.dark .highlight .chroma .no{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nd{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .ni{color:#ffa657}.dark .highlight .chroma .ne{color:#f0883e;font-weight:700}.dark .highlight .chroma .nf{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .nl{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nn{color:#ff7b72}.dark .highlight .chroma .py{color:#79c0ff}.dark .highlight .chroma .nt{color:#7ee787}.dark .highlight .chroma .nv{color:#79c0ff}.dark .highlight .chroma .l{color:#a5d6ff}.dark .highlight .chroma .ld{color:#79c0ff}.dark .highlight .chroma .s{color:#a5d6ff}.dark .highlight .chroma .sa{color:#79c0ff}.dark .highlight .chroma .sb,.dark .highlight .chroma .sc{color:#a5d6ff}.dark .highlight .chroma .dl{color:#79c0ff}.dark .highlight .chroma .sd,.dark .highlight .chroma .s2{color:#a5d6ff}.dark .highlight .chroma .se,.dark .highlight .chroma .sh{color:#79c0ff}.dark .highlight .chroma .si,.dark .highlight .chroma .sx{color:#a5d6ff}.dark .highlight .chroma .sr{color:#79c0ff}.dark .highlight .chroma .s1,.dark .highlight .chroma .ss,.dark .highlight .chroma .m,.dark .highlight .chroma .mb,.dark .highlight .chroma .mf,.dark .highlight .chroma .mh,.dark .highlight .chroma .mi,.dark .highlight .chroma .il,.dark .highlight .chroma .mo{color:#a5d6ff}.dark .highlight .chroma .o,.dark .highlight .chroma .ow{color:#ff7b72;font-weight:700}.dark .highlight .chroma .c,.dark .highlight .chroma .ch,.dark .highlight .chroma .cm,.dark .highlight .chroma .c1{color:#8b949e;font-style:italic}.dark .highlight .chroma .cs,.dark .highlight .chroma .cp,.dark .highlight .chroma .cpf{color:#8b949e;font-style:italic;font-weight:700}.dark .highlight .chroma .gd{color:#ffa198;background-color:#490202}.dark .highlight .chroma .ge{color:inherit;font-style:italic}.dark .highlight .chroma .gr{color:#ffa198}.dark .highlight .chroma .gh{color:#79c0ff;font-weight:700}.dark .highlight .chroma .gi{color:#56d364;background-color:#0f5323}.dark .highlight .chroma .go,.dark .highlight .chroma .gp{color:#8b949e}.dark .highlight .chroma .gs{font-weight:700}.dark .highlight .chroma .gu{color:#79c0ff}.dark .highlight .chroma .gt{color:#ff7b72}.dark .highlight .chroma .gl{text-decoration:underline}.dark .highlight .chroma .w{color:#6e7681}.hextra-code-block{--tw-leading:calc(var(--hx-spacing)*5);font-size:.9em;line-height:calc(var(--hx-spacing)*5)}.hextra-code-block pre{background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block pre{--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.hextra-code-block pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.hextra-code-block pre{--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.hextra-code-block pre:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.hextra-code-block pre:where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.hextra-code-block .hextra-code-filename{top:calc(var(--hx-spacing)*0);z-index:1;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:var(--hx-radius-xl);border-top-right-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);width:100%;position:absolute;overflow:hidden}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block .hextra-code-filename{padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*2);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));color:var(--hx-color-gray-700)}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hextra-code-block .hextra-code-filename+pre:not(.lntable pre){padding-top:calc(var(--hx-spacing)*12)}.hextra-code-block pre:not(.lntable pre){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*4)}.hextra-code-block div:nth-of-type(2) pre{padding-top:calc(var(--hx-spacing)*12);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .lntable{margin:calc(var(--hx-spacing)*0);border-radius:var(--hx-radius-xl);width:auto;display:block;overflow:auto}.chroma .lntable pre{padding-top:calc(var(--hx-spacing)*4);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line){min-width:2.6rem;padding-right:calc(var(--hx-spacing)*4);padding-left:calc(var(--hx-spacing)*4);color:var(--hx-color-neutral-600)}:is(.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line)):where(.dark,.dark *){color:var(--hx-color-neutral-300)}.chroma .lntd{padding:calc(var(--hx-spacing)*0);vertical-align:top}.chroma .lntd:last-of-type{width:100%}.chroma .hl{background-color:var(--hx-color-primary-800);width:100%;display:block}@supports(color:color-mix(in lab,red,red)){.chroma .hl{background-color:color-mix(in oklab,var(--hx-color-primary-800)10%,transparent)}}.hextra-cards{grid-template-columns:repeat(auto-fill,minmax(max(250px,calc((100% - 1rem*2)/var(--hextra-cards-grid-cols))),1fr))}.hextra-card{position:relative}.hextra-card img{-webkit-user-select:none;user-select:none}.hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card .hextra-card-icon svg{color:#0003;width:1.5rem;transition:color .3s}.hextra-card p{margin-top:.5rem;position:relative}.dark .hextra-card .hextra-card-icon svg{color:#fff6}.dark .hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card-tag{z-index:10;position:absolute;top:5px}.hextra-card-tag:where(:dir(ltr)){right:5px}.hextra-card-tag:where(:dir(rtl)){left:5px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker){counter-increment:step}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);margin-left:-41px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);margin-right:-44px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);background-color:var(--hx-color-gray-100)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:4px;border-color:var(--hx-color-white)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);border-color:var(--hx-color-dark)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step);text-align:center;text-indent:-1px;width:33px;height:33px;font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal);color:var(--hx-color-neutral-400);border-radius:340282e33px;position:absolute}:lang(fa) .hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step,persian)}.hextra-search-wrapper li{margin-inline:calc(var(--hx-spacing)*2.5);border-radius:var(--hx-radius-md);overflow-wrap:break-word;color:var(--hx-color-gray-800)}@media(prefers-contrast:more){.hextra-search-wrapper li{border-style:var(--tw-border-style);border-width:1px;border-color:#0000}}.hextra-search-wrapper li:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hextra-search-wrapper li a{scroll-margin:calc(var(--hx-spacing)*12);padding-inline:calc(var(--hx-spacing)*2.5);padding-block:calc(var(--hx-spacing)*2);display:block}.hextra-search-wrapper li a:focus-visible{--tw-outline-style:none;outline-style:none}.hextra-search-wrapper li .hextra-search-title{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hextra-search-wrapper li .hextra-search-active{border-radius:var(--hx-radius-md);background-color:var(--hx-color-primary-500)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper li .hextra-search-active{background-color:color-mix(in oklab,var(--hx-color-primary-500)10%,transparent)}}@media(prefers-contrast:more){.hextra-search-wrapper li .hextra-search-active{border-color:var(--hx-color-primary-500)}}.hextra-search-wrapper .hextra-search-no-result{padding:calc(var(--hx-spacing)*8);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-400);-webkit-user-select:none;user-select:none;display:block}.hextra-search-wrapper .hextra-search-prefix{margin-inline:calc(var(--hx-spacing)*2.5);margin-top:calc(var(--hx-spacing)*6);margin-bottom:calc(var(--hx-spacing)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix{border-color:color-mix(in oklab,var(--hx-color-black)10%,transparent)}}.hextra-search-wrapper .hextra-search-prefix{padding-inline:calc(var(--hx-spacing)*2.5);padding-bottom:calc(var(--hx-spacing)*1.5);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);color:var(--hx-color-gray-500);text-transform:uppercase;-webkit-user-select:none;user-select:none}.hextra-search-wrapper .hextra-search-prefix:first-child{margin-top:calc(var(--hx-spacing)*0)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix{border-color:var(--hx-color-gray-600);color:var(--hx-color-gray-900)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)20%,transparent)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){color:var(--hx-color-gray-300)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-gray-50);color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{margin-top:calc(var(--hx-spacing)*1);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:1.35rem;text-overflow:ellipsis;color:var(--hx-color-gray-600);line-height:1.35rem;overflow:hidden}.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-400)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box}.hextra-search-wrapper .hextra-search-match{color:var(--hx-color-primary-600)}@media(max-width:48rem){.hextra-sidebar-container{top:calc(var(--hx-spacing)*0);bottom:calc(var(--hx-spacing)*0);z-index:15;overscroll-behavior:contain;background-color:var(--hx-color-white);width:100%;padding-top:calc(var(--navbar-height) + var(--hextra-banner-height));position:fixed}.hextra-sidebar-container:where(.dark,.dark *){background-color:var(--hx-color-dark)}.hextra-sidebar-container{will-change:transform,opacity;contain:layout style;backface-visibility:hidden;transition:transform .4s cubic-bezier(.52,.16,.04,1)}}.hextra-sidebar-container li>.hextra-sidebar-children{height:calc(var(--hx-spacing)*0)}.hextra-sidebar-container li.open>.hextra-sidebar-children{height:auto;padding-top:calc(var(--hx-spacing)*1)}.hextra-sidebar-container li.open>.hextra-sidebar-item>.hextra-sidebar-collapsible-button>svg>path{rotate:90deg}.hextra-banner-hidden .hextra-banner{display:none}.hextra-banner :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:underline;text-decoration-thickness:from-font}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}nav .hextra-search-wrapper{display:none}@media(min-width:48rem){nav .hextra-search-wrapper{display:inline-block}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-nav-container-blur{background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur{background-color:color-mix(in oklab,var(--hx-color-white)85%,transparent)}}.hextra-nav-container-blur{--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-nav-container-blur:where(.dark,.dark *){background-color:var(--hx-color-dark)!important}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)80%,transparent)!important}}}.hextra-hamburger-menu svg g{transform-origin:50%;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg path{opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open path{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:0s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open g{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open>path{opacity:0}.hextra-hamburger-menu svg.open>g:first-of-type{rotate:45deg}.hextra-hamburger-menu svg.open>g:first-of-type path{--tw-translate-y:calc(var(--hx-spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-hamburger-menu svg.open>g:nth-of-type(2){rotate:-45deg}.hextra-hamburger-menu svg.open>g:nth-of-type(2) path{--tw-translate-y:calc(var(--hx-spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-scrollbar,.hextra-scrollbar *{scrollbar-width:thin;scrollbar-color:oklch(55.55% 0 0/.4)transparent;scrollbar-gutter:stable}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar{height:calc(var(--hx-spacing)*3);width:calc(var(--hx-spacing)*3)}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-track{background-color:#0000}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-thumb{border-radius:10px}:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{background-color:var(--tw-shadow-color);--tw-shadow-color:var(--hx-color-neutral-500);background-clip:content-box;border:3px solid #0000}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}@media(hover:hover){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:var(--hx-color-neutral-500)}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)40%,transparent)var(--tw-shadow-alpha),transparent)}}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-code-copy-btn{opacity:.85;--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-code-copy-btn:where(.dark,.dark *){opacity:.8}}@media(min-width:1024px){.hextra-feature-grid{grid-template-columns:repeat(var(--hextra-feature-grid-cols),minmax(0,1fr))}}.hextra-jupyter-code-cell{scrollbar-gutter:auto;margin-top:calc(var(--hx-spacing)*6)}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:hidden}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs{max-height:50vh;overflow:auto}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs pre{max-width:100%;font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:auto}.hextra-badge{align-items:center;display:inline-flex}.hextra-toc a.hextra-toc-active{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.2s;transition-duration:.2s;color:var(--hx-color-gray-900)!important}.hextra-toc a.hextra-toc-active:where(.dark,.dark *){color:var(--hx-color-gray-50)!important}.hextra-archive-timeline{border-left-style:var(--tw-border-style);border-left-width:2px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline{border-color:color-mix(in oklab,var(--hx-color-black)15%,transparent)}}.hextra-archive-timeline:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)15%,transparent)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false} \ No newline at end of file diff --git a/public/css/compiled/main.min.1ad8bafe4bcdf3c97beba654bef9e5fe4247509e3e27ac1143012be7f29fcb95.css b/public/css/compiled/main.min.1ad8bafe4bcdf3c97beba654bef9e5fe4247509e3e27ac1143012be7f29fcb95.css new file mode 100644 index 0000000..2d65929 --- /dev/null +++ b/public/css/compiled/main.min.1ad8bafe4bcdf3c97beba654bef9e5fe4247509e3e27ac1143012be7f29fcb95.css @@ -0,0 +1 @@ +:root{--hextra-max-page-width:80rem;--hextra-max-content-width:72rem;--hextra-max-navbar-width:80rem;--hextra-max-footer-width:80rem}.hextra-max-page-width{max-width:var(--hextra-max-page-width)}.hextra-max-content-width{max-width:var(--hextra-max-content-width)}.hextra-max-navbar-width{max-width:var(--hextra-max-navbar-width)}.hextra-max-footer-width{max-width:var(--hextra-max-footer-width)}/*!tailwindcss v4.1.18 | MIT License | https://tailwindcss.com*/@layer properties{@supports(((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--hx-font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--hx-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--hx-color-red-100:oklch(93.6% .032 17.717);--hx-color-red-200:oklch(88.5% .062 18.334);--hx-color-red-900:oklch(39.6% .141 25.723);--hx-color-orange-50:oklch(98% .016 73.684);--hx-color-orange-100:oklch(95.4% .038 75.164);--hx-color-orange-300:oklch(83.7% .128 66.29);--hx-color-orange-400:oklch(75% .183 55.934);--hx-color-orange-800:oklch(47% .157 37.304);--hx-color-amber-100:oklch(96.2% .059 95.617);--hx-color-amber-200:oklch(92.4% .12 95.746);--hx-color-amber-900:oklch(41.4% .112 45.904);--hx-color-yellow-50:oklch(98.7% .026 102.212);--hx-color-yellow-100:oklch(97.3% .071 103.193);--hx-color-yellow-200:oklch(94.5% .129 101.54);--hx-color-yellow-700:oklch(55.4% .135 66.442);--hx-color-yellow-900:oklch(42.1% .095 57.708);--hx-color-green-100:oklch(96.2% .044 156.743);--hx-color-green-200:oklch(92.5% .084 155.995);--hx-color-green-900:oklch(39.3% .095 152.535);--hx-color-blue-100:oklch(93.2% .032 255.585);--hx-color-blue-200:oklch(88.2% .059 254.128);--hx-color-blue-900:oklch(37.9% .146 265.522);--hx-color-indigo-100:oklch(93% .034 272.788);--hx-color-indigo-200:oklch(87% .065 274.039);--hx-color-indigo-900:oklch(35.9% .144 278.697);--hx-color-purple-100:oklch(94.6% .033 307.174);--hx-color-purple-200:oklch(90.2% .063 306.703);--hx-color-purple-900:oklch(38.1% .176 304.987);--hx-color-slate-50:oklch(98.4% .003 247.858);--hx-color-slate-100:oklch(96.8% .007 247.896);--hx-color-slate-900:oklch(20.8% .042 265.755);--hx-color-gray-50:oklch(98.5% .002 247.839);--hx-color-gray-100:oklch(96.7% .003 264.542);--hx-color-gray-200:oklch(92.8% .006 264.531);--hx-color-gray-300:oklch(87.2% .01 258.338);--hx-color-gray-400:oklch(70.7% .022 261.325);--hx-color-gray-500:oklch(55.1% .027 264.364);--hx-color-gray-600:oklch(44.6% .03 256.802);--hx-color-gray-700:oklch(37.3% .034 259.733);--hx-color-gray-800:oklch(27.8% .033 256.848);--hx-color-gray-900:oklch(21% .034 264.665);--hx-color-neutral-50:oklch(98.5% 0 0);--hx-color-neutral-200:oklch(92.2% 0 0);--hx-color-neutral-300:oklch(87% 0 0);--hx-color-neutral-400:oklch(70.8% 0 0);--hx-color-neutral-500:oklch(55.6% 0 0);--hx-color-neutral-600:oklch(43.9% 0 0);--hx-color-neutral-700:oklch(37.1% 0 0);--hx-color-neutral-800:oklch(26.9% 0 0);--hx-color-neutral-900:oklch(20.5% 0 0);--hx-color-black:#000;--hx-color-white:#fff;--hx-spacing:.25rem;--hx-breakpoint-xl:80rem;--hx-text-xs:.75rem;--hx-text-xs--line-height:calc(1/.75);--hx-text-sm:.875rem;--hx-text-sm--line-height:calc(1.25/.875);--hx-text-base:1rem;--hx-text-base--line-height:calc(1.5/1);--hx-text-lg:1.125rem;--hx-text-lg--line-height:calc(1.75/1.125);--hx-text-xl:1.25rem;--hx-text-xl--line-height:calc(1.75/1.25);--hx-text-2xl:1.5rem;--hx-text-2xl--line-height:calc(2/1.5);--hx-text-3xl:1.875rem;--hx-text-3xl--line-height:calc(2.25/1.875);--hx-text-4xl:2.25rem;--hx-text-4xl--line-height:calc(2.5/2.25);--hx-text-5xl:3rem;--hx-text-5xl--line-height:1;--hx-font-weight-normal:400;--hx-font-weight-medium:500;--hx-font-weight-semibold:600;--hx-font-weight-bold:700;--hx-font-weight-extrabold:800;--hx-tracking-tighter:-.05em;--hx-tracking-tight:-.025em;--hx-leading-tight:1.25;--hx-radius-xs:.125rem;--hx-radius-sm:.25rem;--hx-radius-md:.375rem;--hx-radius-lg:.5rem;--hx-radius-xl:.75rem;--hx-radius-3xl:1.5rem;--hx-ease-in:cubic-bezier(.4,0,1,1);--hx-ease-out:cubic-bezier(0,0,.2,1);--hx-ease-in-out:cubic-bezier(.4,0,.2,1);--hx-blur-md:12px;--hx-default-transition-duration:.15s;--hx-default-transition-timing-function:cubic-bezier(.4,0,.2,1);--hx-default-font-family:var(--hx-font-sans);--hx-default-mono-font-family:var(--hx-font-mono);--hx-color-primary-100:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*44));--hx-color-primary-200:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*36));--hx-color-primary-300:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*27));--hx-color-primary-400:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*16));--hx-color-primary-500:hsl(var(--primary-hue)var(--primary-saturation)var(--primary-lightness));--hx-color-primary-600:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*45));--hx-color-primary-700:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*39));--hx-color-primary-800:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*32));--hx-color-primary-900:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*24));--hx-color-dark:#111}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--hx-default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--hx-default-font-feature-settings,normal);font-variation-settings:var(--hx-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--hx-default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--hx-default-mono-font-feature-settings,normal);font-variation-settings:var(--hx-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports(color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}abbr:where([title]){cursor:help}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible:where(.dark,.dark *){--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}}@layer components;@layer utilities{.hx\:pointer-events-none{pointer-events:none}.hx\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hx\:absolute{position:absolute}.hx\:relative{position:relative}.hx\:sticky{position:sticky}.hx\:inset-x-0{inset-inline:calc(var(--hx-spacing)*0)}.hx\:inset-y-0{inset-block:calc(var(--hx-spacing)*0)}.hx\:top-0{top:calc(var(--hx-spacing)*0)}.hx\:top-1\/2{top:50%}.hx\:top-8{top:calc(var(--hx-spacing)*8)}.hx\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:top-\[40\%\]{top:40%}.hx\:top-full{top:100%}.hx\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:bottom-0{bottom:calc(var(--hx-spacing)*0)}.hx\:left-0{left:calc(var(--hx-spacing)*0)}.hx\:left-\[24px\]{left:24px}.hx\:left-\[36px\]{left:36px}.hx\:z-20{z-index:20}.hx\:z-\[-1\]{z-index:-1}.hx\:order-last{order:9999}.hx\:m-\[11px\]{margin:11px}.hx\:mx-1{margin-inline:calc(var(--hx-spacing)*1)}.hx\:mx-4{margin-inline:calc(var(--hx-spacing)*4)}.hx\:mx-auto{margin-inline:auto}.hx\:my-1{margin-block:calc(var(--hx-spacing)*1)}.hx\:my-1\.5{margin-block:calc(var(--hx-spacing)*1.5)}.hx\:my-2{margin-block:calc(var(--hx-spacing)*2)}.hx\:-mt-20{margin-top:calc(var(--hx-spacing)*-20)}.hx\:mt-1{margin-top:calc(var(--hx-spacing)*1)}.hx\:mt-1\.5{margin-top:calc(var(--hx-spacing)*1.5)}.hx\:mt-2{margin-top:calc(var(--hx-spacing)*2)}.hx\:mt-4{margin-top:calc(var(--hx-spacing)*4)}.hx\:mt-5{margin-top:calc(var(--hx-spacing)*5)}.hx\:mt-6{margin-top:calc(var(--hx-spacing)*6)}.hx\:mt-8{margin-top:calc(var(--hx-spacing)*8)}.hx\:mt-12{margin-top:calc(var(--hx-spacing)*12)}.hx\:mt-16{margin-top:calc(var(--hx-spacing)*16)}.hx\:mt-auto{margin-top:auto}.hx\:-mr-2{margin-right:calc(var(--hx-spacing)*-2)}.hx\:mr-1{margin-right:calc(var(--hx-spacing)*1)}.hx\:mr-2{margin-right:calc(var(--hx-spacing)*2)}.hx\:-mb-0\.5{margin-bottom:calc(var(--hx-spacing)*-.5)}.hx\:mb-0{margin-bottom:calc(var(--hx-spacing)*0)}.hx\:mb-2{margin-bottom:calc(var(--hx-spacing)*2)}.hx\:mb-4{margin-bottom:calc(var(--hx-spacing)*4)}.hx\:mb-6{margin-bottom:calc(var(--hx-spacing)*6)}.hx\:mb-8{margin-bottom:calc(var(--hx-spacing)*8)}.hx\:mb-10{margin-bottom:calc(var(--hx-spacing)*10)}.hx\:mb-12{margin-bottom:calc(var(--hx-spacing)*12)}.hx\:mb-16{margin-bottom:calc(var(--hx-spacing)*16)}.hx\:-ml-2{margin-left:calc(var(--hx-spacing)*-2)}.hx\:ml-2{margin-left:calc(var(--hx-spacing)*2)}.hx\:ml-4{margin-left:calc(var(--hx-spacing)*4)}.hx\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hx\:block{display:block}.hx\:flex{display:flex}.hx\:grid{display:grid}.hx\:hidden{display:none}.hx\:inline{display:inline}.hx\:inline-block{display:inline-block}.hx\:inline-flex{display:inline-flex}.hx\:aspect-auto{aspect-ratio:auto}.hx\:size-4{width:calc(var(--hx-spacing)*4);height:calc(var(--hx-spacing)*4)}.hx\:h-0{height:calc(var(--hx-spacing)*0)}.hx\:h-2{height:calc(var(--hx-spacing)*2)}.hx\:h-3\.5{height:calc(var(--hx-spacing)*3.5)}.hx\:h-4{height:calc(var(--hx-spacing)*4)}.hx\:h-5{height:calc(var(--hx-spacing)*5)}.hx\:h-7{height:calc(var(--hx-spacing)*7)}.hx\:h-10{height:calc(var(--hx-spacing)*10)}.hx\:h-16{height:calc(var(--hx-spacing)*16)}.hx\:h-\[18px\]{height:18px}.hx\:h-full{height:100%}.hx\:h-px{height:1px}.hx\:max-h-\(--menu-height\){max-height:var(--menu-height)}.hx\:max-h-64{max-height:calc(var(--hx-spacing)*64)}.hx\:max-h-\[calc\(100vh-var\(--navbar-height\)-env\(safe-area-inset-bottom\)\)\]{max-height:calc(100vh - var(--navbar-height) - env(safe-area-inset-bottom))}.hx\:max-h-\[min\(calc\(50vh-11rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(50vh - 11rem - env(safe-area-inset-bottom)),400px)}.hx\:min-h-\[100px\]{min-height:100px}.hx\:min-h-\[calc\(100vh-var\(--navbar-height\)\)\]{min-height:calc(100vh - var(--navbar-height))}.hx\:w-2{width:calc(var(--hx-spacing)*2)}.hx\:w-3\.5{width:calc(var(--hx-spacing)*3.5)}.hx\:w-4{width:calc(var(--hx-spacing)*4)}.hx\:w-10{width:calc(var(--hx-spacing)*10)}.hx\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:w-\[110\%\]{width:110%}.hx\:w-\[180\%\]{width:180%}.hx\:w-full{width:100%}.hx\:w-max{width:max-content}.hx\:w-screen{width:100vw}.hx\:max-w-\[50\%\]{max-width:50%}.hx\:max-w-\[90rem\]{max-width:90rem}.hx\:max-w-\[min\(calc\(100vw-2rem\)\,calc\(100\%\+20rem\)\)\]{max-width:min(100vw - 2rem,100% + 20rem)}.hx\:max-w-full{max-width:100%}.hx\:max-w-none{max-width:none}.hx\:max-w-screen-xl{max-width:var(--hx-breakpoint-xl)}.hx\:min-w-0{min-width:calc(var(--hx-spacing)*0)}.hx\:min-w-\[18px\]{min-width:18px}.hx\:min-w-\[24px\]{min-width:24px}.hx\:min-w-full{min-width:100%}.hx\:shrink-0{flex-shrink:0}.hx\:grow{flex-grow:1}.hx\:origin-center{transform-origin:50%}.hx\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hx\:cursor-default{cursor:default}.hx\:cursor-pointer{cursor:pointer}.hx\:scroll-my-6{scroll-margin-block:calc(var(--hx-spacing)*6)}.hx\:scroll-py-6{scroll-padding-block:calc(var(--hx-spacing)*6)}.hx\:list-none{list-style-type:none}.hx\:appearance-none{appearance:none}.hx\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.hx\:flex-col{flex-direction:column}.hx\:flex-wrap{flex-wrap:wrap}.hx\:items-baseline{align-items:baseline}.hx\:items-center{align-items:center}.hx\:items-start{align-items:flex-start}.hx\:justify-between{justify-content:space-between}.hx\:justify-center{justify-content:center}.hx\:justify-end{justify-content:flex-end}.hx\:justify-start{justify-content:flex-start}.hx\:justify-items-start{justify-items:start}.hx\:gap-1{gap:calc(var(--hx-spacing)*1)}.hx\:gap-1\.5{gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-2{gap:calc(var(--hx-spacing)*2)}.hx\:gap-4{gap:calc(var(--hx-spacing)*4)}.hx\:gap-x-1\.5{column-gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-x-2{column-gap:calc(var(--hx-spacing)*2)}.hx\:gap-y-1{row-gap:calc(var(--hx-spacing)*1)}.hx\:gap-y-2{row-gap:calc(var(--hx-spacing)*2)}.hx\:overflow-auto{overflow:auto}.hx\:overflow-hidden{overflow:hidden}.hx\:overflow-x-auto{overflow-x:auto}.hx\:overflow-x-hidden{overflow-x:hidden}.hx\:overflow-y-auto{overflow-y:auto}.hx\:overflow-y-hidden{overflow-y:hidden}.hx\:overscroll-contain{overscroll-behavior:contain}.hx\:overscroll-x-contain{overscroll-behavior-x:contain}.hx\:rounded-3xl{border-radius:var(--hx-radius-3xl)}.hx\:rounded-full{border-radius:340282e33px}.hx\:rounded-lg{border-radius:var(--hx-radius-lg)}.hx\:rounded-md{border-radius:var(--hx-radius-md)}.hx\:rounded-sm{border-radius:var(--hx-radius-sm)}.hx\:rounded-xl{border-radius:var(--hx-radius-xl)}.hx\:rounded-xs{border-radius:var(--hx-radius-xs)}.hx\:rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.hx\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:border-0{border-style:var(--tw-border-style);border-width:0}.hx\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.hx\:border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.hx\:border-amber-200{border-color:var(--hx-color-amber-200)}.hx\:border-black\/5{border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:border-black\/5{border-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:border-blue-200{border-color:var(--hx-color-blue-200)}.hx\:border-gray-200{border-color:var(--hx-color-gray-200)}.hx\:border-gray-500{border-color:var(--hx-color-gray-500)}.hx\:border-green-200{border-color:var(--hx-color-green-200)}.hx\:border-indigo-200{border-color:var(--hx-color-indigo-200)}.hx\:border-orange-100{border-color:var(--hx-color-orange-100)}.hx\:border-purple-200{border-color:var(--hx-color-purple-200)}.hx\:border-red-200{border-color:var(--hx-color-red-200)}.hx\:border-transparent{border-color:#0000}.hx\:border-yellow-100{border-color:var(--hx-color-yellow-100)}.hx\:bg-amber-100{background-color:var(--hx-color-amber-100)}.hx\:bg-black\/\[\.05\]{background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-black\/\[\.05\]{background-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:bg-blue-100{background-color:var(--hx-color-blue-100)}.hx\:bg-gray-100{background-color:var(--hx-color-gray-100)}.hx\:bg-gray-200{background-color:var(--hx-color-gray-200)}.hx\:bg-green-100{background-color:var(--hx-color-green-100)}.hx\:bg-indigo-100{background-color:var(--hx-color-indigo-100)}.hx\:bg-neutral-50{background-color:var(--hx-color-neutral-50)}.hx\:bg-neutral-900{background-color:var(--hx-color-neutral-900)}.hx\:bg-orange-50{background-color:var(--hx-color-orange-50)}.hx\:bg-primary-100{background-color:var(--hx-color-primary-100)}.hx\:bg-primary-400{background-color:var(--hx-color-primary-400)}.hx\:bg-primary-600{background-color:var(--hx-color-primary-600)}.hx\:bg-primary-700\/5{background-color:var(--hx-color-primary-700)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-primary-700\/5{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hx\:bg-purple-100{background-color:var(--hx-color-purple-100)}.hx\:bg-red-100{background-color:var(--hx-color-red-100)}.hx\:bg-transparent{background-color:#0000}.hx\:bg-white{background-color:var(--hx-color-white)}.hx\:bg-yellow-50{background-color:var(--hx-color-yellow-50)}.hx\:bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.hx\:from-gray-900{--tw-gradient-from:var(--hx-color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:to-gray-600{--tw-gradient-to:var(--hx-color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:bg-clip-text{-webkit-background-clip:text;background-clip:text}.hx\:p-0{padding:calc(var(--hx-spacing)*0)}.hx\:p-0\.5{padding:calc(var(--hx-spacing)*.5)}.hx\:p-1{padding:calc(var(--hx-spacing)*1)}.hx\:p-1\.5{padding:calc(var(--hx-spacing)*1.5)}.hx\:p-2{padding:calc(var(--hx-spacing)*2)}.hx\:p-4{padding:calc(var(--hx-spacing)*4)}.hx\:p-6{padding:calc(var(--hx-spacing)*6)}.hx\:px-1\.5{padding-inline:calc(var(--hx-spacing)*1.5)}.hx\:px-2{padding-inline:calc(var(--hx-spacing)*2)}.hx\:px-2\.5{padding-inline:calc(var(--hx-spacing)*2.5)}.hx\:px-3{padding-inline:calc(var(--hx-spacing)*3)}.hx\:px-4{padding-inline:calc(var(--hx-spacing)*4)}.hx\:px-6{padding-inline:calc(var(--hx-spacing)*6)}.hx\:px-8{padding-inline:calc(var(--hx-spacing)*8)}.hx\:py-1{padding-block:calc(var(--hx-spacing)*1)}.hx\:py-1\.5{padding-block:calc(var(--hx-spacing)*1.5)}.hx\:py-2{padding-block:calc(var(--hx-spacing)*2)}.hx\:py-2\.5{padding-block:calc(var(--hx-spacing)*2.5)}.hx\:py-3{padding-block:calc(var(--hx-spacing)*3)}.hx\:py-4{padding-block:calc(var(--hx-spacing)*4)}.hx\:py-12{padding-block:calc(var(--hx-spacing)*12)}.hx\:pt-4{padding-top:calc(var(--hx-spacing)*4)}.hx\:pt-6{padding-top:calc(var(--hx-spacing)*6)}.hx\:pt-8{padding-top:calc(var(--hx-spacing)*8)}.hx\:pr-2{padding-right:calc(var(--hx-spacing)*2)}.hx\:pr-4{padding-right:calc(var(--hx-spacing)*4)}.hx\:pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\]{padding-right:calc(env(safe-area-inset-right) - 1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-left),1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-right\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-right),1.5rem)}.hx\:pb-8{padding-bottom:calc(var(--hx-spacing)*8)}.hx\:pb-\[env\(safe-area-inset-bottom\)\]{padding-bottom:env(safe-area-inset-bottom)}.hx\:pb-px{padding-bottom:1px}.hx\:pl-6{padding-left:calc(var(--hx-spacing)*6)}.hx\:pl-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-left:max(env(safe-area-inset-left),1.5rem)}.hx\:text-center{text-align:center}.hx\:text-left{text-align:left}.hx\:align-\[-2\.5px\]{vertical-align:-2.5px}.hx\:align-baseline{vertical-align:baseline}.hx\:align-middle{vertical-align:middle}.hx\:align-text-bottom{vertical-align:text-bottom}.hx\:font-mono{font-family:var(--hx-font-mono)}.hx\:text-2xl{font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height))}.hx\:text-4xl{font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height))}.hx\:text-base{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height))}.hx\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}.hx\:text-xs{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height))}.hx\:text-\[\.65rem\]{font-size:.65rem}.hx\:text-\[10px\]{font-size:10px}.hx\:leading-5{--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5)}.hx\:leading-6{--tw-leading:calc(var(--hx-spacing)*6);line-height:calc(var(--hx-spacing)*6)}.hx\:leading-7{--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hx\:leading-none{--tw-leading:1;line-height:1}.hx\:leading-tight{--tw-leading:var(--hx-leading-tight);line-height:var(--hx-leading-tight)}.hx\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:font-extrabold{--tw-font-weight:var(--hx-font-weight-extrabold);font-weight:var(--hx-font-weight-extrabold)}.hx\:font-medium{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:font-normal{--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal)}.hx\:font-semibold{--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hx\:tracking-tight{--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight)}.hx\:tracking-tighter{--tw-tracking:var(--hx-tracking-tighter);letter-spacing:var(--hx-tracking-tighter)}.hx\:break-words,.hx\:wrap-break-word{overflow-wrap:break-word}.hx\:text-ellipsis{text-overflow:ellipsis}.hx\:whitespace-nowrap{white-space:nowrap}.hx\:text-\[color\:hsl\(var\(--primary-hue\)\,100\%\,50\%\)\]{color:hsl(var(--primary-hue),100%,50%)}.hx\:text-amber-900{color:var(--hx-color-amber-900)}.hx\:text-blue-900{color:var(--hx-color-blue-900)}.hx\:text-current{color:currentColor}.hx\:text-gray-100{color:var(--hx-color-gray-100)}.hx\:text-gray-500{color:var(--hx-color-gray-500)}.hx\:text-gray-600{color:var(--hx-color-gray-600)}.hx\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:text-green-900{color:var(--hx-color-green-900)}.hx\:text-indigo-900{color:var(--hx-color-indigo-900)}.hx\:text-orange-800{color:var(--hx-color-orange-800)}.hx\:text-primary-800{color:var(--hx-color-primary-800)}.hx\:text-purple-900{color:var(--hx-color-purple-900)}.hx\:text-red-900{color:var(--hx-color-red-900)}.hx\:text-slate-50{color:var(--hx-color-slate-50)}.hx\:text-slate-900{color:var(--hx-color-slate-900)}.hx\:text-transparent{color:#0000}.hx\:text-white{color:var(--hx-color-white)}.hx\:text-yellow-900{color:var(--hx-color-yellow-900)}.hx\:capitalize{text-transform:capitalize}.hx\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.hx\:no-underline{text-decoration-line:none}.hx\:underline{text-decoration-line:underline}.hx\:decoration-from-font{text-decoration-thickness:from-font}.hx\:underline-offset-2{text-underline-offset:2px}.hx\:opacity-0{opacity:0}.hx\:opacity-50{opacity:.5}.hx\:opacity-80{opacity:.8}.hx\:shadow-\[0_-12px_16px_\#fff\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_-12px_16px_white\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,white);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_2px_4px_rgba\(0\,0\,0\,\.02\)\,0_1px_0_rgba\(0\,0\,0\,\.06\)\]{--tw-shadow:0 2px 4px var(--tw-shadow-color,#00000005),0 1px 0 var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hextra-focus-visible:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}.hx\:hextra-focus-visible:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}.hx\:hextra-focus-visible-inset:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}.hx\:hextra-focus-visible-inset:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800)}.hx\:shadow-gray-100{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:shadow-gray-100{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}.hx\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:duration-75{--tw-duration:75ms;transition-duration:75ms}.hx\:duration-150{--tw-duration:.15s;transition-duration:.15s}.hx\:duration-200{--tw-duration:.2s;transition-duration:.2s}.hx\:ease-in{--tw-ease:var(--hx-ease-in);transition-timing-function:var(--hx-ease-in)}.hx\:ease-in-out{--tw-ease:var(--hx-ease-in-out);transition-timing-function:var(--hx-ease-in-out)}.hx\:outline-none{--tw-outline-style:none;outline-style:none}.hx\:select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hx\:group-hover\:text-gray-900:is(:where(.hx\:group):hover *){color:var(--hx-color-gray-900)}.hx\:group-hover\:underline:is(:where(.hx\:group):hover *){text-decoration-line:underline}.hx\:group-hover\/code\:opacity-100:is(:where(.hx\:group\/code):hover *){opacity:1}}.hx\:group-data-\[active\=true\]\:text-primary-800:is(:where(.hx\:group)[data-active=true] *){color:var(--hx-color-primary-800)}.hx\:group-data-\[theme\=dark\]\:hidden:is(:where(.hx\:group)[data-theme=dark] *),.hx\:group-data-\[theme\=light\]\:hidden:is(:where(.hx\:group)[data-theme=light] *),.hx\:group-data-\[theme\=system\]\:hidden:is(:where(.hx\:group)[data-theme=system] *){display:none}.hx\:group-\[\.copied\]\/copybtn\:block:is(:where(.hx\:group\/copybtn).copied *){display:block}.hx\:group-\[\.copied\]\/copybtn\:hidden:is(:where(.hx\:group\/copybtn).copied *){display:none}.hx\:placeholder\:text-gray-500::placeholder{color:var(--hx-color-gray-500)}.hx\:before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.hx\:before\:absolute:before{content:var(--tw-content);position:absolute}.hx\:before\:inset-0:before{content:var(--tw-content);inset:calc(var(--hx-spacing)*0)}.hx\:before\:inset-y-1:before{content:var(--tw-content);inset-block:calc(var(--hx-spacing)*1)}.hx\:before\:mr-1:before{content:var(--tw-content);margin-right:calc(var(--hx-spacing)*1)}.hx\:before\:inline-block:before{content:var(--tw-content);display:inline-block}.hx\:before\:w-px:before{content:var(--tw-content);width:1px}.hx\:before\:bg-gray-200:before{content:var(--tw-content);background-color:var(--hx-color-gray-200)}.hx\:before\:opacity-25:before{content:var(--tw-content);opacity:.25}.hx\:before\:transition-transform:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:before\:content-\[\"\"\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\'\#\'\]:before{--tw-content:"#";content:var(--tw-content)}.hx\:before\:content-\[\'\'\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.hx\:group-open\:before\:rotate-90:is(:where(.hx\:group):is([open],:popover-open,:open) *):before{content:var(--tw-content);rotate:90deg}.hx\:first\:mt-0:first-child{margin-top:calc(var(--hx-spacing)*0)}.hx\:last-of-type\:mb-0:last-of-type{margin-bottom:calc(var(--hx-spacing)*0)}@media(hover:hover){.hx\:hover\:border-gray-200:hover{border-color:var(--hx-color-gray-200)}.hx\:hover\:border-gray-300:hover{border-color:var(--hx-color-gray-300)}.hx\:hover\:border-gray-400:hover{border-color:var(--hx-color-gray-400)}.hx\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}.hx\:hover\:bg-gray-100:hover{background-color:var(--hx-color-gray-100)}.hx\:hover\:bg-gray-800\/5:hover{background-color:var(--hx-color-gray-800)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:bg-gray-800\/5:hover{background-color:color-mix(in oklab,var(--hx-color-gray-800)5%,transparent)}}.hx\:hover\:bg-primary-700:hover{background-color:var(--hx-color-primary-700)}.hx\:hover\:bg-slate-50:hover{background-color:var(--hx-color-slate-50)}.hx\:hover\:text-black:hover{color:var(--hx-color-black)}.hx\:hover\:text-gray-700:hover{color:var(--hx-color-gray-700)}.hx\:hover\:text-gray-800:hover{color:var(--hx-color-gray-800)}.hx\:hover\:text-gray-900:hover{color:var(--hx-color-gray-900)}.hx\:hover\:text-primary-600:hover{color:var(--hx-color-primary-600)}.hx\:hover\:opacity-60:hover{opacity:.6}.hx\:hover\:opacity-75:hover{opacity:.75}.hx\:hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}}.hx\:focus-visible\:not-sr-only:focus-visible{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.hx\:focus-visible\:fixed:focus-visible{position:fixed}.hx\:focus-visible\:top-2:focus-visible{top:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:left-2:focus-visible{left:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:z-50:focus-visible{z-index:50}.hx\:focus-visible\:rounded-md:focus-visible{border-radius:var(--hx-radius-md)}.hx\:focus-visible\:bg-primary-500:focus-visible{background-color:var(--hx-color-primary-500)}.hx\:focus-visible\:bg-white:focus-visible{background-color:var(--hx-color-white)}.hx\:focus-visible\:px-4:focus-visible{padding-inline:calc(var(--hx-spacing)*4)}.hx\:focus-visible\:py-2:focus-visible{padding-block:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:text-sm:focus-visible{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:focus-visible\:font-medium:focus-visible{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:focus-visible\:text-white:focus-visible{color:var(--hx-color-white)}.hx\:active\:bg-gray-400\/20:active{background-color:var(--hx-color-gray-400)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:bg-gray-400\/20:active{background-color:color-mix(in oklab,var(--hx-color-gray-400)20%,transparent)}}.hx\:active\:opacity-50:active{opacity:.5}.hx\:active\:shadow-sm:active{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:active\:shadow-gray-200:active{--tw-shadow-color:var(--hx-color-gray-200)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:shadow-gray-200:active{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-200)var(--tw-shadow-alpha),transparent)}}.hx\:data-\[state\=closed\]\:hidden[data-state=closed],.hx\:data-\[state\=open\]\:hidden[data-state=open]{display:none}.hx\:data-\[state\=selected\]\:block[data-state=selected]{display:block}.hx\:data-\[state\=selected\]\:border-primary-500[data-state=selected]{border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:text-primary-600[data-state=selected]{color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:contrast-more\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:contrast-more\:border-current{border-color:initial}.hx\:contrast-more\:border-gray-800{border-color:var(--hx-color-gray-800)}.hx\:contrast-more\:border-gray-900{border-color:var(--hx-color-gray-900)}.hx\:contrast-more\:border-neutral-400{border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:border-primary-500{border-color:var(--hx-color-primary-500)}.hx\:contrast-more\:border-transparent{border-color:#0000}.hx\:contrast-more\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:contrast-more\:text-current{color:currentColor}.hx\:contrast-more\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:contrast-more\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:contrast-more\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:contrast-more\:underline{text-decoration-line:underline}.hx\:contrast-more\:shadow-\[0_0_0_1px_\#000\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}}}@media not all and (min-width:80rem){.hx\:max-xl\:hidden{display:none}}@media not all and (min-width:64rem){.hx\:max-lg\:min-h-\[340px\]{min-height:340px}}@media not all and (min-width:48rem){.hx\:max-md\:sticky{position:sticky}.hx\:max-md\:hidden{display:none}.hx\:max-md\:min-h-\[340px\]{min-height:340px}.hx\:max-md\:\[transform\:translate3d\(0\,-100\%\,0\)\]{transform:translateY(-100%)}.hx\:max-md\:\[transform\:translate3d\(0\,0\,0\)\]{transform:translate(0)}}@media not all and (min-width:40rem){.hx\:max-sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}}@media(min-width:40rem){.hx\:sm\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:sm\:left-auto{left:auto}.hx\:sm\:block{display:block}.hx\:sm\:flex{display:flex}.hx\:sm\:w-\[110\%\]{width:110%}.hx\:sm\:flex-row{flex-direction:row}.hx\:sm\:items-center{align-items:center}.hx\:sm\:items-start{align-items:flex-start}.hx\:sm\:justify-between{justify-content:space-between}.hx\:sm\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}@media not all and (min-width:64rem){.hx\:sm\:max-lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@media(min-width:48rem){.hx\:md\:sticky{position:sticky}.hx\:md\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:md\:mr-0{margin-right:calc(var(--hx-spacing)*0)}.hx\:md\:hidden{display:none}.hx\:md\:inline-block{display:inline-block}.hx\:md\:inline-flex{display:inline-flex}.hx\:md\:aspect-\[1\.1\/1\]{aspect-ratio:1.1}.hx\:md\:h-\[calc\(100vh-var\(--navbar-height\)-var\(--menu-height\)\)\]{height:calc(100vh - var(--navbar-height) - var(--menu-height))}.hx\:md\:max-h-\[min\(calc\(100vh-5rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(100vh - 5rem - env(safe-area-inset-bottom)),400px)}.hx\:md\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:md\:shrink-0{flex-shrink:0}.hx\:md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.hx\:md\:justify-start{justify-content:flex-start}.hx\:md\:self-start{align-self:flex-start}.hx\:md\:overflow-auto{overflow:auto}.hx\:md\:px-12{padding-inline:calc(var(--hx-spacing)*12)}.hx\:md\:pt-12{padding-top:calc(var(--hx-spacing)*12)}.hx\:md\:text-3xl{font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height))}.hx\:md\:text-5xl{font-size:var(--hx-text-5xl);line-height:var(--tw-leading,var(--hx-text-5xl--line-height))}.hx\:md\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:md\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}}@media(min-width:64rem){.hx\:lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:80rem){.hx\:xl\:block{display:block}.hx\:xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.hx\:ltr\:right-1\.5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*1.5)}.hx\:ltr\:right-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*2)}.hx\:ltr\:right-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*3)}.hx\:ltr\:-mr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:calc(var(--hx-spacing)*-4)}.hx\:ltr\:mr-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:auto}.hx\:ltr\:ml-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.hx\:ltr\:ml-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:ml-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:auto}.hx\:ltr\:rotate-180:where(:dir(ltr),[dir=ltr],[dir=ltr] *){rotate:180deg}.hx\:ltr\:rounded-l-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:ltr\:rounded-r-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:ltr\:border-l:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);border-left-width:1px}.hx\:ltr\:pr-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*0)}.hx\:ltr\:pr-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*1)}.hx\:ltr\:pr-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*2)}.hx\:ltr\:pr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:ltr\:pr-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:ltr\:pr-9:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*9)}.hx\:ltr\:pl-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:pl-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:ltr\:pl-5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*5)}.hx\:ltr\:pl-6:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*6)}.hx\:ltr\:pl-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:ltr\:pl-12:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*12)}.hx\:ltr\:pl-16:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*16)}.hx\:ltr\:text-right:where(:dir(ltr),[dir=ltr],[dir=ltr] *){text-align:right}.hx\:ltr\:before\:left-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);left:calc(var(--hx-spacing)*0)}@media(min-width:48rem){.hx\:ltr\:md\:left-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:auto}}.hx\:rtl\:left-1\.5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*1.5)}.hx\:rtl\:left-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*2)}.hx\:rtl\:left-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}.hx\:rtl\:mr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto}.hx\:rtl\:-ml-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:calc(var(--hx-spacing)*-4)}.hx\:rtl\:ml-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:auto}.hx\:rtl\:-rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:-180deg}.hx\:rtl\:rotate-270:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:270deg}.hx\:rtl\:rounded-l-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:rtl\:rounded-r-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:rtl\:border-r:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:1px}.hx\:rtl\:pr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:pr-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:rtl\:pr-5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*5)}.hx\:rtl\:pr-6:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*6)}.hx\:rtl\:pr-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:rtl\:pr-12:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*12)}.hx\:rtl\:pr-16:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*16)}.hx\:rtl\:pl-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*0)}.hx\:rtl\:pl-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*1)}.hx\:rtl\:pl-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*2)}.hx\:rtl\:pl-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:rtl\:pl-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:rtl\:pl-9:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*9)}.hx\:rtl\:text-left:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:left}.hx\:rtl\:before\:right-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);right:calc(var(--hx-spacing)*0)}.hx\:rtl\:before\:rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);rotate:180deg}@media(min-width:48rem){.hx\:rtl\:md\:right-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:auto}}.hx\:dark\:block:where(.dark,.dark *){display:block}.hx\:dark\:hidden:where(.dark,.dark *){display:none}.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:var(--hx-color-amber-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-amber-200)30%,transparent)}}.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:var(--hx-color-blue-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-blue-200)30%,transparent)}}.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-gray-100)20%,transparent)}}.hx\:dark\:border-gray-400:where(.dark,.dark *){border-color:var(--hx-color-gray-400)}.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:var(--hx-color-green-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-green-200)30%,transparent)}}.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:var(--hx-color-indigo-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-indigo-200)30%,transparent)}}.hx\:dark\:border-neutral-700:where(.dark,.dark *){border-color:var(--hx-color-neutral-700)}.hx\:dark\:border-neutral-800:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-orange-400)30%,transparent)}}.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:var(--hx-color-purple-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-purple-200)30%,transparent)}}.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:var(--hx-color-red-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-red-200)30%,transparent)}}.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:var(--hx-color-yellow-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-yellow-200)30%,transparent)}}.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:var(--hx-color-amber-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-amber-900)30%,transparent)}}.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:var(--hx-color-blue-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-blue-900)30%,transparent)}}.hx\:dark\:bg-dark:where(.dark,.dark *),.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:var(--hx-color-dark)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)50%,transparent)}}.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:var(--hx-color-gray-50)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-50)10%,transparent)}}.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:var(--hx-color-green-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-green-900)30%,transparent)}}.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:var(--hx-color-indigo-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-indigo-900)30%,transparent)}}.hx\:dark\:bg-neutral-700:where(.dark,.dark *){background-color:var(--hx-color-neutral-700)}.hx\:dark\:bg-neutral-800:where(.dark,.dark *){background-color:var(--hx-color-neutral-800)}.hx\:dark\:bg-neutral-900:where(.dark,.dark *){background-color:var(--hx-color-neutral-900)}.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-orange-400)20%,transparent)}}.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-400)10%,transparent)}}.hx\:dark\:bg-primary-600:where(.dark,.dark *){background-color:var(--hx-color-primary-600)}.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:var(--hx-color-purple-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-purple-900)30%,transparent)}}.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:var(--hx-color-red-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-red-900)30%,transparent)}}.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:var(--hx-color-yellow-700)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-yellow-700)30%,transparent)}}.hx\:dark\:from-gray-100:where(.dark,.dark *){--tw-gradient-from:var(--hx-color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:to-gray-400:where(.dark,.dark *){--tw-gradient-to:var(--hx-color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:text-amber-200:where(.dark,.dark *){color:var(--hx-color-amber-200)}.hx\:dark\:text-blue-200:where(.dark,.dark *){color:var(--hx-color-blue-200)}.hx\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:dark\:text-gray-200:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hx\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:dark\:text-gray-400:where(.dark,.dark *){color:var(--hx-color-gray-400)}.hx\:dark\:text-green-200:where(.dark,.dark *){color:var(--hx-color-green-200)}.hx\:dark\:text-indigo-200:where(.dark,.dark *){color:var(--hx-color-indigo-200)}.hx\:dark\:text-neutral-200:where(.dark,.dark *){color:var(--hx-color-neutral-200)}.hx\:dark\:text-neutral-400:where(.dark,.dark *){color:var(--hx-color-neutral-400)}.hx\:dark\:text-orange-300:where(.dark,.dark *){color:var(--hx-color-orange-300)}.hx\:dark\:text-primary-600:where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:text-purple-200:where(.dark,.dark *){color:var(--hx-color-purple-200)}.hx\:dark\:text-red-200:where(.dark,.dark *){color:var(--hx-color-red-200)}.hx\:dark\:text-slate-100:where(.dark,.dark *){color:var(--hx-color-slate-100)}.hx\:dark\:text-white:where(.dark,.dark *){color:var(--hx-color-white)}.hx\:dark\:text-yellow-200:where(.dark,.dark *){color:var(--hx-color-yellow-200)}.hx\:dark\:opacity-80:where(.dark,.dark *){opacity:.8}.hx\:dark\:shadow-\[0_-1px_0_rgba\(255\,255\,255\,\.1\)_inset\]:where(.dark,.dark *){--tw-shadow:0 -1px 0 var(--tw-shadow-color,#ffffff1a)inset;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-\[0_-12px_16px_\#111\]:where(.dark,.dark *){--tw-shadow:0 -12px 16px var(--tw-shadow-color,#111);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:dark\:group-hover\:text-gray-50:where(.dark,.dark *):is(:where(.hx\:group):hover *){color:var(--hx-color-gray-50)}}.hx\:group-data-\[active\=true\]\:dark\:text-primary-600:is(:where(.hx\:group)[data-active=true] *):where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:placeholder\:text-gray-400:where(.dark,.dark *)::placeholder{color:var(--hx-color-gray-400)}.hx\:dark\:before\:bg-neutral-800:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hx\:dark\:before\:invert:where(.dark,.dark *):before{content:var(--tw-content);--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media(hover:hover){.hx\:dark\:hover\:border-gray-100:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-100)}.hx\:dark\:hover\:border-gray-600:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-600)}.hx\:dark\:hover\:border-neutral-500:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-500)}.hx\:dark\:hover\:border-neutral-700:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:border-neutral-800:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-gray-100)5%,transparent)}}.hx\:dark\:hover\:bg-neutral-700:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:bg-neutral-800:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-neutral-900:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-900)}.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-primary-100)5%,transparent)}}.hx\:dark\:hover\:bg-primary-700:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-700)}.hx\:dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--hx-color-gray-50)}.hx\:dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--hx-color-gray-100)}.hx\:dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--hx-color-gray-200)}.hx\:dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--hx-color-gray-300)}.hx\:dark\:hover\:text-neutral-50:where(.dark,.dark *):hover{color:var(--hx-color-neutral-50)}.hx\:dark\:hover\:text-primary-400:where(.dark,.dark *):hover{color:var(--hx-color-primary-400)}.hx\:dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--hx-color-white)}.hx\:dark\:hover\:shadow-none:where(.dark,.dark *):hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.hx\:dark\:focus-visible\:bg-dark:where(.dark,.dark *):focus-visible{background-color:var(--hx-color-dark)}.hx\:data-\[state\=selected\]\:dark\:border-primary-500[data-state=selected]:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:dark\:text-primary-600[data-state=selected]:where(.dark,.dark *){color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:dark\:border-current:where(.dark,.dark *){border-color:initial}.hx\:contrast-more\:dark\:border-gray-50:where(.dark,.dark *){border-color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:border-primary-500:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:dark\:contrast-more\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:text-current:where(.dark,.dark *){color:currentColor}.hx\:contrast-more\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:contrast-more\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:contrast-more\:dark\:shadow-\[0_0_0_1px_\#fff\]:where(.dark,.dark *){--tw-shadow:0 0 0 1px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:dark\:hover\:border-gray-50:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-50)}}}@media print{.hx\:print\:\[display\:none\],.hx\:print\:hidden{display:none}.hx\:print\:bg-transparent{background-color:#0000}}}html{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--hx-color-white);width:100%}body:where(.dark,.dark *){background-color:var(--hx-color-dark);color:var(--hx-color-gray-100)}:root{--primary-hue:212deg;--primary-saturation:100%;--primary-lightness:50%;--navbar-height:4rem;--hextra-banner-height:2rem;--menu-height:3.75rem}.dark{--primary-hue:204deg;--primary-saturation:100%;--primary-lightness:50%}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*2);font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height));--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-neutral-200)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-neutral-200)70%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){padding-bottom:calc(var(--hx-spacing)*1);font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--hx-color-neutral-400)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)10%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--hx-color-primary-600);text-decoration-line:underline;text-decoration-thickness:from-font}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);border-color:var(--hx-color-gray-300);color:var(--hx-color-gray-700);font-style:italic}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);padding-left:calc(var(--hx-spacing)*6);border-left-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);padding-right:calc(var(--hx-spacing)*6);border-right-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-gray-700);color:var(--hx-color-gray-400)}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){padding-block:calc(var(--hx-spacing)*4);--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*6);border-collapse:collapse;width:100%;padding:calc(var(--hx-spacing)*0);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);display:block;overflow-x:auto}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead{background-color:var(--hx-color-gray-50)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:var(--hx-color-gray-600)}@supports(color:color-mix(in lab,red,red)){.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-600)20%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr{margin:calc(var(--hx-spacing)*0);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:decimal}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:disc}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):has(li input[type=checkbox]){list-style-type:none}.content :where(ul,ol)>li>:where(ul,ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*0)}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:#0000}.content :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-inline:auto;margin-block:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-md)}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption{margin-top:calc(var(--hx-spacing)*2);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-500);display:block}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption:where(.dark,.dark *){color:var(--hx-color-gray-400)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dt{margin-top:calc(var(--hx-spacing)*6);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dd{margin-block:calc(var(--hx-spacing)*2);padding-inline-start:calc(var(--hx-spacing)*6)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*10);border-color:var(--hx-color-gray-200)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):last-child{margin-bottom:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .footnotes{margin-top:calc(var(--hx-spacing)*12);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.content .footnotes hr{border-color:var(--hx-color-gray-200)}.content .footnotes hr:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .subheading-anchor{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.content .subheading-anchor:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.content .subheading-anchor:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}span:target+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor),.content .subheading-anchor:focus-visible{opacity:1}span+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor){text-decoration-line:none!important}.content .subheading-anchor:after{content:var(--tw-content);color:var(--hx-color-gray-300)}.content .subheading-anchor:where(.dark,.dark *):after{content:var(--tw-content);color:var(--hx-color-neutral-700)}.content .subheading-anchor:after{padding-inline:calc(var(--hx-spacing)*1);--tw-content:"#";content:var(--tw-content)}span:target+:is(){color:var(--hx-color-gray-400)}span:target+:is():where(.dark,.dark *){color:var(--hx-color-neutral-500)}article details>summary::-webkit-details-marker{display:none}article details>summary:before{vertical-align:-4px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='hx:h-5 hx:w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E");width:1.2em;height:1.2em;padding:0 .6em}:lang(fa) ol{list-style-type:persian}.highlight .chroma .err{color:#a61717;background-color:#e3d2d2}.highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.highlight .chroma .line{display:flex}.highlight .chroma .k,.highlight .chroma .kc,.highlight .chroma .kd,.highlight .chroma .kn,.highlight .chroma .kp,.highlight .chroma .kr{color:#000;font-weight:700}.highlight .chroma .kt{color:#458;font-weight:700}.highlight .chroma .na{color:teal}.highlight .chroma .nb{color:#0086b3}.highlight .chroma .bp{color:#999}.highlight .chroma .nc{color:#458;font-weight:700}.highlight .chroma .no{color:teal}.highlight .chroma .nd{color:#3c5d5d;font-weight:700}.highlight .chroma .ni{color:purple}.highlight .chroma .ne,.highlight .chroma .nf,.highlight .chroma .nl{color:#900;font-weight:700}.highlight .chroma .nn{color:#555}.highlight .chroma .nt{color:navy}.highlight .chroma .nv,.highlight .chroma .vc,.highlight .chroma .vg,.highlight .chroma .vi{color:teal}.highlight .chroma .s,.highlight .chroma .sa,.highlight .chroma .sb,.highlight .chroma .sc,.highlight .chroma .dl,.highlight .chroma .sd,.highlight .chroma .s2,.highlight .chroma .se,.highlight .chroma .sh,.highlight .chroma .si,.highlight .chroma .sx{color:#d14}.highlight .chroma .sr{color:#009926}.highlight .chroma .s1{color:#d14}.highlight .chroma .ss{color:#990073}.highlight .chroma .m,.highlight .chroma .mb,.highlight .chroma .mf,.highlight .chroma .mh,.highlight .chroma .mi,.highlight .chroma .il,.highlight .chroma .mo{color:#099}.highlight .chroma .o,.highlight .chroma .ow{color:#000;font-weight:700}.highlight .chroma .c,.highlight .chroma .ch,.highlight .chroma .cm,.highlight .chroma .c1{color:#998;font-style:italic}.highlight .chroma .cs,.highlight .chroma .cp,.highlight .chroma .cpf{color:#999;font-style:italic;font-weight:700}.highlight .chroma .gd{color:#000;background-color:#fdd}.highlight .chroma .ge{color:#000;font-style:italic}.highlight .chroma .gr{color:#a00}.highlight .chroma .gh{color:#999}.highlight .chroma .gi{color:#000;background-color:#dfd}.highlight .chroma .go{color:#888}.highlight .chroma .gp{color:#555}.highlight .chroma .gs{font-weight:700}.highlight .chroma .gu{color:#aaa}.highlight .chroma .gt{color:#a00}.highlight .chroma .gl{text-decoration:underline}.highlight .chroma .w{color:#bbb}.dark .highlight .chroma .err{color:#f85149}.dark .highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.dark .highlight .chroma .line{display:flex}.dark .highlight .chroma .k{color:#ff7b72}.dark .highlight .chroma .kc{color:#79c0ff}.dark .highlight .chroma .kd,.dark .highlight .chroma .kn{color:#ff7b72}.dark .highlight .chroma .kp{color:#79c0ff}.dark .highlight .chroma .kr,.dark .highlight .chroma .kt{color:#ff7b72}.dark .highlight .chroma .nc{color:#f0883e;font-weight:700}.dark .highlight .chroma .no{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nd{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .ni{color:#ffa657}.dark .highlight .chroma .ne{color:#f0883e;font-weight:700}.dark .highlight .chroma .nf{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .nl{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nn{color:#ff7b72}.dark .highlight .chroma .py{color:#79c0ff}.dark .highlight .chroma .nt{color:#7ee787}.dark .highlight .chroma .nv{color:#79c0ff}.dark .highlight .chroma .l{color:#a5d6ff}.dark .highlight .chroma .ld{color:#79c0ff}.dark .highlight .chroma .s{color:#a5d6ff}.dark .highlight .chroma .sa{color:#79c0ff}.dark .highlight .chroma .sb,.dark .highlight .chroma .sc{color:#a5d6ff}.dark .highlight .chroma .dl{color:#79c0ff}.dark .highlight .chroma .sd,.dark .highlight .chroma .s2{color:#a5d6ff}.dark .highlight .chroma .se,.dark .highlight .chroma .sh{color:#79c0ff}.dark .highlight .chroma .si,.dark .highlight .chroma .sx{color:#a5d6ff}.dark .highlight .chroma .sr{color:#79c0ff}.dark .highlight .chroma .s1,.dark .highlight .chroma .ss,.dark .highlight .chroma .m,.dark .highlight .chroma .mb,.dark .highlight .chroma .mf,.dark .highlight .chroma .mh,.dark .highlight .chroma .mi,.dark .highlight .chroma .il,.dark .highlight .chroma .mo{color:#a5d6ff}.dark .highlight .chroma .o,.dark .highlight .chroma .ow{color:#ff7b72;font-weight:700}.dark .highlight .chroma .c,.dark .highlight .chroma .ch,.dark .highlight .chroma .cm,.dark .highlight .chroma .c1{color:#8b949e;font-style:italic}.dark .highlight .chroma .cs,.dark .highlight .chroma .cp,.dark .highlight .chroma .cpf{color:#8b949e;font-style:italic;font-weight:700}.dark .highlight .chroma .gd{color:#ffa198;background-color:#490202}.dark .highlight .chroma .ge{color:inherit;font-style:italic}.dark .highlight .chroma .gr{color:#ffa198}.dark .highlight .chroma .gh{color:#79c0ff;font-weight:700}.dark .highlight .chroma .gi{color:#56d364;background-color:#0f5323}.dark .highlight .chroma .go,.dark .highlight .chroma .gp{color:#8b949e}.dark .highlight .chroma .gs{font-weight:700}.dark .highlight .chroma .gu{color:#79c0ff}.dark .highlight .chroma .gt{color:#ff7b72}.dark .highlight .chroma .gl{text-decoration:underline}.dark .highlight .chroma .w{color:#6e7681}.hextra-code-block{--tw-leading:calc(var(--hx-spacing)*5);font-size:.9em;line-height:calc(var(--hx-spacing)*5)}.hextra-code-block pre{background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block pre{--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.hextra-code-block pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.hextra-code-block pre{--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.hextra-code-block pre:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.hextra-code-block pre:where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.hextra-code-block .hextra-code-filename{top:calc(var(--hx-spacing)*0);z-index:1;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:var(--hx-radius-xl);border-top-right-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);width:100%;position:absolute;overflow:hidden}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block .hextra-code-filename{padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*2);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));color:var(--hx-color-gray-700)}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hextra-code-block .hextra-code-filename+pre:not(.lntable pre){padding-top:calc(var(--hx-spacing)*12)}.hextra-code-block pre:not(.lntable pre){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*4)}.hextra-code-block div:nth-of-type(2) pre{padding-top:calc(var(--hx-spacing)*12);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .lntable{margin:calc(var(--hx-spacing)*0);border-radius:var(--hx-radius-xl);width:auto;display:block;overflow:auto}.chroma .lntable pre{padding-top:calc(var(--hx-spacing)*4);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line){min-width:2.6rem;padding-right:calc(var(--hx-spacing)*4);padding-left:calc(var(--hx-spacing)*4);color:var(--hx-color-neutral-600)}:is(.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line)):where(.dark,.dark *){color:var(--hx-color-neutral-300)}.chroma .lntd{padding:calc(var(--hx-spacing)*0);vertical-align:top}.chroma .lntd:last-of-type{width:100%}.chroma .hl{background-color:var(--hx-color-primary-800);width:100%;display:block}@supports(color:color-mix(in lab,red,red)){.chroma .hl{background-color:color-mix(in oklab,var(--hx-color-primary-800)10%,transparent)}}.hextra-cards{grid-template-columns:repeat(auto-fill,minmax(max(250px,calc((100% - 1rem*2)/var(--hextra-cards-grid-cols))),1fr))}.hextra-card{position:relative}.hextra-card img{-webkit-user-select:none;user-select:none}.hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card .hextra-card-icon svg{color:#0003;width:1.5rem;transition:color .3s}.hextra-card p{margin-top:.5rem;position:relative}.dark .hextra-card .hextra-card-icon svg{color:#fff6}.dark .hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card-tag{z-index:10;position:absolute;top:5px}.hextra-card-tag:where(:dir(ltr)){right:5px}.hextra-card-tag:where(:dir(rtl)){left:5px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker){counter-increment:step}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);margin-left:-41px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);margin-right:-44px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);background-color:var(--hx-color-gray-100)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:4px;border-color:var(--hx-color-white)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);border-color:var(--hx-color-dark)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step);text-align:center;text-indent:-1px;width:33px;height:33px;font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal);color:var(--hx-color-neutral-400);border-radius:340282e33px;position:absolute}:lang(fa) .hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step,persian)}.hextra-search-wrapper li{margin-inline:calc(var(--hx-spacing)*2.5);border-radius:var(--hx-radius-md);overflow-wrap:break-word;color:var(--hx-color-gray-800)}@media(prefers-contrast:more){.hextra-search-wrapper li{border-style:var(--tw-border-style);border-width:1px;border-color:#0000}}.hextra-search-wrapper li:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hextra-search-wrapper li a{scroll-margin:calc(var(--hx-spacing)*12);padding-inline:calc(var(--hx-spacing)*2.5);padding-block:calc(var(--hx-spacing)*2);display:block}.hextra-search-wrapper li a:focus-visible{--tw-outline-style:none;outline-style:none}.hextra-search-wrapper li .hextra-search-title{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hextra-search-wrapper li .hextra-search-active{border-radius:var(--hx-radius-md);background-color:var(--hx-color-primary-500)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper li .hextra-search-active{background-color:color-mix(in oklab,var(--hx-color-primary-500)10%,transparent)}}@media(prefers-contrast:more){.hextra-search-wrapper li .hextra-search-active{border-color:var(--hx-color-primary-500)}}.hextra-search-wrapper .hextra-search-no-result{padding:calc(var(--hx-spacing)*8);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-400);-webkit-user-select:none;user-select:none;display:block}.hextra-search-wrapper .hextra-search-prefix{margin-inline:calc(var(--hx-spacing)*2.5);margin-top:calc(var(--hx-spacing)*6);margin-bottom:calc(var(--hx-spacing)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix{border-color:color-mix(in oklab,var(--hx-color-black)10%,transparent)}}.hextra-search-wrapper .hextra-search-prefix{padding-inline:calc(var(--hx-spacing)*2.5);padding-bottom:calc(var(--hx-spacing)*1.5);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);color:var(--hx-color-gray-500);text-transform:uppercase;-webkit-user-select:none;user-select:none}.hextra-search-wrapper .hextra-search-prefix:first-child{margin-top:calc(var(--hx-spacing)*0)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix{border-color:var(--hx-color-gray-600);color:var(--hx-color-gray-900)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)20%,transparent)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){color:var(--hx-color-gray-300)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-gray-50);color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{margin-top:calc(var(--hx-spacing)*1);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:1.35rem;text-overflow:ellipsis;color:var(--hx-color-gray-600);line-height:1.35rem;overflow:hidden}.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-400)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box}.hextra-search-wrapper .hextra-search-match{color:var(--hx-color-primary-600)}@media(max-width:48rem){.hextra-sidebar-container{top:calc(var(--hx-spacing)*0);bottom:calc(var(--hx-spacing)*0);z-index:15;overscroll-behavior:contain;background-color:var(--hx-color-white);width:100%;padding-top:calc(var(--navbar-height) + var(--hextra-banner-height));position:fixed}.hextra-sidebar-container:where(.dark,.dark *){background-color:var(--hx-color-dark)}.hextra-sidebar-container{will-change:transform,opacity;contain:layout style;backface-visibility:hidden;transition:transform .4s cubic-bezier(.52,.16,.04,1)}}.hextra-sidebar-container li>.hextra-sidebar-children{height:calc(var(--hx-spacing)*0)}.hextra-sidebar-container li.open>.hextra-sidebar-children{height:auto;padding-top:calc(var(--hx-spacing)*1)}.hextra-sidebar-container li.open>.hextra-sidebar-item>.hextra-sidebar-collapsible-button>svg>path{rotate:90deg}.hextra-banner-hidden .hextra-banner{display:none}.hextra-banner :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:underline;text-decoration-thickness:from-font}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}nav .hextra-search-wrapper{display:none}@media(min-width:48rem){nav .hextra-search-wrapper{display:inline-block}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-nav-container-blur{background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur{background-color:color-mix(in oklab,var(--hx-color-white)85%,transparent)}}.hextra-nav-container-blur{--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-nav-container-blur:where(.dark,.dark *){background-color:var(--hx-color-dark)!important}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)80%,transparent)!important}}}.hextra-hamburger-menu svg g{transform-origin:50%;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg path{opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open path{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:0s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open g{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open>path{opacity:0}.hextra-hamburger-menu svg.open>g:first-of-type{rotate:45deg}.hextra-hamburger-menu svg.open>g:first-of-type path{--tw-translate-y:calc(var(--hx-spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-hamburger-menu svg.open>g:nth-of-type(2){rotate:-45deg}.hextra-hamburger-menu svg.open>g:nth-of-type(2) path{--tw-translate-y:calc(var(--hx-spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-scrollbar,.hextra-scrollbar *{scrollbar-width:thin;scrollbar-color:oklch(55.55% 0 0/.4)transparent;scrollbar-gutter:stable}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar{height:calc(var(--hx-spacing)*3);width:calc(var(--hx-spacing)*3)}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-track{background-color:#0000}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-thumb{border-radius:10px}:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{background-color:var(--tw-shadow-color);--tw-shadow-color:var(--hx-color-neutral-500);background-clip:content-box;border:3px solid #0000}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}@media(hover:hover){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:var(--hx-color-neutral-500)}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)40%,transparent)var(--tw-shadow-alpha),transparent)}}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-code-copy-btn{opacity:.85;--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-code-copy-btn:where(.dark,.dark *){opacity:.8}}@media(min-width:1024px){.hextra-feature-grid{grid-template-columns:repeat(var(--hextra-feature-grid-cols),minmax(0,1fr))}}.hextra-jupyter-code-cell{scrollbar-gutter:auto;margin-top:calc(var(--hx-spacing)*6)}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:hidden}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs{max-height:50vh;overflow:auto}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs pre{max-width:100%;font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:auto}.hextra-badge{align-items:center;display:inline-flex}.hextra-toc a.hextra-toc-active{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.2s;transition-duration:.2s;color:var(--hx-color-gray-900)!important}.hextra-toc a.hextra-toc-active:where(.dark,.dark *){color:var(--hx-color-gray-50)!important}.hextra-archive-timeline{border-left-style:var(--tw-border-style);border-left-width:2px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline{border-color:color-mix(in oklab,var(--hx-color-black)15%,transparent)}}.hextra-archive-timeline:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)15%,transparent)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}:root{--hextra-navbar-height:80px}.hextra-nav-container nav{min-height:80px!important;height:80px!important}.hextra-feature-card>img{max-width:160px;max-height:160px;width:auto;height:auto;object-fit:contain} \ No newline at end of file diff --git a/public/css/compiled/main.min.90b97d9fd24b4b64d77532889559f61e388dc6e7e1c40110b315ef477b5ff042.css b/public/css/compiled/main.min.90b97d9fd24b4b64d77532889559f61e388dc6e7e1c40110b315ef477b5ff042.css new file mode 100644 index 0000000..09d8dd3 --- /dev/null +++ b/public/css/compiled/main.min.90b97d9fd24b4b64d77532889559f61e388dc6e7e1c40110b315ef477b5ff042.css @@ -0,0 +1 @@ +:root{--hextra-max-page-width:80rem;--hextra-max-content-width:72rem;--hextra-max-navbar-width:80rem;--hextra-max-footer-width:80rem}.hextra-max-page-width{max-width:var(--hextra-max-page-width)}.hextra-max-content-width{max-width:var(--hextra-max-content-width)}.hextra-max-navbar-width{max-width:var(--hextra-max-navbar-width)}.hextra-max-footer-width{max-width:var(--hextra-max-footer-width)}/*!tailwindcss v4.1.18 | MIT License | https://tailwindcss.com*/@layer properties{@supports(((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--hx-font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--hx-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--hx-color-red-100:oklch(93.6% .032 17.717);--hx-color-red-200:oklch(88.5% .062 18.334);--hx-color-red-900:oklch(39.6% .141 25.723);--hx-color-orange-50:oklch(98% .016 73.684);--hx-color-orange-100:oklch(95.4% .038 75.164);--hx-color-orange-300:oklch(83.7% .128 66.29);--hx-color-orange-400:oklch(75% .183 55.934);--hx-color-orange-800:oklch(47% .157 37.304);--hx-color-amber-100:oklch(96.2% .059 95.617);--hx-color-amber-200:oklch(92.4% .12 95.746);--hx-color-amber-900:oklch(41.4% .112 45.904);--hx-color-yellow-50:oklch(98.7% .026 102.212);--hx-color-yellow-100:oklch(97.3% .071 103.193);--hx-color-yellow-200:oklch(94.5% .129 101.54);--hx-color-yellow-700:oklch(55.4% .135 66.442);--hx-color-yellow-900:oklch(42.1% .095 57.708);--hx-color-green-100:oklch(96.2% .044 156.743);--hx-color-green-200:oklch(92.5% .084 155.995);--hx-color-green-900:oklch(39.3% .095 152.535);--hx-color-blue-100:oklch(93.2% .032 255.585);--hx-color-blue-200:oklch(88.2% .059 254.128);--hx-color-blue-900:oklch(37.9% .146 265.522);--hx-color-indigo-100:oklch(93% .034 272.788);--hx-color-indigo-200:oklch(87% .065 274.039);--hx-color-indigo-900:oklch(35.9% .144 278.697);--hx-color-purple-100:oklch(94.6% .033 307.174);--hx-color-purple-200:oklch(90.2% .063 306.703);--hx-color-purple-900:oklch(38.1% .176 304.987);--hx-color-slate-50:oklch(98.4% .003 247.858);--hx-color-slate-100:oklch(96.8% .007 247.896);--hx-color-slate-900:oklch(20.8% .042 265.755);--hx-color-gray-50:oklch(98.5% .002 247.839);--hx-color-gray-100:oklch(96.7% .003 264.542);--hx-color-gray-200:oklch(92.8% .006 264.531);--hx-color-gray-300:oklch(87.2% .01 258.338);--hx-color-gray-400:oklch(70.7% .022 261.325);--hx-color-gray-500:oklch(55.1% .027 264.364);--hx-color-gray-600:oklch(44.6% .03 256.802);--hx-color-gray-700:oklch(37.3% .034 259.733);--hx-color-gray-800:oklch(27.8% .033 256.848);--hx-color-gray-900:oklch(21% .034 264.665);--hx-color-neutral-50:oklch(98.5% 0 0);--hx-color-neutral-200:oklch(92.2% 0 0);--hx-color-neutral-300:oklch(87% 0 0);--hx-color-neutral-400:oklch(70.8% 0 0);--hx-color-neutral-500:oklch(55.6% 0 0);--hx-color-neutral-600:oklch(43.9% 0 0);--hx-color-neutral-700:oklch(37.1% 0 0);--hx-color-neutral-800:oklch(26.9% 0 0);--hx-color-neutral-900:oklch(20.5% 0 0);--hx-color-black:#000;--hx-color-white:#fff;--hx-spacing:.25rem;--hx-breakpoint-xl:80rem;--hx-text-xs:.75rem;--hx-text-xs--line-height:calc(1/.75);--hx-text-sm:.875rem;--hx-text-sm--line-height:calc(1.25/.875);--hx-text-base:1rem;--hx-text-base--line-height:calc(1.5/1);--hx-text-lg:1.125rem;--hx-text-lg--line-height:calc(1.75/1.125);--hx-text-xl:1.25rem;--hx-text-xl--line-height:calc(1.75/1.25);--hx-text-2xl:1.5rem;--hx-text-2xl--line-height:calc(2/1.5);--hx-text-3xl:1.875rem;--hx-text-3xl--line-height:calc(2.25/1.875);--hx-text-4xl:2.25rem;--hx-text-4xl--line-height:calc(2.5/2.25);--hx-text-5xl:3rem;--hx-text-5xl--line-height:1;--hx-font-weight-normal:400;--hx-font-weight-medium:500;--hx-font-weight-semibold:600;--hx-font-weight-bold:700;--hx-font-weight-extrabold:800;--hx-tracking-tighter:-.05em;--hx-tracking-tight:-.025em;--hx-leading-tight:1.25;--hx-radius-xs:.125rem;--hx-radius-sm:.25rem;--hx-radius-md:.375rem;--hx-radius-lg:.5rem;--hx-radius-xl:.75rem;--hx-radius-3xl:1.5rem;--hx-ease-in:cubic-bezier(.4,0,1,1);--hx-ease-out:cubic-bezier(0,0,.2,1);--hx-ease-in-out:cubic-bezier(.4,0,.2,1);--hx-blur-md:12px;--hx-default-transition-duration:.15s;--hx-default-transition-timing-function:cubic-bezier(.4,0,.2,1);--hx-default-font-family:var(--hx-font-sans);--hx-default-mono-font-family:var(--hx-font-mono);--hx-color-primary-100:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*44));--hx-color-primary-200:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*36));--hx-color-primary-300:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*27));--hx-color-primary-400:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*16));--hx-color-primary-500:hsl(var(--primary-hue)var(--primary-saturation)var(--primary-lightness));--hx-color-primary-600:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*45));--hx-color-primary-700:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*39));--hx-color-primary-800:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*32));--hx-color-primary-900:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*24));--hx-color-dark:#111}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--hx-default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--hx-default-font-feature-settings,normal);font-variation-settings:var(--hx-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--hx-default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--hx-default-mono-font-feature-settings,normal);font-variation-settings:var(--hx-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports(color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}abbr:where([title]){cursor:help}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible:where(.dark,.dark *){--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}}@layer components;@layer utilities{.hx\:pointer-events-none{pointer-events:none}.hx\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hx\:absolute{position:absolute}.hx\:relative{position:relative}.hx\:sticky{position:sticky}.hx\:inset-x-0{inset-inline:calc(var(--hx-spacing)*0)}.hx\:inset-y-0{inset-block:calc(var(--hx-spacing)*0)}.hx\:top-0{top:calc(var(--hx-spacing)*0)}.hx\:top-1\/2{top:50%}.hx\:top-8{top:calc(var(--hx-spacing)*8)}.hx\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:top-\[40\%\]{top:40%}.hx\:top-full{top:100%}.hx\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:bottom-0{bottom:calc(var(--hx-spacing)*0)}.hx\:left-0{left:calc(var(--hx-spacing)*0)}.hx\:left-\[24px\]{left:24px}.hx\:left-\[36px\]{left:36px}.hx\:z-20{z-index:20}.hx\:z-\[-1\]{z-index:-1}.hx\:order-last{order:9999}.hx\:m-\[11px\]{margin:11px}.hx\:mx-1{margin-inline:calc(var(--hx-spacing)*1)}.hx\:mx-4{margin-inline:calc(var(--hx-spacing)*4)}.hx\:mx-auto{margin-inline:auto}.hx\:my-1{margin-block:calc(var(--hx-spacing)*1)}.hx\:my-1\.5{margin-block:calc(var(--hx-spacing)*1.5)}.hx\:my-2{margin-block:calc(var(--hx-spacing)*2)}.hx\:-mt-20{margin-top:calc(var(--hx-spacing)*-20)}.hx\:mt-1{margin-top:calc(var(--hx-spacing)*1)}.hx\:mt-1\.5{margin-top:calc(var(--hx-spacing)*1.5)}.hx\:mt-2{margin-top:calc(var(--hx-spacing)*2)}.hx\:mt-4{margin-top:calc(var(--hx-spacing)*4)}.hx\:mt-5{margin-top:calc(var(--hx-spacing)*5)}.hx\:mt-6{margin-top:calc(var(--hx-spacing)*6)}.hx\:mt-8{margin-top:calc(var(--hx-spacing)*8)}.hx\:mt-12{margin-top:calc(var(--hx-spacing)*12)}.hx\:mt-16{margin-top:calc(var(--hx-spacing)*16)}.hx\:mt-auto{margin-top:auto}.hx\:-mr-2{margin-right:calc(var(--hx-spacing)*-2)}.hx\:mr-1{margin-right:calc(var(--hx-spacing)*1)}.hx\:mr-2{margin-right:calc(var(--hx-spacing)*2)}.hx\:-mb-0\.5{margin-bottom:calc(var(--hx-spacing)*-.5)}.hx\:mb-0{margin-bottom:calc(var(--hx-spacing)*0)}.hx\:mb-2{margin-bottom:calc(var(--hx-spacing)*2)}.hx\:mb-4{margin-bottom:calc(var(--hx-spacing)*4)}.hx\:mb-6{margin-bottom:calc(var(--hx-spacing)*6)}.hx\:mb-8{margin-bottom:calc(var(--hx-spacing)*8)}.hx\:mb-10{margin-bottom:calc(var(--hx-spacing)*10)}.hx\:mb-12{margin-bottom:calc(var(--hx-spacing)*12)}.hx\:mb-16{margin-bottom:calc(var(--hx-spacing)*16)}.hx\:-ml-2{margin-left:calc(var(--hx-spacing)*-2)}.hx\:ml-2{margin-left:calc(var(--hx-spacing)*2)}.hx\:ml-4{margin-left:calc(var(--hx-spacing)*4)}.hx\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hx\:block{display:block}.hx\:flex{display:flex}.hx\:grid{display:grid}.hx\:hidden{display:none}.hx\:inline{display:inline}.hx\:inline-block{display:inline-block}.hx\:inline-flex{display:inline-flex}.hx\:aspect-auto{aspect-ratio:auto}.hx\:size-4{width:calc(var(--hx-spacing)*4);height:calc(var(--hx-spacing)*4)}.hx\:h-0{height:calc(var(--hx-spacing)*0)}.hx\:h-2{height:calc(var(--hx-spacing)*2)}.hx\:h-3\.5{height:calc(var(--hx-spacing)*3.5)}.hx\:h-4{height:calc(var(--hx-spacing)*4)}.hx\:h-5{height:calc(var(--hx-spacing)*5)}.hx\:h-7{height:calc(var(--hx-spacing)*7)}.hx\:h-10{height:calc(var(--hx-spacing)*10)}.hx\:h-16{height:calc(var(--hx-spacing)*16)}.hx\:h-\[18px\]{height:18px}.hx\:h-full{height:100%}.hx\:h-px{height:1px}.hx\:max-h-\(--menu-height\){max-height:var(--menu-height)}.hx\:max-h-64{max-height:calc(var(--hx-spacing)*64)}.hx\:max-h-\[calc\(100vh-var\(--navbar-height\)-env\(safe-area-inset-bottom\)\)\]{max-height:calc(100vh - var(--navbar-height) - env(safe-area-inset-bottom))}.hx\:max-h-\[min\(calc\(50vh-11rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(50vh - 11rem - env(safe-area-inset-bottom)),400px)}.hx\:min-h-\[100px\]{min-height:100px}.hx\:min-h-\[calc\(100vh-var\(--navbar-height\)\)\]{min-height:calc(100vh - var(--navbar-height))}.hx\:w-2{width:calc(var(--hx-spacing)*2)}.hx\:w-3\.5{width:calc(var(--hx-spacing)*3.5)}.hx\:w-4{width:calc(var(--hx-spacing)*4)}.hx\:w-10{width:calc(var(--hx-spacing)*10)}.hx\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:w-\[110\%\]{width:110%}.hx\:w-\[180\%\]{width:180%}.hx\:w-full{width:100%}.hx\:w-max{width:max-content}.hx\:w-screen{width:100vw}.hx\:max-w-\[50\%\]{max-width:50%}.hx\:max-w-\[90rem\]{max-width:90rem}.hx\:max-w-\[min\(calc\(100vw-2rem\)\,calc\(100\%\+20rem\)\)\]{max-width:min(100vw - 2rem,100% + 20rem)}.hx\:max-w-full{max-width:100%}.hx\:max-w-none{max-width:none}.hx\:max-w-screen-xl{max-width:var(--hx-breakpoint-xl)}.hx\:min-w-0{min-width:calc(var(--hx-spacing)*0)}.hx\:min-w-\[18px\]{min-width:18px}.hx\:min-w-\[24px\]{min-width:24px}.hx\:min-w-full{min-width:100%}.hx\:shrink-0{flex-shrink:0}.hx\:grow{flex-grow:1}.hx\:origin-center{transform-origin:50%}.hx\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hx\:cursor-default{cursor:default}.hx\:cursor-pointer{cursor:pointer}.hx\:scroll-my-6{scroll-margin-block:calc(var(--hx-spacing)*6)}.hx\:scroll-py-6{scroll-padding-block:calc(var(--hx-spacing)*6)}.hx\:list-none{list-style-type:none}.hx\:appearance-none{appearance:none}.hx\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.hx\:flex-col{flex-direction:column}.hx\:flex-wrap{flex-wrap:wrap}.hx\:items-baseline{align-items:baseline}.hx\:items-center{align-items:center}.hx\:items-start{align-items:flex-start}.hx\:justify-between{justify-content:space-between}.hx\:justify-center{justify-content:center}.hx\:justify-end{justify-content:flex-end}.hx\:justify-start{justify-content:flex-start}.hx\:justify-items-start{justify-items:start}.hx\:gap-1{gap:calc(var(--hx-spacing)*1)}.hx\:gap-1\.5{gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-2{gap:calc(var(--hx-spacing)*2)}.hx\:gap-4{gap:calc(var(--hx-spacing)*4)}.hx\:gap-x-1\.5{column-gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-x-2{column-gap:calc(var(--hx-spacing)*2)}.hx\:gap-y-1{row-gap:calc(var(--hx-spacing)*1)}.hx\:gap-y-2{row-gap:calc(var(--hx-spacing)*2)}.hx\:overflow-auto{overflow:auto}.hx\:overflow-hidden{overflow:hidden}.hx\:overflow-x-auto{overflow-x:auto}.hx\:overflow-x-hidden{overflow-x:hidden}.hx\:overflow-y-auto{overflow-y:auto}.hx\:overflow-y-hidden{overflow-y:hidden}.hx\:overscroll-contain{overscroll-behavior:contain}.hx\:overscroll-x-contain{overscroll-behavior-x:contain}.hx\:rounded-3xl{border-radius:var(--hx-radius-3xl)}.hx\:rounded-full{border-radius:340282e33px}.hx\:rounded-lg{border-radius:var(--hx-radius-lg)}.hx\:rounded-md{border-radius:var(--hx-radius-md)}.hx\:rounded-sm{border-radius:var(--hx-radius-sm)}.hx\:rounded-xl{border-radius:var(--hx-radius-xl)}.hx\:rounded-xs{border-radius:var(--hx-radius-xs)}.hx\:rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.hx\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:border-0{border-style:var(--tw-border-style);border-width:0}.hx\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.hx\:border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.hx\:border-amber-200{border-color:var(--hx-color-amber-200)}.hx\:border-black\/5{border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:border-black\/5{border-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:border-blue-200{border-color:var(--hx-color-blue-200)}.hx\:border-gray-200{border-color:var(--hx-color-gray-200)}.hx\:border-gray-500{border-color:var(--hx-color-gray-500)}.hx\:border-green-200{border-color:var(--hx-color-green-200)}.hx\:border-indigo-200{border-color:var(--hx-color-indigo-200)}.hx\:border-orange-100{border-color:var(--hx-color-orange-100)}.hx\:border-purple-200{border-color:var(--hx-color-purple-200)}.hx\:border-red-200{border-color:var(--hx-color-red-200)}.hx\:border-transparent{border-color:#0000}.hx\:border-yellow-100{border-color:var(--hx-color-yellow-100)}.hx\:bg-amber-100{background-color:var(--hx-color-amber-100)}.hx\:bg-black\/\[\.05\]{background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-black\/\[\.05\]{background-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:bg-blue-100{background-color:var(--hx-color-blue-100)}.hx\:bg-gray-100{background-color:var(--hx-color-gray-100)}.hx\:bg-gray-200{background-color:var(--hx-color-gray-200)}.hx\:bg-green-100{background-color:var(--hx-color-green-100)}.hx\:bg-indigo-100{background-color:var(--hx-color-indigo-100)}.hx\:bg-neutral-50{background-color:var(--hx-color-neutral-50)}.hx\:bg-neutral-900{background-color:var(--hx-color-neutral-900)}.hx\:bg-orange-50{background-color:var(--hx-color-orange-50)}.hx\:bg-primary-100{background-color:var(--hx-color-primary-100)}.hx\:bg-primary-400{background-color:var(--hx-color-primary-400)}.hx\:bg-primary-600{background-color:var(--hx-color-primary-600)}.hx\:bg-primary-700\/5{background-color:var(--hx-color-primary-700)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-primary-700\/5{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hx\:bg-purple-100{background-color:var(--hx-color-purple-100)}.hx\:bg-red-100{background-color:var(--hx-color-red-100)}.hx\:bg-transparent{background-color:#0000}.hx\:bg-white{background-color:var(--hx-color-white)}.hx\:bg-yellow-50{background-color:var(--hx-color-yellow-50)}.hx\:bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.hx\:from-gray-900{--tw-gradient-from:var(--hx-color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:to-gray-600{--tw-gradient-to:var(--hx-color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:bg-clip-text{-webkit-background-clip:text;background-clip:text}.hx\:p-0{padding:calc(var(--hx-spacing)*0)}.hx\:p-0\.5{padding:calc(var(--hx-spacing)*.5)}.hx\:p-1{padding:calc(var(--hx-spacing)*1)}.hx\:p-1\.5{padding:calc(var(--hx-spacing)*1.5)}.hx\:p-2{padding:calc(var(--hx-spacing)*2)}.hx\:p-4{padding:calc(var(--hx-spacing)*4)}.hx\:p-6{padding:calc(var(--hx-spacing)*6)}.hx\:px-1\.5{padding-inline:calc(var(--hx-spacing)*1.5)}.hx\:px-2{padding-inline:calc(var(--hx-spacing)*2)}.hx\:px-2\.5{padding-inline:calc(var(--hx-spacing)*2.5)}.hx\:px-3{padding-inline:calc(var(--hx-spacing)*3)}.hx\:px-4{padding-inline:calc(var(--hx-spacing)*4)}.hx\:px-6{padding-inline:calc(var(--hx-spacing)*6)}.hx\:px-8{padding-inline:calc(var(--hx-spacing)*8)}.hx\:py-1{padding-block:calc(var(--hx-spacing)*1)}.hx\:py-1\.5{padding-block:calc(var(--hx-spacing)*1.5)}.hx\:py-2{padding-block:calc(var(--hx-spacing)*2)}.hx\:py-2\.5{padding-block:calc(var(--hx-spacing)*2.5)}.hx\:py-3{padding-block:calc(var(--hx-spacing)*3)}.hx\:py-4{padding-block:calc(var(--hx-spacing)*4)}.hx\:py-12{padding-block:calc(var(--hx-spacing)*12)}.hx\:pt-4{padding-top:calc(var(--hx-spacing)*4)}.hx\:pt-6{padding-top:calc(var(--hx-spacing)*6)}.hx\:pt-8{padding-top:calc(var(--hx-spacing)*8)}.hx\:pr-2{padding-right:calc(var(--hx-spacing)*2)}.hx\:pr-4{padding-right:calc(var(--hx-spacing)*4)}.hx\:pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\]{padding-right:calc(env(safe-area-inset-right) - 1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-left),1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-right\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-right),1.5rem)}.hx\:pb-8{padding-bottom:calc(var(--hx-spacing)*8)}.hx\:pb-\[env\(safe-area-inset-bottom\)\]{padding-bottom:env(safe-area-inset-bottom)}.hx\:pb-px{padding-bottom:1px}.hx\:pl-6{padding-left:calc(var(--hx-spacing)*6)}.hx\:pl-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-left:max(env(safe-area-inset-left),1.5rem)}.hx\:text-center{text-align:center}.hx\:text-left{text-align:left}.hx\:align-\[-2\.5px\]{vertical-align:-2.5px}.hx\:align-baseline{vertical-align:baseline}.hx\:align-middle{vertical-align:middle}.hx\:align-text-bottom{vertical-align:text-bottom}.hx\:font-mono{font-family:var(--hx-font-mono)}.hx\:text-2xl{font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height))}.hx\:text-4xl{font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height))}.hx\:text-base{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height))}.hx\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}.hx\:text-xs{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height))}.hx\:text-\[\.65rem\]{font-size:.65rem}.hx\:text-\[10px\]{font-size:10px}.hx\:leading-5{--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5)}.hx\:leading-6{--tw-leading:calc(var(--hx-spacing)*6);line-height:calc(var(--hx-spacing)*6)}.hx\:leading-7{--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hx\:leading-none{--tw-leading:1;line-height:1}.hx\:leading-tight{--tw-leading:var(--hx-leading-tight);line-height:var(--hx-leading-tight)}.hx\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:font-extrabold{--tw-font-weight:var(--hx-font-weight-extrabold);font-weight:var(--hx-font-weight-extrabold)}.hx\:font-medium{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:font-normal{--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal)}.hx\:font-semibold{--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hx\:tracking-tight{--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight)}.hx\:tracking-tighter{--tw-tracking:var(--hx-tracking-tighter);letter-spacing:var(--hx-tracking-tighter)}.hx\:break-words,.hx\:wrap-break-word{overflow-wrap:break-word}.hx\:text-ellipsis{text-overflow:ellipsis}.hx\:whitespace-nowrap{white-space:nowrap}.hx\:text-\[color\:hsl\(var\(--primary-hue\)\,100\%\,50\%\)\]{color:hsl(var(--primary-hue),100%,50%)}.hx\:text-amber-900{color:var(--hx-color-amber-900)}.hx\:text-blue-900{color:var(--hx-color-blue-900)}.hx\:text-current{color:currentColor}.hx\:text-gray-100{color:var(--hx-color-gray-100)}.hx\:text-gray-500{color:var(--hx-color-gray-500)}.hx\:text-gray-600{color:var(--hx-color-gray-600)}.hx\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:text-green-900{color:var(--hx-color-green-900)}.hx\:text-indigo-900{color:var(--hx-color-indigo-900)}.hx\:text-orange-800{color:var(--hx-color-orange-800)}.hx\:text-primary-800{color:var(--hx-color-primary-800)}.hx\:text-purple-900{color:var(--hx-color-purple-900)}.hx\:text-red-900{color:var(--hx-color-red-900)}.hx\:text-slate-50{color:var(--hx-color-slate-50)}.hx\:text-slate-900{color:var(--hx-color-slate-900)}.hx\:text-transparent{color:#0000}.hx\:text-white{color:var(--hx-color-white)}.hx\:text-yellow-900{color:var(--hx-color-yellow-900)}.hx\:capitalize{text-transform:capitalize}.hx\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.hx\:no-underline{text-decoration-line:none}.hx\:underline{text-decoration-line:underline}.hx\:decoration-from-font{text-decoration-thickness:from-font}.hx\:underline-offset-2{text-underline-offset:2px}.hx\:opacity-0{opacity:0}.hx\:opacity-50{opacity:.5}.hx\:opacity-80{opacity:.8}.hx\:shadow-\[0_-12px_16px_\#fff\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_-12px_16px_white\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,white);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_2px_4px_rgba\(0\,0\,0\,\.02\)\,0_1px_0_rgba\(0\,0\,0\,\.06\)\]{--tw-shadow:0 2px 4px var(--tw-shadow-color,#00000005),0 1px 0 var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hextra-focus-visible:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}.hx\:hextra-focus-visible:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}.hx\:hextra-focus-visible-inset:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}.hx\:hextra-focus-visible-inset:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800)}.hx\:shadow-gray-100{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:shadow-gray-100{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}.hx\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:duration-75{--tw-duration:75ms;transition-duration:75ms}.hx\:duration-150{--tw-duration:.15s;transition-duration:.15s}.hx\:duration-200{--tw-duration:.2s;transition-duration:.2s}.hx\:ease-in{--tw-ease:var(--hx-ease-in);transition-timing-function:var(--hx-ease-in)}.hx\:ease-in-out{--tw-ease:var(--hx-ease-in-out);transition-timing-function:var(--hx-ease-in-out)}.hx\:outline-none{--tw-outline-style:none;outline-style:none}.hx\:select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hx\:group-hover\:text-gray-900:is(:where(.hx\:group):hover *){color:var(--hx-color-gray-900)}.hx\:group-hover\:underline:is(:where(.hx\:group):hover *){text-decoration-line:underline}.hx\:group-hover\/code\:opacity-100:is(:where(.hx\:group\/code):hover *){opacity:1}}.hx\:group-data-\[active\=true\]\:text-primary-800:is(:where(.hx\:group)[data-active=true] *){color:var(--hx-color-primary-800)}.hx\:group-data-\[theme\=dark\]\:hidden:is(:where(.hx\:group)[data-theme=dark] *),.hx\:group-data-\[theme\=light\]\:hidden:is(:where(.hx\:group)[data-theme=light] *),.hx\:group-data-\[theme\=system\]\:hidden:is(:where(.hx\:group)[data-theme=system] *){display:none}.hx\:group-\[\.copied\]\/copybtn\:block:is(:where(.hx\:group\/copybtn).copied *){display:block}.hx\:group-\[\.copied\]\/copybtn\:hidden:is(:where(.hx\:group\/copybtn).copied *){display:none}.hx\:placeholder\:text-gray-500::placeholder{color:var(--hx-color-gray-500)}.hx\:before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.hx\:before\:absolute:before{content:var(--tw-content);position:absolute}.hx\:before\:inset-0:before{content:var(--tw-content);inset:calc(var(--hx-spacing)*0)}.hx\:before\:inset-y-1:before{content:var(--tw-content);inset-block:calc(var(--hx-spacing)*1)}.hx\:before\:mr-1:before{content:var(--tw-content);margin-right:calc(var(--hx-spacing)*1)}.hx\:before\:inline-block:before{content:var(--tw-content);display:inline-block}.hx\:before\:w-px:before{content:var(--tw-content);width:1px}.hx\:before\:bg-gray-200:before{content:var(--tw-content);background-color:var(--hx-color-gray-200)}.hx\:before\:opacity-25:before{content:var(--tw-content);opacity:.25}.hx\:before\:transition-transform:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:before\:content-\[\"\"\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\'\#\'\]:before{--tw-content:"#";content:var(--tw-content)}.hx\:before\:content-\[\'\'\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.hx\:group-open\:before\:rotate-90:is(:where(.hx\:group):is([open],:popover-open,:open) *):before{content:var(--tw-content);rotate:90deg}.hx\:first\:mt-0:first-child{margin-top:calc(var(--hx-spacing)*0)}.hx\:last-of-type\:mb-0:last-of-type{margin-bottom:calc(var(--hx-spacing)*0)}@media(hover:hover){.hx\:hover\:border-gray-200:hover{border-color:var(--hx-color-gray-200)}.hx\:hover\:border-gray-300:hover{border-color:var(--hx-color-gray-300)}.hx\:hover\:border-gray-400:hover{border-color:var(--hx-color-gray-400)}.hx\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}.hx\:hover\:bg-gray-100:hover{background-color:var(--hx-color-gray-100)}.hx\:hover\:bg-gray-800\/5:hover{background-color:var(--hx-color-gray-800)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:bg-gray-800\/5:hover{background-color:color-mix(in oklab,var(--hx-color-gray-800)5%,transparent)}}.hx\:hover\:bg-primary-700:hover{background-color:var(--hx-color-primary-700)}.hx\:hover\:bg-slate-50:hover{background-color:var(--hx-color-slate-50)}.hx\:hover\:text-black:hover{color:var(--hx-color-black)}.hx\:hover\:text-gray-700:hover{color:var(--hx-color-gray-700)}.hx\:hover\:text-gray-800:hover{color:var(--hx-color-gray-800)}.hx\:hover\:text-gray-900:hover{color:var(--hx-color-gray-900)}.hx\:hover\:text-primary-600:hover{color:var(--hx-color-primary-600)}.hx\:hover\:opacity-60:hover{opacity:.6}.hx\:hover\:opacity-75:hover{opacity:.75}.hx\:hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}}.hx\:focus-visible\:not-sr-only:focus-visible{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.hx\:focus-visible\:fixed:focus-visible{position:fixed}.hx\:focus-visible\:top-2:focus-visible{top:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:left-2:focus-visible{left:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:z-50:focus-visible{z-index:50}.hx\:focus-visible\:rounded-md:focus-visible{border-radius:var(--hx-radius-md)}.hx\:focus-visible\:bg-primary-500:focus-visible{background-color:var(--hx-color-primary-500)}.hx\:focus-visible\:bg-white:focus-visible{background-color:var(--hx-color-white)}.hx\:focus-visible\:px-4:focus-visible{padding-inline:calc(var(--hx-spacing)*4)}.hx\:focus-visible\:py-2:focus-visible{padding-block:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:text-sm:focus-visible{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:focus-visible\:font-medium:focus-visible{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:focus-visible\:text-white:focus-visible{color:var(--hx-color-white)}.hx\:active\:bg-gray-400\/20:active{background-color:var(--hx-color-gray-400)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:bg-gray-400\/20:active{background-color:color-mix(in oklab,var(--hx-color-gray-400)20%,transparent)}}.hx\:active\:opacity-50:active{opacity:.5}.hx\:active\:shadow-sm:active{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:active\:shadow-gray-200:active{--tw-shadow-color:var(--hx-color-gray-200)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:shadow-gray-200:active{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-200)var(--tw-shadow-alpha),transparent)}}.hx\:data-\[state\=closed\]\:hidden[data-state=closed],.hx\:data-\[state\=open\]\:hidden[data-state=open]{display:none}.hx\:data-\[state\=selected\]\:block[data-state=selected]{display:block}.hx\:data-\[state\=selected\]\:border-primary-500[data-state=selected]{border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:text-primary-600[data-state=selected]{color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:contrast-more\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:contrast-more\:border-current{border-color:initial}.hx\:contrast-more\:border-gray-800{border-color:var(--hx-color-gray-800)}.hx\:contrast-more\:border-gray-900{border-color:var(--hx-color-gray-900)}.hx\:contrast-more\:border-neutral-400{border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:border-primary-500{border-color:var(--hx-color-primary-500)}.hx\:contrast-more\:border-transparent{border-color:#0000}.hx\:contrast-more\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:contrast-more\:text-current{color:currentColor}.hx\:contrast-more\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:contrast-more\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:contrast-more\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:contrast-more\:underline{text-decoration-line:underline}.hx\:contrast-more\:shadow-\[0_0_0_1px_\#000\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}}}@media not all and (min-width:80rem){.hx\:max-xl\:hidden{display:none}}@media not all and (min-width:64rem){.hx\:max-lg\:min-h-\[340px\]{min-height:340px}}@media not all and (min-width:48rem){.hx\:max-md\:sticky{position:sticky}.hx\:max-md\:hidden{display:none}.hx\:max-md\:min-h-\[340px\]{min-height:340px}.hx\:max-md\:\[transform\:translate3d\(0\,-100\%\,0\)\]{transform:translateY(-100%)}.hx\:max-md\:\[transform\:translate3d\(0\,0\,0\)\]{transform:translate(0)}}@media not all and (min-width:40rem){.hx\:max-sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}}@media(min-width:40rem){.hx\:sm\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:sm\:left-auto{left:auto}.hx\:sm\:block{display:block}.hx\:sm\:flex{display:flex}.hx\:sm\:w-\[110\%\]{width:110%}.hx\:sm\:flex-row{flex-direction:row}.hx\:sm\:items-center{align-items:center}.hx\:sm\:items-start{align-items:flex-start}.hx\:sm\:justify-between{justify-content:space-between}.hx\:sm\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}@media not all and (min-width:64rem){.hx\:sm\:max-lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@media(min-width:48rem){.hx\:md\:sticky{position:sticky}.hx\:md\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:md\:mr-0{margin-right:calc(var(--hx-spacing)*0)}.hx\:md\:hidden{display:none}.hx\:md\:inline-block{display:inline-block}.hx\:md\:inline-flex{display:inline-flex}.hx\:md\:aspect-\[1\.1\/1\]{aspect-ratio:1.1}.hx\:md\:h-\[calc\(100vh-var\(--navbar-height\)-var\(--menu-height\)\)\]{height:calc(100vh - var(--navbar-height) - var(--menu-height))}.hx\:md\:max-h-\[min\(calc\(100vh-5rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(100vh - 5rem - env(safe-area-inset-bottom)),400px)}.hx\:md\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:md\:shrink-0{flex-shrink:0}.hx\:md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.hx\:md\:justify-start{justify-content:flex-start}.hx\:md\:self-start{align-self:flex-start}.hx\:md\:overflow-auto{overflow:auto}.hx\:md\:px-12{padding-inline:calc(var(--hx-spacing)*12)}.hx\:md\:pt-12{padding-top:calc(var(--hx-spacing)*12)}.hx\:md\:text-3xl{font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height))}.hx\:md\:text-5xl{font-size:var(--hx-text-5xl);line-height:var(--tw-leading,var(--hx-text-5xl--line-height))}.hx\:md\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:md\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}}@media(min-width:64rem){.hx\:lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:80rem){.hx\:xl\:block{display:block}.hx\:xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.hx\:ltr\:right-1\.5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*1.5)}.hx\:ltr\:right-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*2)}.hx\:ltr\:right-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*3)}.hx\:ltr\:-mr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:calc(var(--hx-spacing)*-4)}.hx\:ltr\:mr-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:auto}.hx\:ltr\:ml-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.hx\:ltr\:ml-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:ml-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:auto}.hx\:ltr\:rotate-180:where(:dir(ltr),[dir=ltr],[dir=ltr] *){rotate:180deg}.hx\:ltr\:rounded-l-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:ltr\:rounded-r-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:ltr\:border-l:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);border-left-width:1px}.hx\:ltr\:pr-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*0)}.hx\:ltr\:pr-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*1)}.hx\:ltr\:pr-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*2)}.hx\:ltr\:pr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:ltr\:pr-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:ltr\:pr-9:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*9)}.hx\:ltr\:pl-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:pl-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:ltr\:pl-5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*5)}.hx\:ltr\:pl-6:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*6)}.hx\:ltr\:pl-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:ltr\:pl-12:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*12)}.hx\:ltr\:pl-16:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*16)}.hx\:ltr\:text-right:where(:dir(ltr),[dir=ltr],[dir=ltr] *){text-align:right}.hx\:ltr\:before\:left-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);left:calc(var(--hx-spacing)*0)}@media(min-width:48rem){.hx\:ltr\:md\:left-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:auto}}.hx\:rtl\:left-1\.5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*1.5)}.hx\:rtl\:left-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*2)}.hx\:rtl\:left-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}.hx\:rtl\:mr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto}.hx\:rtl\:-ml-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:calc(var(--hx-spacing)*-4)}.hx\:rtl\:ml-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:auto}.hx\:rtl\:-rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:-180deg}.hx\:rtl\:rotate-270:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:270deg}.hx\:rtl\:rounded-l-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:rtl\:rounded-r-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:rtl\:border-r:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:1px}.hx\:rtl\:pr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:pr-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:rtl\:pr-5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*5)}.hx\:rtl\:pr-6:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*6)}.hx\:rtl\:pr-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:rtl\:pr-12:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*12)}.hx\:rtl\:pr-16:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*16)}.hx\:rtl\:pl-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*0)}.hx\:rtl\:pl-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*1)}.hx\:rtl\:pl-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*2)}.hx\:rtl\:pl-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:rtl\:pl-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:rtl\:pl-9:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*9)}.hx\:rtl\:text-left:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:left}.hx\:rtl\:before\:right-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);right:calc(var(--hx-spacing)*0)}.hx\:rtl\:before\:rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);rotate:180deg}@media(min-width:48rem){.hx\:rtl\:md\:right-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:auto}}.hx\:dark\:block:where(.dark,.dark *){display:block}.hx\:dark\:hidden:where(.dark,.dark *){display:none}.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:var(--hx-color-amber-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-amber-200)30%,transparent)}}.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:var(--hx-color-blue-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-blue-200)30%,transparent)}}.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-gray-100)20%,transparent)}}.hx\:dark\:border-gray-400:where(.dark,.dark *){border-color:var(--hx-color-gray-400)}.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:var(--hx-color-green-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-green-200)30%,transparent)}}.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:var(--hx-color-indigo-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-indigo-200)30%,transparent)}}.hx\:dark\:border-neutral-700:where(.dark,.dark *){border-color:var(--hx-color-neutral-700)}.hx\:dark\:border-neutral-800:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-orange-400)30%,transparent)}}.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:var(--hx-color-purple-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-purple-200)30%,transparent)}}.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:var(--hx-color-red-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-red-200)30%,transparent)}}.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:var(--hx-color-yellow-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-yellow-200)30%,transparent)}}.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:var(--hx-color-amber-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-amber-900)30%,transparent)}}.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:var(--hx-color-blue-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-blue-900)30%,transparent)}}.hx\:dark\:bg-dark:where(.dark,.dark *),.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:var(--hx-color-dark)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)50%,transparent)}}.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:var(--hx-color-gray-50)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-50)10%,transparent)}}.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:var(--hx-color-green-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-green-900)30%,transparent)}}.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:var(--hx-color-indigo-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-indigo-900)30%,transparent)}}.hx\:dark\:bg-neutral-700:where(.dark,.dark *){background-color:var(--hx-color-neutral-700)}.hx\:dark\:bg-neutral-800:where(.dark,.dark *){background-color:var(--hx-color-neutral-800)}.hx\:dark\:bg-neutral-900:where(.dark,.dark *){background-color:var(--hx-color-neutral-900)}.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-orange-400)20%,transparent)}}.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-400)10%,transparent)}}.hx\:dark\:bg-primary-600:where(.dark,.dark *){background-color:var(--hx-color-primary-600)}.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:var(--hx-color-purple-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-purple-900)30%,transparent)}}.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:var(--hx-color-red-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-red-900)30%,transparent)}}.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:var(--hx-color-yellow-700)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-yellow-700)30%,transparent)}}.hx\:dark\:from-gray-100:where(.dark,.dark *){--tw-gradient-from:var(--hx-color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:to-gray-400:where(.dark,.dark *){--tw-gradient-to:var(--hx-color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:text-amber-200:where(.dark,.dark *){color:var(--hx-color-amber-200)}.hx\:dark\:text-blue-200:where(.dark,.dark *){color:var(--hx-color-blue-200)}.hx\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:dark\:text-gray-200:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hx\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:dark\:text-gray-400:where(.dark,.dark *){color:var(--hx-color-gray-400)}.hx\:dark\:text-green-200:where(.dark,.dark *){color:var(--hx-color-green-200)}.hx\:dark\:text-indigo-200:where(.dark,.dark *){color:var(--hx-color-indigo-200)}.hx\:dark\:text-neutral-200:where(.dark,.dark *){color:var(--hx-color-neutral-200)}.hx\:dark\:text-neutral-400:where(.dark,.dark *){color:var(--hx-color-neutral-400)}.hx\:dark\:text-orange-300:where(.dark,.dark *){color:var(--hx-color-orange-300)}.hx\:dark\:text-primary-600:where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:text-purple-200:where(.dark,.dark *){color:var(--hx-color-purple-200)}.hx\:dark\:text-red-200:where(.dark,.dark *){color:var(--hx-color-red-200)}.hx\:dark\:text-slate-100:where(.dark,.dark *){color:var(--hx-color-slate-100)}.hx\:dark\:text-white:where(.dark,.dark *){color:var(--hx-color-white)}.hx\:dark\:text-yellow-200:where(.dark,.dark *){color:var(--hx-color-yellow-200)}.hx\:dark\:opacity-80:where(.dark,.dark *){opacity:.8}.hx\:dark\:shadow-\[0_-1px_0_rgba\(255\,255\,255\,\.1\)_inset\]:where(.dark,.dark *){--tw-shadow:0 -1px 0 var(--tw-shadow-color,#ffffff1a)inset;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-\[0_-12px_16px_\#111\]:where(.dark,.dark *){--tw-shadow:0 -12px 16px var(--tw-shadow-color,#111);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:dark\:group-hover\:text-gray-50:where(.dark,.dark *):is(:where(.hx\:group):hover *){color:var(--hx-color-gray-50)}}.hx\:group-data-\[active\=true\]\:dark\:text-primary-600:is(:where(.hx\:group)[data-active=true] *):where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:placeholder\:text-gray-400:where(.dark,.dark *)::placeholder{color:var(--hx-color-gray-400)}.hx\:dark\:before\:bg-neutral-800:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hx\:dark\:before\:invert:where(.dark,.dark *):before{content:var(--tw-content);--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media(hover:hover){.hx\:dark\:hover\:border-gray-100:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-100)}.hx\:dark\:hover\:border-gray-600:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-600)}.hx\:dark\:hover\:border-neutral-500:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-500)}.hx\:dark\:hover\:border-neutral-700:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:border-neutral-800:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-gray-100)5%,transparent)}}.hx\:dark\:hover\:bg-neutral-700:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:bg-neutral-800:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-neutral-900:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-900)}.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-primary-100)5%,transparent)}}.hx\:dark\:hover\:bg-primary-700:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-700)}.hx\:dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--hx-color-gray-50)}.hx\:dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--hx-color-gray-100)}.hx\:dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--hx-color-gray-200)}.hx\:dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--hx-color-gray-300)}.hx\:dark\:hover\:text-neutral-50:where(.dark,.dark *):hover{color:var(--hx-color-neutral-50)}.hx\:dark\:hover\:text-primary-400:where(.dark,.dark *):hover{color:var(--hx-color-primary-400)}.hx\:dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--hx-color-white)}.hx\:dark\:hover\:shadow-none:where(.dark,.dark *):hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.hx\:dark\:focus-visible\:bg-dark:where(.dark,.dark *):focus-visible{background-color:var(--hx-color-dark)}.hx\:data-\[state\=selected\]\:dark\:border-primary-500[data-state=selected]:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:dark\:text-primary-600[data-state=selected]:where(.dark,.dark *){color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:dark\:border-current:where(.dark,.dark *){border-color:initial}.hx\:contrast-more\:dark\:border-gray-50:where(.dark,.dark *){border-color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:border-primary-500:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:dark\:contrast-more\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:text-current:where(.dark,.dark *){color:currentColor}.hx\:contrast-more\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:contrast-more\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:contrast-more\:dark\:shadow-\[0_0_0_1px_\#fff\]:where(.dark,.dark *){--tw-shadow:0 0 0 1px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:dark\:hover\:border-gray-50:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-50)}}}@media print{.hx\:print\:\[display\:none\],.hx\:print\:hidden{display:none}.hx\:print\:bg-transparent{background-color:#0000}}}html{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--hx-color-white);width:100%}body:where(.dark,.dark *){background-color:var(--hx-color-dark);color:var(--hx-color-gray-100)}:root{--primary-hue:212deg;--primary-saturation:100%;--primary-lightness:50%;--navbar-height:4rem;--hextra-banner-height:2rem;--menu-height:3.75rem}.dark{--primary-hue:204deg;--primary-saturation:100%;--primary-lightness:50%}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*2);font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height));--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-neutral-200)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-neutral-200)70%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){padding-bottom:calc(var(--hx-spacing)*1);font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--hx-color-neutral-400)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)10%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--hx-color-primary-600);text-decoration-line:underline;text-decoration-thickness:from-font}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);border-color:var(--hx-color-gray-300);color:var(--hx-color-gray-700);font-style:italic}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);padding-left:calc(var(--hx-spacing)*6);border-left-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);padding-right:calc(var(--hx-spacing)*6);border-right-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-gray-700);color:var(--hx-color-gray-400)}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){padding-block:calc(var(--hx-spacing)*4);--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*6);border-collapse:collapse;width:100%;padding:calc(var(--hx-spacing)*0);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);display:block;overflow-x:auto}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead{background-color:var(--hx-color-gray-50)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:var(--hx-color-gray-600)}@supports(color:color-mix(in lab,red,red)){.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-600)20%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr{margin:calc(var(--hx-spacing)*0);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:decimal}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:disc}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):has(li input[type=checkbox]){list-style-type:none}.content :where(ul,ol)>li>:where(ul,ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*0)}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:#0000}.content :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-inline:auto;margin-block:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-md)}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption{margin-top:calc(var(--hx-spacing)*2);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-500);display:block}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption:where(.dark,.dark *){color:var(--hx-color-gray-400)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dt{margin-top:calc(var(--hx-spacing)*6);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dd{margin-block:calc(var(--hx-spacing)*2);padding-inline-start:calc(var(--hx-spacing)*6)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*10);border-color:var(--hx-color-gray-200)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):last-child{margin-bottom:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .footnotes{margin-top:calc(var(--hx-spacing)*12);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.content .footnotes hr{border-color:var(--hx-color-gray-200)}.content .footnotes hr:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .subheading-anchor{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.content .subheading-anchor:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.content .subheading-anchor:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}span:target+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor),.content .subheading-anchor:focus-visible{opacity:1}span+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor){text-decoration-line:none!important}.content .subheading-anchor:after{content:var(--tw-content);color:var(--hx-color-gray-300)}.content .subheading-anchor:where(.dark,.dark *):after{content:var(--tw-content);color:var(--hx-color-neutral-700)}.content .subheading-anchor:after{padding-inline:calc(var(--hx-spacing)*1);--tw-content:"#";content:var(--tw-content)}span:target+:is(){color:var(--hx-color-gray-400)}span:target+:is():where(.dark,.dark *){color:var(--hx-color-neutral-500)}article details>summary::-webkit-details-marker{display:none}article details>summary:before{vertical-align:-4px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='hx:h-5 hx:w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E");width:1.2em;height:1.2em;padding:0 .6em}:lang(fa) ol{list-style-type:persian}.highlight .chroma .err{color:#a61717;background-color:#e3d2d2}.highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.highlight .chroma .line{display:flex}.highlight .chroma .k,.highlight .chroma .kc,.highlight .chroma .kd,.highlight .chroma .kn,.highlight .chroma .kp,.highlight .chroma .kr{color:#000;font-weight:700}.highlight .chroma .kt{color:#458;font-weight:700}.highlight .chroma .na{color:teal}.highlight .chroma .nb{color:#0086b3}.highlight .chroma .bp{color:#999}.highlight .chroma .nc{color:#458;font-weight:700}.highlight .chroma .no{color:teal}.highlight .chroma .nd{color:#3c5d5d;font-weight:700}.highlight .chroma .ni{color:purple}.highlight .chroma .ne,.highlight .chroma .nf,.highlight .chroma .nl{color:#900;font-weight:700}.highlight .chroma .nn{color:#555}.highlight .chroma .nt{color:navy}.highlight .chroma .nv,.highlight .chroma .vc,.highlight .chroma .vg,.highlight .chroma .vi{color:teal}.highlight .chroma .s,.highlight .chroma .sa,.highlight .chroma .sb,.highlight .chroma .sc,.highlight .chroma .dl,.highlight .chroma .sd,.highlight .chroma .s2,.highlight .chroma .se,.highlight .chroma .sh,.highlight .chroma .si,.highlight .chroma .sx{color:#d14}.highlight .chroma .sr{color:#009926}.highlight .chroma .s1{color:#d14}.highlight .chroma .ss{color:#990073}.highlight .chroma .m,.highlight .chroma .mb,.highlight .chroma .mf,.highlight .chroma .mh,.highlight .chroma .mi,.highlight .chroma .il,.highlight .chroma .mo{color:#099}.highlight .chroma .o,.highlight .chroma .ow{color:#000;font-weight:700}.highlight .chroma .c,.highlight .chroma .ch,.highlight .chroma .cm,.highlight .chroma .c1{color:#998;font-style:italic}.highlight .chroma .cs,.highlight .chroma .cp,.highlight .chroma .cpf{color:#999;font-style:italic;font-weight:700}.highlight .chroma .gd{color:#000;background-color:#fdd}.highlight .chroma .ge{color:#000;font-style:italic}.highlight .chroma .gr{color:#a00}.highlight .chroma .gh{color:#999}.highlight .chroma .gi{color:#000;background-color:#dfd}.highlight .chroma .go{color:#888}.highlight .chroma .gp{color:#555}.highlight .chroma .gs{font-weight:700}.highlight .chroma .gu{color:#aaa}.highlight .chroma .gt{color:#a00}.highlight .chroma .gl{text-decoration:underline}.highlight .chroma .w{color:#bbb}.dark .highlight .chroma .err{color:#f85149}.dark .highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.dark .highlight .chroma .line{display:flex}.dark .highlight .chroma .k{color:#ff7b72}.dark .highlight .chroma .kc{color:#79c0ff}.dark .highlight .chroma .kd,.dark .highlight .chroma .kn{color:#ff7b72}.dark .highlight .chroma .kp{color:#79c0ff}.dark .highlight .chroma .kr,.dark .highlight .chroma .kt{color:#ff7b72}.dark .highlight .chroma .nc{color:#f0883e;font-weight:700}.dark .highlight .chroma .no{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nd{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .ni{color:#ffa657}.dark .highlight .chroma .ne{color:#f0883e;font-weight:700}.dark .highlight .chroma .nf{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .nl{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nn{color:#ff7b72}.dark .highlight .chroma .py{color:#79c0ff}.dark .highlight .chroma .nt{color:#7ee787}.dark .highlight .chroma .nv{color:#79c0ff}.dark .highlight .chroma .l{color:#a5d6ff}.dark .highlight .chroma .ld{color:#79c0ff}.dark .highlight .chroma .s{color:#a5d6ff}.dark .highlight .chroma .sa{color:#79c0ff}.dark .highlight .chroma .sb,.dark .highlight .chroma .sc{color:#a5d6ff}.dark .highlight .chroma .dl{color:#79c0ff}.dark .highlight .chroma .sd,.dark .highlight .chroma .s2{color:#a5d6ff}.dark .highlight .chroma .se,.dark .highlight .chroma .sh{color:#79c0ff}.dark .highlight .chroma .si,.dark .highlight .chroma .sx{color:#a5d6ff}.dark .highlight .chroma .sr{color:#79c0ff}.dark .highlight .chroma .s1,.dark .highlight .chroma .ss,.dark .highlight .chroma .m,.dark .highlight .chroma .mb,.dark .highlight .chroma .mf,.dark .highlight .chroma .mh,.dark .highlight .chroma .mi,.dark .highlight .chroma .il,.dark .highlight .chroma .mo{color:#a5d6ff}.dark .highlight .chroma .o,.dark .highlight .chroma .ow{color:#ff7b72;font-weight:700}.dark .highlight .chroma .c,.dark .highlight .chroma .ch,.dark .highlight .chroma .cm,.dark .highlight .chroma .c1{color:#8b949e;font-style:italic}.dark .highlight .chroma .cs,.dark .highlight .chroma .cp,.dark .highlight .chroma .cpf{color:#8b949e;font-style:italic;font-weight:700}.dark .highlight .chroma .gd{color:#ffa198;background-color:#490202}.dark .highlight .chroma .ge{color:inherit;font-style:italic}.dark .highlight .chroma .gr{color:#ffa198}.dark .highlight .chroma .gh{color:#79c0ff;font-weight:700}.dark .highlight .chroma .gi{color:#56d364;background-color:#0f5323}.dark .highlight .chroma .go,.dark .highlight .chroma .gp{color:#8b949e}.dark .highlight .chroma .gs{font-weight:700}.dark .highlight .chroma .gu{color:#79c0ff}.dark .highlight .chroma .gt{color:#ff7b72}.dark .highlight .chroma .gl{text-decoration:underline}.dark .highlight .chroma .w{color:#6e7681}.hextra-code-block{--tw-leading:calc(var(--hx-spacing)*5);font-size:.9em;line-height:calc(var(--hx-spacing)*5)}.hextra-code-block pre{background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block pre{--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.hextra-code-block pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.hextra-code-block pre{--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.hextra-code-block pre:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.hextra-code-block pre:where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.hextra-code-block .hextra-code-filename{top:calc(var(--hx-spacing)*0);z-index:1;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:var(--hx-radius-xl);border-top-right-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);width:100%;position:absolute;overflow:hidden}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block .hextra-code-filename{padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*2);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));color:var(--hx-color-gray-700)}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hextra-code-block .hextra-code-filename+pre:not(.lntable pre){padding-top:calc(var(--hx-spacing)*12)}.hextra-code-block pre:not(.lntable pre){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*4)}.hextra-code-block div:nth-of-type(2) pre{padding-top:calc(var(--hx-spacing)*12);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .lntable{margin:calc(var(--hx-spacing)*0);border-radius:var(--hx-radius-xl);width:auto;display:block;overflow:auto}.chroma .lntable pre{padding-top:calc(var(--hx-spacing)*4);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line){min-width:2.6rem;padding-right:calc(var(--hx-spacing)*4);padding-left:calc(var(--hx-spacing)*4);color:var(--hx-color-neutral-600)}:is(.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line)):where(.dark,.dark *){color:var(--hx-color-neutral-300)}.chroma .lntd{padding:calc(var(--hx-spacing)*0);vertical-align:top}.chroma .lntd:last-of-type{width:100%}.chroma .hl{background-color:var(--hx-color-primary-800);width:100%;display:block}@supports(color:color-mix(in lab,red,red)){.chroma .hl{background-color:color-mix(in oklab,var(--hx-color-primary-800)10%,transparent)}}.hextra-cards{grid-template-columns:repeat(auto-fill,minmax(max(250px,calc((100% - 1rem*2)/var(--hextra-cards-grid-cols))),1fr))}.hextra-card{position:relative}.hextra-card img{-webkit-user-select:none;user-select:none}.hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card .hextra-card-icon svg{color:#0003;width:1.5rem;transition:color .3s}.hextra-card p{margin-top:.5rem;position:relative}.dark .hextra-card .hextra-card-icon svg{color:#fff6}.dark .hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card-tag{z-index:10;position:absolute;top:5px}.hextra-card-tag:where(:dir(ltr)){right:5px}.hextra-card-tag:where(:dir(rtl)){left:5px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker){counter-increment:step}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);margin-left:-41px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);margin-right:-44px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);background-color:var(--hx-color-gray-100)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:4px;border-color:var(--hx-color-white)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);border-color:var(--hx-color-dark)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step);text-align:center;text-indent:-1px;width:33px;height:33px;font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal);color:var(--hx-color-neutral-400);border-radius:340282e33px;position:absolute}:lang(fa) .hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step,persian)}.hextra-search-wrapper li{margin-inline:calc(var(--hx-spacing)*2.5);border-radius:var(--hx-radius-md);overflow-wrap:break-word;color:var(--hx-color-gray-800)}@media(prefers-contrast:more){.hextra-search-wrapper li{border-style:var(--tw-border-style);border-width:1px;border-color:#0000}}.hextra-search-wrapper li:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hextra-search-wrapper li a{scroll-margin:calc(var(--hx-spacing)*12);padding-inline:calc(var(--hx-spacing)*2.5);padding-block:calc(var(--hx-spacing)*2);display:block}.hextra-search-wrapper li a:focus-visible{--tw-outline-style:none;outline-style:none}.hextra-search-wrapper li .hextra-search-title{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hextra-search-wrapper li .hextra-search-active{border-radius:var(--hx-radius-md);background-color:var(--hx-color-primary-500)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper li .hextra-search-active{background-color:color-mix(in oklab,var(--hx-color-primary-500)10%,transparent)}}@media(prefers-contrast:more){.hextra-search-wrapper li .hextra-search-active{border-color:var(--hx-color-primary-500)}}.hextra-search-wrapper .hextra-search-no-result{padding:calc(var(--hx-spacing)*8);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-400);-webkit-user-select:none;user-select:none;display:block}.hextra-search-wrapper .hextra-search-prefix{margin-inline:calc(var(--hx-spacing)*2.5);margin-top:calc(var(--hx-spacing)*6);margin-bottom:calc(var(--hx-spacing)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix{border-color:color-mix(in oklab,var(--hx-color-black)10%,transparent)}}.hextra-search-wrapper .hextra-search-prefix{padding-inline:calc(var(--hx-spacing)*2.5);padding-bottom:calc(var(--hx-spacing)*1.5);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);color:var(--hx-color-gray-500);text-transform:uppercase;-webkit-user-select:none;user-select:none}.hextra-search-wrapper .hextra-search-prefix:first-child{margin-top:calc(var(--hx-spacing)*0)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix{border-color:var(--hx-color-gray-600);color:var(--hx-color-gray-900)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)20%,transparent)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){color:var(--hx-color-gray-300)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-gray-50);color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{margin-top:calc(var(--hx-spacing)*1);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:1.35rem;text-overflow:ellipsis;color:var(--hx-color-gray-600);line-height:1.35rem;overflow:hidden}.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-400)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box}.hextra-search-wrapper .hextra-search-match{color:var(--hx-color-primary-600)}@media(max-width:48rem){.hextra-sidebar-container{top:calc(var(--hx-spacing)*0);bottom:calc(var(--hx-spacing)*0);z-index:15;overscroll-behavior:contain;background-color:var(--hx-color-white);width:100%;padding-top:calc(var(--navbar-height) + var(--hextra-banner-height));position:fixed}.hextra-sidebar-container:where(.dark,.dark *){background-color:var(--hx-color-dark)}.hextra-sidebar-container{will-change:transform,opacity;contain:layout style;backface-visibility:hidden;transition:transform .4s cubic-bezier(.52,.16,.04,1)}}.hextra-sidebar-container li>.hextra-sidebar-children{height:calc(var(--hx-spacing)*0)}.hextra-sidebar-container li.open>.hextra-sidebar-children{height:auto;padding-top:calc(var(--hx-spacing)*1)}.hextra-sidebar-container li.open>.hextra-sidebar-item>.hextra-sidebar-collapsible-button>svg>path{rotate:90deg}.hextra-banner-hidden .hextra-banner{display:none}.hextra-banner :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:underline;text-decoration-thickness:from-font}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}nav .hextra-search-wrapper{display:none}@media(min-width:48rem){nav .hextra-search-wrapper{display:inline-block}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-nav-container-blur{background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur{background-color:color-mix(in oklab,var(--hx-color-white)85%,transparent)}}.hextra-nav-container-blur{--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-nav-container-blur:where(.dark,.dark *){background-color:var(--hx-color-dark)!important}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)80%,transparent)!important}}}.hextra-hamburger-menu svg g{transform-origin:50%;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg path{opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open path{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:0s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open g{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open>path{opacity:0}.hextra-hamburger-menu svg.open>g:first-of-type{rotate:45deg}.hextra-hamburger-menu svg.open>g:first-of-type path{--tw-translate-y:calc(var(--hx-spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-hamburger-menu svg.open>g:nth-of-type(2){rotate:-45deg}.hextra-hamburger-menu svg.open>g:nth-of-type(2) path{--tw-translate-y:calc(var(--hx-spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-scrollbar,.hextra-scrollbar *{scrollbar-width:thin;scrollbar-color:oklch(55.55% 0 0/.4)transparent;scrollbar-gutter:stable}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar{height:calc(var(--hx-spacing)*3);width:calc(var(--hx-spacing)*3)}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-track{background-color:#0000}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-thumb{border-radius:10px}:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{background-color:var(--tw-shadow-color);--tw-shadow-color:var(--hx-color-neutral-500);background-clip:content-box;border:3px solid #0000}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}@media(hover:hover){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:var(--hx-color-neutral-500)}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)40%,transparent)var(--tw-shadow-alpha),transparent)}}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-code-copy-btn{opacity:.85;--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-code-copy-btn:where(.dark,.dark *){opacity:.8}}@media(min-width:1024px){.hextra-feature-grid{grid-template-columns:repeat(var(--hextra-feature-grid-cols),minmax(0,1fr))}}.hextra-jupyter-code-cell{scrollbar-gutter:auto;margin-top:calc(var(--hx-spacing)*6)}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:hidden}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs{max-height:50vh;overflow:auto}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs pre{max-width:100%;font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:auto}.hextra-badge{align-items:center;display:inline-flex}.hextra-toc a.hextra-toc-active{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.2s;transition-duration:.2s;color:var(--hx-color-gray-900)!important}.hextra-toc a.hextra-toc-active:where(.dark,.dark *){color:var(--hx-color-gray-50)!important}.hextra-archive-timeline{border-left-style:var(--tw-border-style);border-left-width:2px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline{border-color:color-mix(in oklab,var(--hx-color-black)15%,transparent)}}.hextra-archive-timeline:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)15%,transparent)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}:root{--hextra-navbar-height:10.5rem} \ No newline at end of file diff --git a/public/css/compiled/main.min.dd0a2e06153504ce1622d60dbc1c29c348f59a33b1a7ebefabee95a192584553.css b/public/css/compiled/main.min.dd0a2e06153504ce1622d60dbc1c29c348f59a33b1a7ebefabee95a192584553.css new file mode 100644 index 0000000..57ef37d --- /dev/null +++ b/public/css/compiled/main.min.dd0a2e06153504ce1622d60dbc1c29c348f59a33b1a7ebefabee95a192584553.css @@ -0,0 +1 @@ +:root{--hextra-max-page-width:80rem;--hextra-max-content-width:72rem;--hextra-max-navbar-width:80rem;--hextra-max-footer-width:80rem}.hextra-max-page-width{max-width:var(--hextra-max-page-width)}.hextra-max-content-width{max-width:var(--hextra-max-content-width)}.hextra-max-navbar-width{max-width:var(--hextra-max-navbar-width)}.hextra-max-footer-width{max-width:var(--hextra-max-footer-width)}/*!tailwindcss v4.1.18 | MIT License | https://tailwindcss.com*/@layer properties{@supports(((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--hx-font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--hx-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--hx-color-red-100:oklch(93.6% .032 17.717);--hx-color-red-200:oklch(88.5% .062 18.334);--hx-color-red-900:oklch(39.6% .141 25.723);--hx-color-orange-50:oklch(98% .016 73.684);--hx-color-orange-100:oklch(95.4% .038 75.164);--hx-color-orange-300:oklch(83.7% .128 66.29);--hx-color-orange-400:oklch(75% .183 55.934);--hx-color-orange-800:oklch(47% .157 37.304);--hx-color-amber-100:oklch(96.2% .059 95.617);--hx-color-amber-200:oklch(92.4% .12 95.746);--hx-color-amber-900:oklch(41.4% .112 45.904);--hx-color-yellow-50:oklch(98.7% .026 102.212);--hx-color-yellow-100:oklch(97.3% .071 103.193);--hx-color-yellow-200:oklch(94.5% .129 101.54);--hx-color-yellow-700:oklch(55.4% .135 66.442);--hx-color-yellow-900:oklch(42.1% .095 57.708);--hx-color-green-100:oklch(96.2% .044 156.743);--hx-color-green-200:oklch(92.5% .084 155.995);--hx-color-green-900:oklch(39.3% .095 152.535);--hx-color-blue-100:oklch(93.2% .032 255.585);--hx-color-blue-200:oklch(88.2% .059 254.128);--hx-color-blue-900:oklch(37.9% .146 265.522);--hx-color-indigo-100:oklch(93% .034 272.788);--hx-color-indigo-200:oklch(87% .065 274.039);--hx-color-indigo-900:oklch(35.9% .144 278.697);--hx-color-purple-100:oklch(94.6% .033 307.174);--hx-color-purple-200:oklch(90.2% .063 306.703);--hx-color-purple-900:oklch(38.1% .176 304.987);--hx-color-slate-50:oklch(98.4% .003 247.858);--hx-color-slate-100:oklch(96.8% .007 247.896);--hx-color-slate-900:oklch(20.8% .042 265.755);--hx-color-gray-50:oklch(98.5% .002 247.839);--hx-color-gray-100:oklch(96.7% .003 264.542);--hx-color-gray-200:oklch(92.8% .006 264.531);--hx-color-gray-300:oklch(87.2% .01 258.338);--hx-color-gray-400:oklch(70.7% .022 261.325);--hx-color-gray-500:oklch(55.1% .027 264.364);--hx-color-gray-600:oklch(44.6% .03 256.802);--hx-color-gray-700:oklch(37.3% .034 259.733);--hx-color-gray-800:oklch(27.8% .033 256.848);--hx-color-gray-900:oklch(21% .034 264.665);--hx-color-neutral-50:oklch(98.5% 0 0);--hx-color-neutral-200:oklch(92.2% 0 0);--hx-color-neutral-300:oklch(87% 0 0);--hx-color-neutral-400:oklch(70.8% 0 0);--hx-color-neutral-500:oklch(55.6% 0 0);--hx-color-neutral-600:oklch(43.9% 0 0);--hx-color-neutral-700:oklch(37.1% 0 0);--hx-color-neutral-800:oklch(26.9% 0 0);--hx-color-neutral-900:oklch(20.5% 0 0);--hx-color-black:#000;--hx-color-white:#fff;--hx-spacing:.25rem;--hx-breakpoint-xl:80rem;--hx-text-xs:.75rem;--hx-text-xs--line-height:calc(1/.75);--hx-text-sm:.875rem;--hx-text-sm--line-height:calc(1.25/.875);--hx-text-base:1rem;--hx-text-base--line-height:calc(1.5/1);--hx-text-lg:1.125rem;--hx-text-lg--line-height:calc(1.75/1.125);--hx-text-xl:1.25rem;--hx-text-xl--line-height:calc(1.75/1.25);--hx-text-2xl:1.5rem;--hx-text-2xl--line-height:calc(2/1.5);--hx-text-3xl:1.875rem;--hx-text-3xl--line-height:calc(2.25/1.875);--hx-text-4xl:2.25rem;--hx-text-4xl--line-height:calc(2.5/2.25);--hx-text-5xl:3rem;--hx-text-5xl--line-height:1;--hx-font-weight-normal:400;--hx-font-weight-medium:500;--hx-font-weight-semibold:600;--hx-font-weight-bold:700;--hx-font-weight-extrabold:800;--hx-tracking-tighter:-.05em;--hx-tracking-tight:-.025em;--hx-leading-tight:1.25;--hx-radius-xs:.125rem;--hx-radius-sm:.25rem;--hx-radius-md:.375rem;--hx-radius-lg:.5rem;--hx-radius-xl:.75rem;--hx-radius-3xl:1.5rem;--hx-ease-in:cubic-bezier(.4,0,1,1);--hx-ease-out:cubic-bezier(0,0,.2,1);--hx-ease-in-out:cubic-bezier(.4,0,.2,1);--hx-blur-md:12px;--hx-default-transition-duration:.15s;--hx-default-transition-timing-function:cubic-bezier(.4,0,.2,1);--hx-default-font-family:var(--hx-font-sans);--hx-default-mono-font-family:var(--hx-font-mono);--hx-color-primary-100:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*44));--hx-color-primary-200:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*36));--hx-color-primary-300:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*27));--hx-color-primary-400:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*16));--hx-color-primary-500:hsl(var(--primary-hue)var(--primary-saturation)var(--primary-lightness));--hx-color-primary-600:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*45));--hx-color-primary-700:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*39));--hx-color-primary-800:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*32));--hx-color-primary-900:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*24));--hx-color-dark:#111}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--hx-default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--hx-default-font-feature-settings,normal);font-variation-settings:var(--hx-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--hx-default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--hx-default-mono-font-feature-settings,normal);font-variation-settings:var(--hx-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports(color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}abbr:where([title]){cursor:help}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible:where(.dark,.dark *){--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}}@layer components;@layer utilities{.hx\:pointer-events-none{pointer-events:none}.hx\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hx\:absolute{position:absolute}.hx\:relative{position:relative}.hx\:sticky{position:sticky}.hx\:inset-x-0{inset-inline:calc(var(--hx-spacing)*0)}.hx\:inset-y-0{inset-block:calc(var(--hx-spacing)*0)}.hx\:top-0{top:calc(var(--hx-spacing)*0)}.hx\:top-1\/2{top:50%}.hx\:top-8{top:calc(var(--hx-spacing)*8)}.hx\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:top-\[40\%\]{top:40%}.hx\:top-full{top:100%}.hx\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:bottom-0{bottom:calc(var(--hx-spacing)*0)}.hx\:left-0{left:calc(var(--hx-spacing)*0)}.hx\:left-\[24px\]{left:24px}.hx\:left-\[36px\]{left:36px}.hx\:z-20{z-index:20}.hx\:z-\[-1\]{z-index:-1}.hx\:order-last{order:9999}.hx\:m-\[11px\]{margin:11px}.hx\:mx-1{margin-inline:calc(var(--hx-spacing)*1)}.hx\:mx-4{margin-inline:calc(var(--hx-spacing)*4)}.hx\:mx-auto{margin-inline:auto}.hx\:my-1{margin-block:calc(var(--hx-spacing)*1)}.hx\:my-1\.5{margin-block:calc(var(--hx-spacing)*1.5)}.hx\:my-2{margin-block:calc(var(--hx-spacing)*2)}.hx\:-mt-20{margin-top:calc(var(--hx-spacing)*-20)}.hx\:mt-1{margin-top:calc(var(--hx-spacing)*1)}.hx\:mt-1\.5{margin-top:calc(var(--hx-spacing)*1.5)}.hx\:mt-2{margin-top:calc(var(--hx-spacing)*2)}.hx\:mt-4{margin-top:calc(var(--hx-spacing)*4)}.hx\:mt-5{margin-top:calc(var(--hx-spacing)*5)}.hx\:mt-6{margin-top:calc(var(--hx-spacing)*6)}.hx\:mt-8{margin-top:calc(var(--hx-spacing)*8)}.hx\:mt-12{margin-top:calc(var(--hx-spacing)*12)}.hx\:mt-16{margin-top:calc(var(--hx-spacing)*16)}.hx\:mt-auto{margin-top:auto}.hx\:-mr-2{margin-right:calc(var(--hx-spacing)*-2)}.hx\:mr-1{margin-right:calc(var(--hx-spacing)*1)}.hx\:mr-2{margin-right:calc(var(--hx-spacing)*2)}.hx\:-mb-0\.5{margin-bottom:calc(var(--hx-spacing)*-.5)}.hx\:mb-0{margin-bottom:calc(var(--hx-spacing)*0)}.hx\:mb-2{margin-bottom:calc(var(--hx-spacing)*2)}.hx\:mb-4{margin-bottom:calc(var(--hx-spacing)*4)}.hx\:mb-6{margin-bottom:calc(var(--hx-spacing)*6)}.hx\:mb-8{margin-bottom:calc(var(--hx-spacing)*8)}.hx\:mb-10{margin-bottom:calc(var(--hx-spacing)*10)}.hx\:mb-12{margin-bottom:calc(var(--hx-spacing)*12)}.hx\:mb-16{margin-bottom:calc(var(--hx-spacing)*16)}.hx\:-ml-2{margin-left:calc(var(--hx-spacing)*-2)}.hx\:ml-2{margin-left:calc(var(--hx-spacing)*2)}.hx\:ml-4{margin-left:calc(var(--hx-spacing)*4)}.hx\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hx\:block{display:block}.hx\:flex{display:flex}.hx\:grid{display:grid}.hx\:hidden{display:none}.hx\:inline{display:inline}.hx\:inline-block{display:inline-block}.hx\:inline-flex{display:inline-flex}.hx\:aspect-auto{aspect-ratio:auto}.hx\:size-4{width:calc(var(--hx-spacing)*4);height:calc(var(--hx-spacing)*4)}.hx\:h-0{height:calc(var(--hx-spacing)*0)}.hx\:h-2{height:calc(var(--hx-spacing)*2)}.hx\:h-3\.5{height:calc(var(--hx-spacing)*3.5)}.hx\:h-4{height:calc(var(--hx-spacing)*4)}.hx\:h-5{height:calc(var(--hx-spacing)*5)}.hx\:h-7{height:calc(var(--hx-spacing)*7)}.hx\:h-10{height:calc(var(--hx-spacing)*10)}.hx\:h-16{height:calc(var(--hx-spacing)*16)}.hx\:h-\[18px\]{height:18px}.hx\:h-full{height:100%}.hx\:h-px{height:1px}.hx\:max-h-\(--menu-height\){max-height:var(--menu-height)}.hx\:max-h-64{max-height:calc(var(--hx-spacing)*64)}.hx\:max-h-\[calc\(100vh-var\(--navbar-height\)-env\(safe-area-inset-bottom\)\)\]{max-height:calc(100vh - var(--navbar-height) - env(safe-area-inset-bottom))}.hx\:max-h-\[min\(calc\(50vh-11rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(50vh - 11rem - env(safe-area-inset-bottom)),400px)}.hx\:min-h-\[100px\]{min-height:100px}.hx\:min-h-\[calc\(100vh-var\(--navbar-height\)\)\]{min-height:calc(100vh - var(--navbar-height))}.hx\:w-2{width:calc(var(--hx-spacing)*2)}.hx\:w-3\.5{width:calc(var(--hx-spacing)*3.5)}.hx\:w-4{width:calc(var(--hx-spacing)*4)}.hx\:w-10{width:calc(var(--hx-spacing)*10)}.hx\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:w-\[110\%\]{width:110%}.hx\:w-\[180\%\]{width:180%}.hx\:w-full{width:100%}.hx\:w-max{width:max-content}.hx\:w-screen{width:100vw}.hx\:max-w-\[50\%\]{max-width:50%}.hx\:max-w-\[90rem\]{max-width:90rem}.hx\:max-w-\[min\(calc\(100vw-2rem\)\,calc\(100\%\+20rem\)\)\]{max-width:min(100vw - 2rem,100% + 20rem)}.hx\:max-w-full{max-width:100%}.hx\:max-w-none{max-width:none}.hx\:max-w-screen-xl{max-width:var(--hx-breakpoint-xl)}.hx\:min-w-0{min-width:calc(var(--hx-spacing)*0)}.hx\:min-w-\[18px\]{min-width:18px}.hx\:min-w-\[24px\]{min-width:24px}.hx\:min-w-full{min-width:100%}.hx\:shrink-0{flex-shrink:0}.hx\:grow{flex-grow:1}.hx\:origin-center{transform-origin:50%}.hx\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hx\:cursor-default{cursor:default}.hx\:cursor-pointer{cursor:pointer}.hx\:scroll-my-6{scroll-margin-block:calc(var(--hx-spacing)*6)}.hx\:scroll-py-6{scroll-padding-block:calc(var(--hx-spacing)*6)}.hx\:list-none{list-style-type:none}.hx\:appearance-none{appearance:none}.hx\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.hx\:flex-col{flex-direction:column}.hx\:flex-wrap{flex-wrap:wrap}.hx\:items-baseline{align-items:baseline}.hx\:items-center{align-items:center}.hx\:items-start{align-items:flex-start}.hx\:justify-between{justify-content:space-between}.hx\:justify-center{justify-content:center}.hx\:justify-end{justify-content:flex-end}.hx\:justify-start{justify-content:flex-start}.hx\:justify-items-start{justify-items:start}.hx\:gap-1{gap:calc(var(--hx-spacing)*1)}.hx\:gap-1\.5{gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-2{gap:calc(var(--hx-spacing)*2)}.hx\:gap-4{gap:calc(var(--hx-spacing)*4)}.hx\:gap-x-1\.5{column-gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-x-2{column-gap:calc(var(--hx-spacing)*2)}.hx\:gap-y-1{row-gap:calc(var(--hx-spacing)*1)}.hx\:gap-y-2{row-gap:calc(var(--hx-spacing)*2)}.hx\:overflow-auto{overflow:auto}.hx\:overflow-hidden{overflow:hidden}.hx\:overflow-x-auto{overflow-x:auto}.hx\:overflow-x-hidden{overflow-x:hidden}.hx\:overflow-y-auto{overflow-y:auto}.hx\:overflow-y-hidden{overflow-y:hidden}.hx\:overscroll-contain{overscroll-behavior:contain}.hx\:overscroll-x-contain{overscroll-behavior-x:contain}.hx\:rounded-3xl{border-radius:var(--hx-radius-3xl)}.hx\:rounded-full{border-radius:340282e33px}.hx\:rounded-lg{border-radius:var(--hx-radius-lg)}.hx\:rounded-md{border-radius:var(--hx-radius-md)}.hx\:rounded-sm{border-radius:var(--hx-radius-sm)}.hx\:rounded-xl{border-radius:var(--hx-radius-xl)}.hx\:rounded-xs{border-radius:var(--hx-radius-xs)}.hx\:rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.hx\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:border-0{border-style:var(--tw-border-style);border-width:0}.hx\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.hx\:border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.hx\:border-amber-200{border-color:var(--hx-color-amber-200)}.hx\:border-black\/5{border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:border-black\/5{border-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:border-blue-200{border-color:var(--hx-color-blue-200)}.hx\:border-gray-200{border-color:var(--hx-color-gray-200)}.hx\:border-gray-500{border-color:var(--hx-color-gray-500)}.hx\:border-green-200{border-color:var(--hx-color-green-200)}.hx\:border-indigo-200{border-color:var(--hx-color-indigo-200)}.hx\:border-orange-100{border-color:var(--hx-color-orange-100)}.hx\:border-purple-200{border-color:var(--hx-color-purple-200)}.hx\:border-red-200{border-color:var(--hx-color-red-200)}.hx\:border-transparent{border-color:#0000}.hx\:border-yellow-100{border-color:var(--hx-color-yellow-100)}.hx\:bg-amber-100{background-color:var(--hx-color-amber-100)}.hx\:bg-black\/\[\.05\]{background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-black\/\[\.05\]{background-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:bg-blue-100{background-color:var(--hx-color-blue-100)}.hx\:bg-gray-100{background-color:var(--hx-color-gray-100)}.hx\:bg-gray-200{background-color:var(--hx-color-gray-200)}.hx\:bg-green-100{background-color:var(--hx-color-green-100)}.hx\:bg-indigo-100{background-color:var(--hx-color-indigo-100)}.hx\:bg-neutral-50{background-color:var(--hx-color-neutral-50)}.hx\:bg-neutral-900{background-color:var(--hx-color-neutral-900)}.hx\:bg-orange-50{background-color:var(--hx-color-orange-50)}.hx\:bg-primary-100{background-color:var(--hx-color-primary-100)}.hx\:bg-primary-400{background-color:var(--hx-color-primary-400)}.hx\:bg-primary-600{background-color:var(--hx-color-primary-600)}.hx\:bg-primary-700\/5{background-color:var(--hx-color-primary-700)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-primary-700\/5{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hx\:bg-purple-100{background-color:var(--hx-color-purple-100)}.hx\:bg-red-100{background-color:var(--hx-color-red-100)}.hx\:bg-transparent{background-color:#0000}.hx\:bg-white{background-color:var(--hx-color-white)}.hx\:bg-yellow-50{background-color:var(--hx-color-yellow-50)}.hx\:bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.hx\:from-gray-900{--tw-gradient-from:var(--hx-color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:to-gray-600{--tw-gradient-to:var(--hx-color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:bg-clip-text{-webkit-background-clip:text;background-clip:text}.hx\:p-0{padding:calc(var(--hx-spacing)*0)}.hx\:p-0\.5{padding:calc(var(--hx-spacing)*.5)}.hx\:p-1{padding:calc(var(--hx-spacing)*1)}.hx\:p-1\.5{padding:calc(var(--hx-spacing)*1.5)}.hx\:p-2{padding:calc(var(--hx-spacing)*2)}.hx\:p-4{padding:calc(var(--hx-spacing)*4)}.hx\:p-6{padding:calc(var(--hx-spacing)*6)}.hx\:px-1\.5{padding-inline:calc(var(--hx-spacing)*1.5)}.hx\:px-2{padding-inline:calc(var(--hx-spacing)*2)}.hx\:px-2\.5{padding-inline:calc(var(--hx-spacing)*2.5)}.hx\:px-3{padding-inline:calc(var(--hx-spacing)*3)}.hx\:px-4{padding-inline:calc(var(--hx-spacing)*4)}.hx\:px-6{padding-inline:calc(var(--hx-spacing)*6)}.hx\:px-8{padding-inline:calc(var(--hx-spacing)*8)}.hx\:py-1{padding-block:calc(var(--hx-spacing)*1)}.hx\:py-1\.5{padding-block:calc(var(--hx-spacing)*1.5)}.hx\:py-2{padding-block:calc(var(--hx-spacing)*2)}.hx\:py-2\.5{padding-block:calc(var(--hx-spacing)*2.5)}.hx\:py-3{padding-block:calc(var(--hx-spacing)*3)}.hx\:py-4{padding-block:calc(var(--hx-spacing)*4)}.hx\:py-12{padding-block:calc(var(--hx-spacing)*12)}.hx\:pt-4{padding-top:calc(var(--hx-spacing)*4)}.hx\:pt-6{padding-top:calc(var(--hx-spacing)*6)}.hx\:pt-8{padding-top:calc(var(--hx-spacing)*8)}.hx\:pr-2{padding-right:calc(var(--hx-spacing)*2)}.hx\:pr-4{padding-right:calc(var(--hx-spacing)*4)}.hx\:pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\]{padding-right:calc(env(safe-area-inset-right) - 1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-left),1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-right\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-right),1.5rem)}.hx\:pb-8{padding-bottom:calc(var(--hx-spacing)*8)}.hx\:pb-\[env\(safe-area-inset-bottom\)\]{padding-bottom:env(safe-area-inset-bottom)}.hx\:pb-px{padding-bottom:1px}.hx\:pl-6{padding-left:calc(var(--hx-spacing)*6)}.hx\:pl-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-left:max(env(safe-area-inset-left),1.5rem)}.hx\:text-center{text-align:center}.hx\:text-left{text-align:left}.hx\:align-\[-2\.5px\]{vertical-align:-2.5px}.hx\:align-baseline{vertical-align:baseline}.hx\:align-middle{vertical-align:middle}.hx\:align-text-bottom{vertical-align:text-bottom}.hx\:font-mono{font-family:var(--hx-font-mono)}.hx\:text-2xl{font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height))}.hx\:text-4xl{font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height))}.hx\:text-base{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height))}.hx\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}.hx\:text-xs{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height))}.hx\:text-\[\.65rem\]{font-size:.65rem}.hx\:text-\[10px\]{font-size:10px}.hx\:leading-5{--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5)}.hx\:leading-6{--tw-leading:calc(var(--hx-spacing)*6);line-height:calc(var(--hx-spacing)*6)}.hx\:leading-7{--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hx\:leading-none{--tw-leading:1;line-height:1}.hx\:leading-tight{--tw-leading:var(--hx-leading-tight);line-height:var(--hx-leading-tight)}.hx\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:font-extrabold{--tw-font-weight:var(--hx-font-weight-extrabold);font-weight:var(--hx-font-weight-extrabold)}.hx\:font-medium{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:font-normal{--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal)}.hx\:font-semibold{--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hx\:tracking-tight{--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight)}.hx\:tracking-tighter{--tw-tracking:var(--hx-tracking-tighter);letter-spacing:var(--hx-tracking-tighter)}.hx\:break-words,.hx\:wrap-break-word{overflow-wrap:break-word}.hx\:text-ellipsis{text-overflow:ellipsis}.hx\:whitespace-nowrap{white-space:nowrap}.hx\:text-\[color\:hsl\(var\(--primary-hue\)\,100\%\,50\%\)\]{color:hsl(var(--primary-hue),100%,50%)}.hx\:text-amber-900{color:var(--hx-color-amber-900)}.hx\:text-blue-900{color:var(--hx-color-blue-900)}.hx\:text-current{color:currentColor}.hx\:text-gray-100{color:var(--hx-color-gray-100)}.hx\:text-gray-500{color:var(--hx-color-gray-500)}.hx\:text-gray-600{color:var(--hx-color-gray-600)}.hx\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:text-green-900{color:var(--hx-color-green-900)}.hx\:text-indigo-900{color:var(--hx-color-indigo-900)}.hx\:text-orange-800{color:var(--hx-color-orange-800)}.hx\:text-primary-800{color:var(--hx-color-primary-800)}.hx\:text-purple-900{color:var(--hx-color-purple-900)}.hx\:text-red-900{color:var(--hx-color-red-900)}.hx\:text-slate-50{color:var(--hx-color-slate-50)}.hx\:text-slate-900{color:var(--hx-color-slate-900)}.hx\:text-transparent{color:#0000}.hx\:text-white{color:var(--hx-color-white)}.hx\:text-yellow-900{color:var(--hx-color-yellow-900)}.hx\:capitalize{text-transform:capitalize}.hx\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.hx\:no-underline{text-decoration-line:none}.hx\:underline{text-decoration-line:underline}.hx\:decoration-from-font{text-decoration-thickness:from-font}.hx\:underline-offset-2{text-underline-offset:2px}.hx\:opacity-0{opacity:0}.hx\:opacity-50{opacity:.5}.hx\:opacity-80{opacity:.8}.hx\:shadow-\[0_-12px_16px_\#fff\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_-12px_16px_white\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,white);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_2px_4px_rgba\(0\,0\,0\,\.02\)\,0_1px_0_rgba\(0\,0\,0\,\.06\)\]{--tw-shadow:0 2px 4px var(--tw-shadow-color,#00000005),0 1px 0 var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hextra-focus-visible:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}.hx\:hextra-focus-visible:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}.hx\:hextra-focus-visible-inset:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}.hx\:hextra-focus-visible-inset:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800)}.hx\:shadow-gray-100{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:shadow-gray-100{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}.hx\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:duration-75{--tw-duration:75ms;transition-duration:75ms}.hx\:duration-150{--tw-duration:.15s;transition-duration:.15s}.hx\:duration-200{--tw-duration:.2s;transition-duration:.2s}.hx\:ease-in{--tw-ease:var(--hx-ease-in);transition-timing-function:var(--hx-ease-in)}.hx\:ease-in-out{--tw-ease:var(--hx-ease-in-out);transition-timing-function:var(--hx-ease-in-out)}.hx\:outline-none{--tw-outline-style:none;outline-style:none}.hx\:select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hx\:group-hover\:text-gray-900:is(:where(.hx\:group):hover *){color:var(--hx-color-gray-900)}.hx\:group-hover\:underline:is(:where(.hx\:group):hover *){text-decoration-line:underline}.hx\:group-hover\/code\:opacity-100:is(:where(.hx\:group\/code):hover *){opacity:1}}.hx\:group-data-\[active\=true\]\:text-primary-800:is(:where(.hx\:group)[data-active=true] *){color:var(--hx-color-primary-800)}.hx\:group-data-\[theme\=dark\]\:hidden:is(:where(.hx\:group)[data-theme=dark] *),.hx\:group-data-\[theme\=light\]\:hidden:is(:where(.hx\:group)[data-theme=light] *),.hx\:group-data-\[theme\=system\]\:hidden:is(:where(.hx\:group)[data-theme=system] *){display:none}.hx\:group-\[\.copied\]\/copybtn\:block:is(:where(.hx\:group\/copybtn).copied *){display:block}.hx\:group-\[\.copied\]\/copybtn\:hidden:is(:where(.hx\:group\/copybtn).copied *){display:none}.hx\:placeholder\:text-gray-500::placeholder{color:var(--hx-color-gray-500)}.hx\:before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.hx\:before\:absolute:before{content:var(--tw-content);position:absolute}.hx\:before\:inset-0:before{content:var(--tw-content);inset:calc(var(--hx-spacing)*0)}.hx\:before\:inset-y-1:before{content:var(--tw-content);inset-block:calc(var(--hx-spacing)*1)}.hx\:before\:mr-1:before{content:var(--tw-content);margin-right:calc(var(--hx-spacing)*1)}.hx\:before\:inline-block:before{content:var(--tw-content);display:inline-block}.hx\:before\:w-px:before{content:var(--tw-content);width:1px}.hx\:before\:bg-gray-200:before{content:var(--tw-content);background-color:var(--hx-color-gray-200)}.hx\:before\:opacity-25:before{content:var(--tw-content);opacity:.25}.hx\:before\:transition-transform:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:before\:content-\[\"\"\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\'\#\'\]:before{--tw-content:"#";content:var(--tw-content)}.hx\:before\:content-\[\'\'\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.hx\:group-open\:before\:rotate-90:is(:where(.hx\:group):is([open],:popover-open,:open) *):before{content:var(--tw-content);rotate:90deg}.hx\:first\:mt-0:first-child{margin-top:calc(var(--hx-spacing)*0)}.hx\:last-of-type\:mb-0:last-of-type{margin-bottom:calc(var(--hx-spacing)*0)}@media(hover:hover){.hx\:hover\:border-gray-200:hover{border-color:var(--hx-color-gray-200)}.hx\:hover\:border-gray-300:hover{border-color:var(--hx-color-gray-300)}.hx\:hover\:border-gray-400:hover{border-color:var(--hx-color-gray-400)}.hx\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}.hx\:hover\:bg-gray-100:hover{background-color:var(--hx-color-gray-100)}.hx\:hover\:bg-gray-800\/5:hover{background-color:var(--hx-color-gray-800)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:bg-gray-800\/5:hover{background-color:color-mix(in oklab,var(--hx-color-gray-800)5%,transparent)}}.hx\:hover\:bg-primary-700:hover{background-color:var(--hx-color-primary-700)}.hx\:hover\:bg-slate-50:hover{background-color:var(--hx-color-slate-50)}.hx\:hover\:text-black:hover{color:var(--hx-color-black)}.hx\:hover\:text-gray-700:hover{color:var(--hx-color-gray-700)}.hx\:hover\:text-gray-800:hover{color:var(--hx-color-gray-800)}.hx\:hover\:text-gray-900:hover{color:var(--hx-color-gray-900)}.hx\:hover\:text-primary-600:hover{color:var(--hx-color-primary-600)}.hx\:hover\:opacity-60:hover{opacity:.6}.hx\:hover\:opacity-75:hover{opacity:.75}.hx\:hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}}.hx\:focus-visible\:not-sr-only:focus-visible{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.hx\:focus-visible\:fixed:focus-visible{position:fixed}.hx\:focus-visible\:top-2:focus-visible{top:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:left-2:focus-visible{left:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:z-50:focus-visible{z-index:50}.hx\:focus-visible\:rounded-md:focus-visible{border-radius:var(--hx-radius-md)}.hx\:focus-visible\:bg-primary-500:focus-visible{background-color:var(--hx-color-primary-500)}.hx\:focus-visible\:bg-white:focus-visible{background-color:var(--hx-color-white)}.hx\:focus-visible\:px-4:focus-visible{padding-inline:calc(var(--hx-spacing)*4)}.hx\:focus-visible\:py-2:focus-visible{padding-block:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:text-sm:focus-visible{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:focus-visible\:font-medium:focus-visible{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:focus-visible\:text-white:focus-visible{color:var(--hx-color-white)}.hx\:active\:bg-gray-400\/20:active{background-color:var(--hx-color-gray-400)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:bg-gray-400\/20:active{background-color:color-mix(in oklab,var(--hx-color-gray-400)20%,transparent)}}.hx\:active\:opacity-50:active{opacity:.5}.hx\:active\:shadow-sm:active{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:active\:shadow-gray-200:active{--tw-shadow-color:var(--hx-color-gray-200)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:shadow-gray-200:active{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-200)var(--tw-shadow-alpha),transparent)}}.hx\:data-\[state\=closed\]\:hidden[data-state=closed],.hx\:data-\[state\=open\]\:hidden[data-state=open]{display:none}.hx\:data-\[state\=selected\]\:block[data-state=selected]{display:block}.hx\:data-\[state\=selected\]\:border-primary-500[data-state=selected]{border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:text-primary-600[data-state=selected]{color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:contrast-more\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:contrast-more\:border-current{border-color:initial}.hx\:contrast-more\:border-gray-800{border-color:var(--hx-color-gray-800)}.hx\:contrast-more\:border-gray-900{border-color:var(--hx-color-gray-900)}.hx\:contrast-more\:border-neutral-400{border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:border-primary-500{border-color:var(--hx-color-primary-500)}.hx\:contrast-more\:border-transparent{border-color:#0000}.hx\:contrast-more\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:contrast-more\:text-current{color:currentColor}.hx\:contrast-more\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:contrast-more\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:contrast-more\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:contrast-more\:underline{text-decoration-line:underline}.hx\:contrast-more\:shadow-\[0_0_0_1px_\#000\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}}}@media not all and (min-width:80rem){.hx\:max-xl\:hidden{display:none}}@media not all and (min-width:64rem){.hx\:max-lg\:min-h-\[340px\]{min-height:340px}}@media not all and (min-width:48rem){.hx\:max-md\:sticky{position:sticky}.hx\:max-md\:hidden{display:none}.hx\:max-md\:min-h-\[340px\]{min-height:340px}.hx\:max-md\:\[transform\:translate3d\(0\,-100\%\,0\)\]{transform:translateY(-100%)}.hx\:max-md\:\[transform\:translate3d\(0\,0\,0\)\]{transform:translate(0)}}@media not all and (min-width:40rem){.hx\:max-sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}}@media(min-width:40rem){.hx\:sm\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:sm\:left-auto{left:auto}.hx\:sm\:block{display:block}.hx\:sm\:flex{display:flex}.hx\:sm\:w-\[110\%\]{width:110%}.hx\:sm\:flex-row{flex-direction:row}.hx\:sm\:items-center{align-items:center}.hx\:sm\:items-start{align-items:flex-start}.hx\:sm\:justify-between{justify-content:space-between}.hx\:sm\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}@media not all and (min-width:64rem){.hx\:sm\:max-lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@media(min-width:48rem){.hx\:md\:sticky{position:sticky}.hx\:md\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:md\:mr-0{margin-right:calc(var(--hx-spacing)*0)}.hx\:md\:hidden{display:none}.hx\:md\:inline-block{display:inline-block}.hx\:md\:inline-flex{display:inline-flex}.hx\:md\:aspect-\[1\.1\/1\]{aspect-ratio:1.1}.hx\:md\:h-\[calc\(100vh-var\(--navbar-height\)-var\(--menu-height\)\)\]{height:calc(100vh - var(--navbar-height) - var(--menu-height))}.hx\:md\:max-h-\[min\(calc\(100vh-5rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(100vh - 5rem - env(safe-area-inset-bottom)),400px)}.hx\:md\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:md\:shrink-0{flex-shrink:0}.hx\:md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.hx\:md\:justify-start{justify-content:flex-start}.hx\:md\:self-start{align-self:flex-start}.hx\:md\:overflow-auto{overflow:auto}.hx\:md\:px-12{padding-inline:calc(var(--hx-spacing)*12)}.hx\:md\:pt-12{padding-top:calc(var(--hx-spacing)*12)}.hx\:md\:text-3xl{font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height))}.hx\:md\:text-5xl{font-size:var(--hx-text-5xl);line-height:var(--tw-leading,var(--hx-text-5xl--line-height))}.hx\:md\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:md\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}}@media(min-width:64rem){.hx\:lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:80rem){.hx\:xl\:block{display:block}.hx\:xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.hx\:ltr\:right-1\.5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*1.5)}.hx\:ltr\:right-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*2)}.hx\:ltr\:right-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*3)}.hx\:ltr\:-mr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:calc(var(--hx-spacing)*-4)}.hx\:ltr\:mr-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:auto}.hx\:ltr\:ml-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.hx\:ltr\:ml-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:ml-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:auto}.hx\:ltr\:rotate-180:where(:dir(ltr),[dir=ltr],[dir=ltr] *){rotate:180deg}.hx\:ltr\:rounded-l-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:ltr\:rounded-r-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:ltr\:border-l:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);border-left-width:1px}.hx\:ltr\:pr-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*0)}.hx\:ltr\:pr-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*1)}.hx\:ltr\:pr-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*2)}.hx\:ltr\:pr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:ltr\:pr-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:ltr\:pr-9:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*9)}.hx\:ltr\:pl-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:pl-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:ltr\:pl-5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*5)}.hx\:ltr\:pl-6:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*6)}.hx\:ltr\:pl-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:ltr\:pl-12:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*12)}.hx\:ltr\:pl-16:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*16)}.hx\:ltr\:text-right:where(:dir(ltr),[dir=ltr],[dir=ltr] *){text-align:right}.hx\:ltr\:before\:left-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);left:calc(var(--hx-spacing)*0)}@media(min-width:48rem){.hx\:ltr\:md\:left-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:auto}}.hx\:rtl\:left-1\.5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*1.5)}.hx\:rtl\:left-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*2)}.hx\:rtl\:left-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}.hx\:rtl\:mr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto}.hx\:rtl\:-ml-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:calc(var(--hx-spacing)*-4)}.hx\:rtl\:ml-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:auto}.hx\:rtl\:-rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:-180deg}.hx\:rtl\:rotate-270:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:270deg}.hx\:rtl\:rounded-l-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:rtl\:rounded-r-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:rtl\:border-r:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:1px}.hx\:rtl\:pr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:pr-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:rtl\:pr-5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*5)}.hx\:rtl\:pr-6:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*6)}.hx\:rtl\:pr-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:rtl\:pr-12:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*12)}.hx\:rtl\:pr-16:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*16)}.hx\:rtl\:pl-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*0)}.hx\:rtl\:pl-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*1)}.hx\:rtl\:pl-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*2)}.hx\:rtl\:pl-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:rtl\:pl-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:rtl\:pl-9:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*9)}.hx\:rtl\:text-left:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:left}.hx\:rtl\:before\:right-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);right:calc(var(--hx-spacing)*0)}.hx\:rtl\:before\:rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);rotate:180deg}@media(min-width:48rem){.hx\:rtl\:md\:right-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:auto}}.hx\:dark\:block:where(.dark,.dark *){display:block}.hx\:dark\:hidden:where(.dark,.dark *){display:none}.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:var(--hx-color-amber-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-amber-200)30%,transparent)}}.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:var(--hx-color-blue-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-blue-200)30%,transparent)}}.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-gray-100)20%,transparent)}}.hx\:dark\:border-gray-400:where(.dark,.dark *){border-color:var(--hx-color-gray-400)}.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:var(--hx-color-green-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-green-200)30%,transparent)}}.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:var(--hx-color-indigo-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-indigo-200)30%,transparent)}}.hx\:dark\:border-neutral-700:where(.dark,.dark *){border-color:var(--hx-color-neutral-700)}.hx\:dark\:border-neutral-800:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-orange-400)30%,transparent)}}.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:var(--hx-color-purple-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-purple-200)30%,transparent)}}.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:var(--hx-color-red-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-red-200)30%,transparent)}}.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:var(--hx-color-yellow-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-yellow-200)30%,transparent)}}.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:var(--hx-color-amber-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-amber-900)30%,transparent)}}.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:var(--hx-color-blue-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-blue-900)30%,transparent)}}.hx\:dark\:bg-dark:where(.dark,.dark *),.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:var(--hx-color-dark)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)50%,transparent)}}.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:var(--hx-color-gray-50)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-50)10%,transparent)}}.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:var(--hx-color-green-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-green-900)30%,transparent)}}.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:var(--hx-color-indigo-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-indigo-900)30%,transparent)}}.hx\:dark\:bg-neutral-700:where(.dark,.dark *){background-color:var(--hx-color-neutral-700)}.hx\:dark\:bg-neutral-800:where(.dark,.dark *){background-color:var(--hx-color-neutral-800)}.hx\:dark\:bg-neutral-900:where(.dark,.dark *){background-color:var(--hx-color-neutral-900)}.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-orange-400)20%,transparent)}}.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-400)10%,transparent)}}.hx\:dark\:bg-primary-600:where(.dark,.dark *){background-color:var(--hx-color-primary-600)}.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:var(--hx-color-purple-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-purple-900)30%,transparent)}}.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:var(--hx-color-red-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-red-900)30%,transparent)}}.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:var(--hx-color-yellow-700)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-yellow-700)30%,transparent)}}.hx\:dark\:from-gray-100:where(.dark,.dark *){--tw-gradient-from:var(--hx-color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:to-gray-400:where(.dark,.dark *){--tw-gradient-to:var(--hx-color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:text-amber-200:where(.dark,.dark *){color:var(--hx-color-amber-200)}.hx\:dark\:text-blue-200:where(.dark,.dark *){color:var(--hx-color-blue-200)}.hx\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:dark\:text-gray-200:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hx\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:dark\:text-gray-400:where(.dark,.dark *){color:var(--hx-color-gray-400)}.hx\:dark\:text-green-200:where(.dark,.dark *){color:var(--hx-color-green-200)}.hx\:dark\:text-indigo-200:where(.dark,.dark *){color:var(--hx-color-indigo-200)}.hx\:dark\:text-neutral-200:where(.dark,.dark *){color:var(--hx-color-neutral-200)}.hx\:dark\:text-neutral-400:where(.dark,.dark *){color:var(--hx-color-neutral-400)}.hx\:dark\:text-orange-300:where(.dark,.dark *){color:var(--hx-color-orange-300)}.hx\:dark\:text-primary-600:where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:text-purple-200:where(.dark,.dark *){color:var(--hx-color-purple-200)}.hx\:dark\:text-red-200:where(.dark,.dark *){color:var(--hx-color-red-200)}.hx\:dark\:text-slate-100:where(.dark,.dark *){color:var(--hx-color-slate-100)}.hx\:dark\:text-white:where(.dark,.dark *){color:var(--hx-color-white)}.hx\:dark\:text-yellow-200:where(.dark,.dark *){color:var(--hx-color-yellow-200)}.hx\:dark\:opacity-80:where(.dark,.dark *){opacity:.8}.hx\:dark\:shadow-\[0_-1px_0_rgba\(255\,255\,255\,\.1\)_inset\]:where(.dark,.dark *){--tw-shadow:0 -1px 0 var(--tw-shadow-color,#ffffff1a)inset;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-\[0_-12px_16px_\#111\]:where(.dark,.dark *){--tw-shadow:0 -12px 16px var(--tw-shadow-color,#111);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:dark\:group-hover\:text-gray-50:where(.dark,.dark *):is(:where(.hx\:group):hover *){color:var(--hx-color-gray-50)}}.hx\:group-data-\[active\=true\]\:dark\:text-primary-600:is(:where(.hx\:group)[data-active=true] *):where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:placeholder\:text-gray-400:where(.dark,.dark *)::placeholder{color:var(--hx-color-gray-400)}.hx\:dark\:before\:bg-neutral-800:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hx\:dark\:before\:invert:where(.dark,.dark *):before{content:var(--tw-content);--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media(hover:hover){.hx\:dark\:hover\:border-gray-100:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-100)}.hx\:dark\:hover\:border-gray-600:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-600)}.hx\:dark\:hover\:border-neutral-500:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-500)}.hx\:dark\:hover\:border-neutral-700:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:border-neutral-800:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-gray-100)5%,transparent)}}.hx\:dark\:hover\:bg-neutral-700:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:bg-neutral-800:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-neutral-900:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-900)}.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-primary-100)5%,transparent)}}.hx\:dark\:hover\:bg-primary-700:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-700)}.hx\:dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--hx-color-gray-50)}.hx\:dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--hx-color-gray-100)}.hx\:dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--hx-color-gray-200)}.hx\:dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--hx-color-gray-300)}.hx\:dark\:hover\:text-neutral-50:where(.dark,.dark *):hover{color:var(--hx-color-neutral-50)}.hx\:dark\:hover\:text-primary-400:where(.dark,.dark *):hover{color:var(--hx-color-primary-400)}.hx\:dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--hx-color-white)}.hx\:dark\:hover\:shadow-none:where(.dark,.dark *):hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.hx\:dark\:focus-visible\:bg-dark:where(.dark,.dark *):focus-visible{background-color:var(--hx-color-dark)}.hx\:data-\[state\=selected\]\:dark\:border-primary-500[data-state=selected]:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:dark\:text-primary-600[data-state=selected]:where(.dark,.dark *){color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:dark\:border-current:where(.dark,.dark *){border-color:initial}.hx\:contrast-more\:dark\:border-gray-50:where(.dark,.dark *){border-color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:border-primary-500:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:dark\:contrast-more\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:text-current:where(.dark,.dark *){color:currentColor}.hx\:contrast-more\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:contrast-more\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:contrast-more\:dark\:shadow-\[0_0_0_1px_\#fff\]:where(.dark,.dark *){--tw-shadow:0 0 0 1px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:dark\:hover\:border-gray-50:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-50)}}}@media print{.hx\:print\:\[display\:none\],.hx\:print\:hidden{display:none}.hx\:print\:bg-transparent{background-color:#0000}}}html{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--hx-color-white);width:100%}body:where(.dark,.dark *){background-color:var(--hx-color-dark);color:var(--hx-color-gray-100)}:root{--primary-hue:212deg;--primary-saturation:100%;--primary-lightness:50%;--navbar-height:4rem;--hextra-banner-height:2rem;--menu-height:3.75rem}.dark{--primary-hue:204deg;--primary-saturation:100%;--primary-lightness:50%}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*2);font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height));--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-neutral-200)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-neutral-200)70%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){padding-bottom:calc(var(--hx-spacing)*1);font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--hx-color-neutral-400)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)10%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--hx-color-primary-600);text-decoration-line:underline;text-decoration-thickness:from-font}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);border-color:var(--hx-color-gray-300);color:var(--hx-color-gray-700);font-style:italic}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);padding-left:calc(var(--hx-spacing)*6);border-left-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);padding-right:calc(var(--hx-spacing)*6);border-right-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-gray-700);color:var(--hx-color-gray-400)}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){padding-block:calc(var(--hx-spacing)*4);--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*6);border-collapse:collapse;width:100%;padding:calc(var(--hx-spacing)*0);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);display:block;overflow-x:auto}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead{background-color:var(--hx-color-gray-50)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:var(--hx-color-gray-600)}@supports(color:color-mix(in lab,red,red)){.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-600)20%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr{margin:calc(var(--hx-spacing)*0);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:decimal}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:disc}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):has(li input[type=checkbox]){list-style-type:none}.content :where(ul,ol)>li>:where(ul,ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*0)}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:#0000}.content :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-inline:auto;margin-block:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-md)}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption{margin-top:calc(var(--hx-spacing)*2);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-500);display:block}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption:where(.dark,.dark *){color:var(--hx-color-gray-400)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dt{margin-top:calc(var(--hx-spacing)*6);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dd{margin-block:calc(var(--hx-spacing)*2);padding-inline-start:calc(var(--hx-spacing)*6)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*10);border-color:var(--hx-color-gray-200)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):last-child{margin-bottom:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .footnotes{margin-top:calc(var(--hx-spacing)*12);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.content .footnotes hr{border-color:var(--hx-color-gray-200)}.content .footnotes hr:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .subheading-anchor{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.content .subheading-anchor:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.content .subheading-anchor:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}span:target+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor),.content .subheading-anchor:focus-visible{opacity:1}span+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor){text-decoration-line:none!important}.content .subheading-anchor:after{content:var(--tw-content);color:var(--hx-color-gray-300)}.content .subheading-anchor:where(.dark,.dark *):after{content:var(--tw-content);color:var(--hx-color-neutral-700)}.content .subheading-anchor:after{padding-inline:calc(var(--hx-spacing)*1);--tw-content:"#";content:var(--tw-content)}span:target+:is(){color:var(--hx-color-gray-400)}span:target+:is():where(.dark,.dark *){color:var(--hx-color-neutral-500)}article details>summary::-webkit-details-marker{display:none}article details>summary:before{vertical-align:-4px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='hx:h-5 hx:w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E");width:1.2em;height:1.2em;padding:0 .6em}:lang(fa) ol{list-style-type:persian}.highlight .chroma .err{color:#a61717;background-color:#e3d2d2}.highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.highlight .chroma .line{display:flex}.highlight .chroma .k,.highlight .chroma .kc,.highlight .chroma .kd,.highlight .chroma .kn,.highlight .chroma .kp,.highlight .chroma .kr{color:#000;font-weight:700}.highlight .chroma .kt{color:#458;font-weight:700}.highlight .chroma .na{color:teal}.highlight .chroma .nb{color:#0086b3}.highlight .chroma .bp{color:#999}.highlight .chroma .nc{color:#458;font-weight:700}.highlight .chroma .no{color:teal}.highlight .chroma .nd{color:#3c5d5d;font-weight:700}.highlight .chroma .ni{color:purple}.highlight .chroma .ne,.highlight .chroma .nf,.highlight .chroma .nl{color:#900;font-weight:700}.highlight .chroma .nn{color:#555}.highlight .chroma .nt{color:navy}.highlight .chroma .nv,.highlight .chroma .vc,.highlight .chroma .vg,.highlight .chroma .vi{color:teal}.highlight .chroma .s,.highlight .chroma .sa,.highlight .chroma .sb,.highlight .chroma .sc,.highlight .chroma .dl,.highlight .chroma .sd,.highlight .chroma .s2,.highlight .chroma .se,.highlight .chroma .sh,.highlight .chroma .si,.highlight .chroma .sx{color:#d14}.highlight .chroma .sr{color:#009926}.highlight .chroma .s1{color:#d14}.highlight .chroma .ss{color:#990073}.highlight .chroma .m,.highlight .chroma .mb,.highlight .chroma .mf,.highlight .chroma .mh,.highlight .chroma .mi,.highlight .chroma .il,.highlight .chroma .mo{color:#099}.highlight .chroma .o,.highlight .chroma .ow{color:#000;font-weight:700}.highlight .chroma .c,.highlight .chroma .ch,.highlight .chroma .cm,.highlight .chroma .c1{color:#998;font-style:italic}.highlight .chroma .cs,.highlight .chroma .cp,.highlight .chroma .cpf{color:#999;font-style:italic;font-weight:700}.highlight .chroma .gd{color:#000;background-color:#fdd}.highlight .chroma .ge{color:#000;font-style:italic}.highlight .chroma .gr{color:#a00}.highlight .chroma .gh{color:#999}.highlight .chroma .gi{color:#000;background-color:#dfd}.highlight .chroma .go{color:#888}.highlight .chroma .gp{color:#555}.highlight .chroma .gs{font-weight:700}.highlight .chroma .gu{color:#aaa}.highlight .chroma .gt{color:#a00}.highlight .chroma .gl{text-decoration:underline}.highlight .chroma .w{color:#bbb}.dark .highlight .chroma .err{color:#f85149}.dark .highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.dark .highlight .chroma .line{display:flex}.dark .highlight .chroma .k{color:#ff7b72}.dark .highlight .chroma .kc{color:#79c0ff}.dark .highlight .chroma .kd,.dark .highlight .chroma .kn{color:#ff7b72}.dark .highlight .chroma .kp{color:#79c0ff}.dark .highlight .chroma .kr,.dark .highlight .chroma .kt{color:#ff7b72}.dark .highlight .chroma .nc{color:#f0883e;font-weight:700}.dark .highlight .chroma .no{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nd{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .ni{color:#ffa657}.dark .highlight .chroma .ne{color:#f0883e;font-weight:700}.dark .highlight .chroma .nf{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .nl{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nn{color:#ff7b72}.dark .highlight .chroma .py{color:#79c0ff}.dark .highlight .chroma .nt{color:#7ee787}.dark .highlight .chroma .nv{color:#79c0ff}.dark .highlight .chroma .l{color:#a5d6ff}.dark .highlight .chroma .ld{color:#79c0ff}.dark .highlight .chroma .s{color:#a5d6ff}.dark .highlight .chroma .sa{color:#79c0ff}.dark .highlight .chroma .sb,.dark .highlight .chroma .sc{color:#a5d6ff}.dark .highlight .chroma .dl{color:#79c0ff}.dark .highlight .chroma .sd,.dark .highlight .chroma .s2{color:#a5d6ff}.dark .highlight .chroma .se,.dark .highlight .chroma .sh{color:#79c0ff}.dark .highlight .chroma .si,.dark .highlight .chroma .sx{color:#a5d6ff}.dark .highlight .chroma .sr{color:#79c0ff}.dark .highlight .chroma .s1,.dark .highlight .chroma .ss,.dark .highlight .chroma .m,.dark .highlight .chroma .mb,.dark .highlight .chroma .mf,.dark .highlight .chroma .mh,.dark .highlight .chroma .mi,.dark .highlight .chroma .il,.dark .highlight .chroma .mo{color:#a5d6ff}.dark .highlight .chroma .o,.dark .highlight .chroma .ow{color:#ff7b72;font-weight:700}.dark .highlight .chroma .c,.dark .highlight .chroma .ch,.dark .highlight .chroma .cm,.dark .highlight .chroma .c1{color:#8b949e;font-style:italic}.dark .highlight .chroma .cs,.dark .highlight .chroma .cp,.dark .highlight .chroma .cpf{color:#8b949e;font-style:italic;font-weight:700}.dark .highlight .chroma .gd{color:#ffa198;background-color:#490202}.dark .highlight .chroma .ge{color:inherit;font-style:italic}.dark .highlight .chroma .gr{color:#ffa198}.dark .highlight .chroma .gh{color:#79c0ff;font-weight:700}.dark .highlight .chroma .gi{color:#56d364;background-color:#0f5323}.dark .highlight .chroma .go,.dark .highlight .chroma .gp{color:#8b949e}.dark .highlight .chroma .gs{font-weight:700}.dark .highlight .chroma .gu{color:#79c0ff}.dark .highlight .chroma .gt{color:#ff7b72}.dark .highlight .chroma .gl{text-decoration:underline}.dark .highlight .chroma .w{color:#6e7681}.hextra-code-block{--tw-leading:calc(var(--hx-spacing)*5);font-size:.9em;line-height:calc(var(--hx-spacing)*5)}.hextra-code-block pre{background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block pre{--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.hextra-code-block pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.hextra-code-block pre{--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.hextra-code-block pre:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.hextra-code-block pre:where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.hextra-code-block .hextra-code-filename{top:calc(var(--hx-spacing)*0);z-index:1;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:var(--hx-radius-xl);border-top-right-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);width:100%;position:absolute;overflow:hidden}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block .hextra-code-filename{padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*2);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));color:var(--hx-color-gray-700)}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hextra-code-block .hextra-code-filename+pre:not(.lntable pre){padding-top:calc(var(--hx-spacing)*12)}.hextra-code-block pre:not(.lntable pre){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*4)}.hextra-code-block div:nth-of-type(2) pre{padding-top:calc(var(--hx-spacing)*12);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .lntable{margin:calc(var(--hx-spacing)*0);border-radius:var(--hx-radius-xl);width:auto;display:block;overflow:auto}.chroma .lntable pre{padding-top:calc(var(--hx-spacing)*4);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line){min-width:2.6rem;padding-right:calc(var(--hx-spacing)*4);padding-left:calc(var(--hx-spacing)*4);color:var(--hx-color-neutral-600)}:is(.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line)):where(.dark,.dark *){color:var(--hx-color-neutral-300)}.chroma .lntd{padding:calc(var(--hx-spacing)*0);vertical-align:top}.chroma .lntd:last-of-type{width:100%}.chroma .hl{background-color:var(--hx-color-primary-800);width:100%;display:block}@supports(color:color-mix(in lab,red,red)){.chroma .hl{background-color:color-mix(in oklab,var(--hx-color-primary-800)10%,transparent)}}.hextra-cards{grid-template-columns:repeat(auto-fill,minmax(max(250px,calc((100% - 1rem*2)/var(--hextra-cards-grid-cols))),1fr))}.hextra-card{position:relative}.hextra-card img{-webkit-user-select:none;user-select:none}.hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card .hextra-card-icon svg{color:#0003;width:1.5rem;transition:color .3s}.hextra-card p{margin-top:.5rem;position:relative}.dark .hextra-card .hextra-card-icon svg{color:#fff6}.dark .hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card-tag{z-index:10;position:absolute;top:5px}.hextra-card-tag:where(:dir(ltr)){right:5px}.hextra-card-tag:where(:dir(rtl)){left:5px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker){counter-increment:step}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);margin-left:-41px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);margin-right:-44px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);background-color:var(--hx-color-gray-100)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:4px;border-color:var(--hx-color-white)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);border-color:var(--hx-color-dark)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step);text-align:center;text-indent:-1px;width:33px;height:33px;font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal);color:var(--hx-color-neutral-400);border-radius:340282e33px;position:absolute}:lang(fa) .hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step,persian)}.hextra-search-wrapper li{margin-inline:calc(var(--hx-spacing)*2.5);border-radius:var(--hx-radius-md);overflow-wrap:break-word;color:var(--hx-color-gray-800)}@media(prefers-contrast:more){.hextra-search-wrapper li{border-style:var(--tw-border-style);border-width:1px;border-color:#0000}}.hextra-search-wrapper li:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hextra-search-wrapper li a{scroll-margin:calc(var(--hx-spacing)*12);padding-inline:calc(var(--hx-spacing)*2.5);padding-block:calc(var(--hx-spacing)*2);display:block}.hextra-search-wrapper li a:focus-visible{--tw-outline-style:none;outline-style:none}.hextra-search-wrapper li .hextra-search-title{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hextra-search-wrapper li .hextra-search-active{border-radius:var(--hx-radius-md);background-color:var(--hx-color-primary-500)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper li .hextra-search-active{background-color:color-mix(in oklab,var(--hx-color-primary-500)10%,transparent)}}@media(prefers-contrast:more){.hextra-search-wrapper li .hextra-search-active{border-color:var(--hx-color-primary-500)}}.hextra-search-wrapper .hextra-search-no-result{padding:calc(var(--hx-spacing)*8);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-400);-webkit-user-select:none;user-select:none;display:block}.hextra-search-wrapper .hextra-search-prefix{margin-inline:calc(var(--hx-spacing)*2.5);margin-top:calc(var(--hx-spacing)*6);margin-bottom:calc(var(--hx-spacing)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix{border-color:color-mix(in oklab,var(--hx-color-black)10%,transparent)}}.hextra-search-wrapper .hextra-search-prefix{padding-inline:calc(var(--hx-spacing)*2.5);padding-bottom:calc(var(--hx-spacing)*1.5);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);color:var(--hx-color-gray-500);text-transform:uppercase;-webkit-user-select:none;user-select:none}.hextra-search-wrapper .hextra-search-prefix:first-child{margin-top:calc(var(--hx-spacing)*0)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix{border-color:var(--hx-color-gray-600);color:var(--hx-color-gray-900)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)20%,transparent)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){color:var(--hx-color-gray-300)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-gray-50);color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{margin-top:calc(var(--hx-spacing)*1);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:1.35rem;text-overflow:ellipsis;color:var(--hx-color-gray-600);line-height:1.35rem;overflow:hidden}.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-400)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box}.hextra-search-wrapper .hextra-search-match{color:var(--hx-color-primary-600)}@media(max-width:48rem){.hextra-sidebar-container{top:calc(var(--hx-spacing)*0);bottom:calc(var(--hx-spacing)*0);z-index:15;overscroll-behavior:contain;background-color:var(--hx-color-white);width:100%;padding-top:calc(var(--navbar-height) + var(--hextra-banner-height));position:fixed}.hextra-sidebar-container:where(.dark,.dark *){background-color:var(--hx-color-dark)}.hextra-sidebar-container{will-change:transform,opacity;contain:layout style;backface-visibility:hidden;transition:transform .4s cubic-bezier(.52,.16,.04,1)}}.hextra-sidebar-container li>.hextra-sidebar-children{height:calc(var(--hx-spacing)*0)}.hextra-sidebar-container li.open>.hextra-sidebar-children{height:auto;padding-top:calc(var(--hx-spacing)*1)}.hextra-sidebar-container li.open>.hextra-sidebar-item>.hextra-sidebar-collapsible-button>svg>path{rotate:90deg}.hextra-banner-hidden .hextra-banner{display:none}.hextra-banner :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:underline;text-decoration-thickness:from-font}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}nav .hextra-search-wrapper{display:none}@media(min-width:48rem){nav .hextra-search-wrapper{display:inline-block}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-nav-container-blur{background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur{background-color:color-mix(in oklab,var(--hx-color-white)85%,transparent)}}.hextra-nav-container-blur{--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-nav-container-blur:where(.dark,.dark *){background-color:var(--hx-color-dark)!important}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)80%,transparent)!important}}}.hextra-hamburger-menu svg g{transform-origin:50%;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg path{opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open path{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:0s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open g{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open>path{opacity:0}.hextra-hamburger-menu svg.open>g:first-of-type{rotate:45deg}.hextra-hamburger-menu svg.open>g:first-of-type path{--tw-translate-y:calc(var(--hx-spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-hamburger-menu svg.open>g:nth-of-type(2){rotate:-45deg}.hextra-hamburger-menu svg.open>g:nth-of-type(2) path{--tw-translate-y:calc(var(--hx-spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-scrollbar,.hextra-scrollbar *{scrollbar-width:thin;scrollbar-color:oklch(55.55% 0 0/.4)transparent;scrollbar-gutter:stable}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar{height:calc(var(--hx-spacing)*3);width:calc(var(--hx-spacing)*3)}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-track{background-color:#0000}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-thumb{border-radius:10px}:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{background-color:var(--tw-shadow-color);--tw-shadow-color:var(--hx-color-neutral-500);background-clip:content-box;border:3px solid #0000}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}@media(hover:hover){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:var(--hx-color-neutral-500)}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)40%,transparent)var(--tw-shadow-alpha),transparent)}}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-code-copy-btn{opacity:.85;--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-code-copy-btn:where(.dark,.dark *){opacity:.8}}@media(min-width:1024px){.hextra-feature-grid{grid-template-columns:repeat(var(--hextra-feature-grid-cols),minmax(0,1fr))}}.hextra-jupyter-code-cell{scrollbar-gutter:auto;margin-top:calc(var(--hx-spacing)*6)}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:hidden}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs{max-height:50vh;overflow:auto}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs pre{max-width:100%;font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:auto}.hextra-badge{align-items:center;display:inline-flex}.hextra-toc a.hextra-toc-active{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.2s;transition-duration:.2s;color:var(--hx-color-gray-900)!important}.hextra-toc a.hextra-toc-active:where(.dark,.dark *){color:var(--hx-color-gray-50)!important}.hextra-archive-timeline{border-left-style:var(--tw-border-style);border-left-width:2px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline{border-color:color-mix(in oklab,var(--hx-color-black)15%,transparent)}}.hextra-archive-timeline:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)15%,transparent)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}:root{--hextra-navbar-height:80px}.hextra-nav-container nav{min-height:80px!important;height:80px!important} \ No newline at end of file diff --git a/public/css/compiled/main.min.e3e32e1e64b51d8cf07eddfd28385006113c25a60d1a7976dff2c8feaf1d6c2e.css b/public/css/compiled/main.min.e3e32e1e64b51d8cf07eddfd28385006113c25a60d1a7976dff2c8feaf1d6c2e.css new file mode 100644 index 0000000..d632f4a --- /dev/null +++ b/public/css/compiled/main.min.e3e32e1e64b51d8cf07eddfd28385006113c25a60d1a7976dff2c8feaf1d6c2e.css @@ -0,0 +1 @@ +:root{--hextra-max-page-width:80rem;--hextra-max-content-width:72rem;--hextra-max-navbar-width:80rem;--hextra-max-footer-width:80rem}.hextra-max-page-width{max-width:var(--hextra-max-page-width)}.hextra-max-content-width{max-width:var(--hextra-max-content-width)}.hextra-max-navbar-width{max-width:var(--hextra-max-navbar-width)}.hextra-max-footer-width{max-width:var(--hextra-max-footer-width)}/*!tailwindcss v4.1.18 | MIT License | https://tailwindcss.com*/@layer properties{@supports(((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--hx-font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--hx-font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--hx-color-red-100:oklch(93.6% .032 17.717);--hx-color-red-200:oklch(88.5% .062 18.334);--hx-color-red-900:oklch(39.6% .141 25.723);--hx-color-orange-50:oklch(98% .016 73.684);--hx-color-orange-100:oklch(95.4% .038 75.164);--hx-color-orange-300:oklch(83.7% .128 66.29);--hx-color-orange-400:oklch(75% .183 55.934);--hx-color-orange-800:oklch(47% .157 37.304);--hx-color-amber-100:oklch(96.2% .059 95.617);--hx-color-amber-200:oklch(92.4% .12 95.746);--hx-color-amber-900:oklch(41.4% .112 45.904);--hx-color-yellow-50:oklch(98.7% .026 102.212);--hx-color-yellow-100:oklch(97.3% .071 103.193);--hx-color-yellow-200:oklch(94.5% .129 101.54);--hx-color-yellow-700:oklch(55.4% .135 66.442);--hx-color-yellow-900:oklch(42.1% .095 57.708);--hx-color-green-100:oklch(96.2% .044 156.743);--hx-color-green-200:oklch(92.5% .084 155.995);--hx-color-green-900:oklch(39.3% .095 152.535);--hx-color-blue-100:oklch(93.2% .032 255.585);--hx-color-blue-200:oklch(88.2% .059 254.128);--hx-color-blue-900:oklch(37.9% .146 265.522);--hx-color-indigo-100:oklch(93% .034 272.788);--hx-color-indigo-200:oklch(87% .065 274.039);--hx-color-indigo-900:oklch(35.9% .144 278.697);--hx-color-purple-100:oklch(94.6% .033 307.174);--hx-color-purple-200:oklch(90.2% .063 306.703);--hx-color-purple-900:oklch(38.1% .176 304.987);--hx-color-slate-50:oklch(98.4% .003 247.858);--hx-color-slate-100:oklch(96.8% .007 247.896);--hx-color-slate-900:oklch(20.8% .042 265.755);--hx-color-gray-50:oklch(98.5% .002 247.839);--hx-color-gray-100:oklch(96.7% .003 264.542);--hx-color-gray-200:oklch(92.8% .006 264.531);--hx-color-gray-300:oklch(87.2% .01 258.338);--hx-color-gray-400:oklch(70.7% .022 261.325);--hx-color-gray-500:oklch(55.1% .027 264.364);--hx-color-gray-600:oklch(44.6% .03 256.802);--hx-color-gray-700:oklch(37.3% .034 259.733);--hx-color-gray-800:oklch(27.8% .033 256.848);--hx-color-gray-900:oklch(21% .034 264.665);--hx-color-neutral-50:oklch(98.5% 0 0);--hx-color-neutral-200:oklch(92.2% 0 0);--hx-color-neutral-300:oklch(87% 0 0);--hx-color-neutral-400:oklch(70.8% 0 0);--hx-color-neutral-500:oklch(55.6% 0 0);--hx-color-neutral-600:oklch(43.9% 0 0);--hx-color-neutral-700:oklch(37.1% 0 0);--hx-color-neutral-800:oklch(26.9% 0 0);--hx-color-neutral-900:oklch(20.5% 0 0);--hx-color-black:#000;--hx-color-white:#fff;--hx-spacing:.25rem;--hx-breakpoint-xl:80rem;--hx-text-xs:.75rem;--hx-text-xs--line-height:calc(1/.75);--hx-text-sm:.875rem;--hx-text-sm--line-height:calc(1.25/.875);--hx-text-base:1rem;--hx-text-base--line-height:calc(1.5/1);--hx-text-lg:1.125rem;--hx-text-lg--line-height:calc(1.75/1.125);--hx-text-xl:1.25rem;--hx-text-xl--line-height:calc(1.75/1.25);--hx-text-2xl:1.5rem;--hx-text-2xl--line-height:calc(2/1.5);--hx-text-3xl:1.875rem;--hx-text-3xl--line-height:calc(2.25/1.875);--hx-text-4xl:2.25rem;--hx-text-4xl--line-height:calc(2.5/2.25);--hx-text-5xl:3rem;--hx-text-5xl--line-height:1;--hx-font-weight-normal:400;--hx-font-weight-medium:500;--hx-font-weight-semibold:600;--hx-font-weight-bold:700;--hx-font-weight-extrabold:800;--hx-tracking-tighter:-.05em;--hx-tracking-tight:-.025em;--hx-leading-tight:1.25;--hx-radius-xs:.125rem;--hx-radius-sm:.25rem;--hx-radius-md:.375rem;--hx-radius-lg:.5rem;--hx-radius-xl:.75rem;--hx-radius-3xl:1.5rem;--hx-ease-in:cubic-bezier(.4,0,1,1);--hx-ease-out:cubic-bezier(0,0,.2,1);--hx-ease-in-out:cubic-bezier(.4,0,.2,1);--hx-blur-md:12px;--hx-default-transition-duration:.15s;--hx-default-transition-timing-function:cubic-bezier(.4,0,.2,1);--hx-default-font-family:var(--hx-font-sans);--hx-default-mono-font-family:var(--hx-font-mono);--hx-color-primary-100:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*44));--hx-color-primary-200:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*36));--hx-color-primary-300:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*27));--hx-color-primary-400:hsl(var(--primary-hue)var(--primary-saturation)calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness))/50)*16));--hx-color-primary-500:hsl(var(--primary-hue)var(--primary-saturation)var(--primary-lightness));--hx-color-primary-600:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*45));--hx-color-primary-700:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*39));--hx-color-primary-800:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*32));--hx-color-primary-900:hsl(var(--primary-hue)var(--primary-saturation)calc(calc(var(--primary-lightness)/50)*24));--hx-color-dark:#111}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--hx-default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--hx-default-font-feature-settings,normal);font-variation-settings:var(--hx-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--hx-default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--hx-default-mono-font-feature-settings,normal);font-variation-settings:var(--hx-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports(not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports(color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}abbr:where([title]){cursor:help}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}:where(a,button,[role=tab],[role=menuitem],[role=menuitemradio],input,select,textarea,[tabindex="0"]):not([class*=hextra-focus-visible]):focus-visible:where(.dark,.dark *){--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}}@layer components;@layer utilities{.hx\:pointer-events-none{pointer-events:none}.hx\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hx\:absolute{position:absolute}.hx\:relative{position:relative}.hx\:sticky{position:sticky}.hx\:inset-x-0{inset-inline:calc(var(--hx-spacing)*0)}.hx\:inset-y-0{inset-block:calc(var(--hx-spacing)*0)}.hx\:top-0{top:calc(var(--hx-spacing)*0)}.hx\:top-1\/2{top:50%}.hx\:top-8{top:calc(var(--hx-spacing)*8)}.hx\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:top-\[40\%\]{top:40%}.hx\:top-full{top:100%}.hx\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:bottom-0{bottom:calc(var(--hx-spacing)*0)}.hx\:left-0{left:calc(var(--hx-spacing)*0)}.hx\:left-\[24px\]{left:24px}.hx\:left-\[36px\]{left:36px}.hx\:z-20{z-index:20}.hx\:z-\[-1\]{z-index:-1}.hx\:order-last{order:9999}.hx\:m-\[11px\]{margin:11px}.hx\:mx-1{margin-inline:calc(var(--hx-spacing)*1)}.hx\:mx-4{margin-inline:calc(var(--hx-spacing)*4)}.hx\:mx-auto{margin-inline:auto}.hx\:my-1{margin-block:calc(var(--hx-spacing)*1)}.hx\:my-1\.5{margin-block:calc(var(--hx-spacing)*1.5)}.hx\:my-2{margin-block:calc(var(--hx-spacing)*2)}.hx\:-mt-20{margin-top:calc(var(--hx-spacing)*-20)}.hx\:mt-1{margin-top:calc(var(--hx-spacing)*1)}.hx\:mt-1\.5{margin-top:calc(var(--hx-spacing)*1.5)}.hx\:mt-2{margin-top:calc(var(--hx-spacing)*2)}.hx\:mt-4{margin-top:calc(var(--hx-spacing)*4)}.hx\:mt-5{margin-top:calc(var(--hx-spacing)*5)}.hx\:mt-6{margin-top:calc(var(--hx-spacing)*6)}.hx\:mt-8{margin-top:calc(var(--hx-spacing)*8)}.hx\:mt-12{margin-top:calc(var(--hx-spacing)*12)}.hx\:mt-16{margin-top:calc(var(--hx-spacing)*16)}.hx\:mt-auto{margin-top:auto}.hx\:-mr-2{margin-right:calc(var(--hx-spacing)*-2)}.hx\:mr-1{margin-right:calc(var(--hx-spacing)*1)}.hx\:mr-2{margin-right:calc(var(--hx-spacing)*2)}.hx\:-mb-0\.5{margin-bottom:calc(var(--hx-spacing)*-.5)}.hx\:mb-0{margin-bottom:calc(var(--hx-spacing)*0)}.hx\:mb-2{margin-bottom:calc(var(--hx-spacing)*2)}.hx\:mb-4{margin-bottom:calc(var(--hx-spacing)*4)}.hx\:mb-6{margin-bottom:calc(var(--hx-spacing)*6)}.hx\:mb-8{margin-bottom:calc(var(--hx-spacing)*8)}.hx\:mb-10{margin-bottom:calc(var(--hx-spacing)*10)}.hx\:mb-12{margin-bottom:calc(var(--hx-spacing)*12)}.hx\:mb-16{margin-bottom:calc(var(--hx-spacing)*16)}.hx\:-ml-2{margin-left:calc(var(--hx-spacing)*-2)}.hx\:ml-2{margin-left:calc(var(--hx-spacing)*2)}.hx\:ml-4{margin-left:calc(var(--hx-spacing)*4)}.hx\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.hx\:block{display:block}.hx\:flex{display:flex}.hx\:grid{display:grid}.hx\:hidden{display:none}.hx\:inline{display:inline}.hx\:inline-block{display:inline-block}.hx\:inline-flex{display:inline-flex}.hx\:aspect-auto{aspect-ratio:auto}.hx\:size-4{width:calc(var(--hx-spacing)*4);height:calc(var(--hx-spacing)*4)}.hx\:h-0{height:calc(var(--hx-spacing)*0)}.hx\:h-2{height:calc(var(--hx-spacing)*2)}.hx\:h-3\.5{height:calc(var(--hx-spacing)*3.5)}.hx\:h-4{height:calc(var(--hx-spacing)*4)}.hx\:h-5{height:calc(var(--hx-spacing)*5)}.hx\:h-7{height:calc(var(--hx-spacing)*7)}.hx\:h-10{height:calc(var(--hx-spacing)*10)}.hx\:h-16{height:calc(var(--hx-spacing)*16)}.hx\:h-\[18px\]{height:18px}.hx\:h-full{height:100%}.hx\:h-px{height:1px}.hx\:max-h-\(--menu-height\){max-height:var(--menu-height)}.hx\:max-h-64{max-height:calc(var(--hx-spacing)*64)}.hx\:max-h-\[calc\(100vh-var\(--navbar-height\)-env\(safe-area-inset-bottom\)\)\]{max-height:calc(100vh - var(--navbar-height) - env(safe-area-inset-bottom))}.hx\:max-h-\[min\(calc\(50vh-11rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(50vh - 11rem - env(safe-area-inset-bottom)),400px)}.hx\:min-h-\[100px\]{min-height:100px}.hx\:min-h-\[calc\(100vh-var\(--navbar-height\)\)\]{min-height:calc(100vh - var(--navbar-height))}.hx\:w-2{width:calc(var(--hx-spacing)*2)}.hx\:w-3\.5{width:calc(var(--hx-spacing)*3.5)}.hx\:w-4{width:calc(var(--hx-spacing)*4)}.hx\:w-10{width:calc(var(--hx-spacing)*10)}.hx\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:w-\[110\%\]{width:110%}.hx\:w-\[180\%\]{width:180%}.hx\:w-full{width:100%}.hx\:w-max{width:max-content}.hx\:w-screen{width:100vw}.hx\:max-w-\[50\%\]{max-width:50%}.hx\:max-w-\[90rem\]{max-width:90rem}.hx\:max-w-\[min\(calc\(100vw-2rem\)\,calc\(100\%\+20rem\)\)\]{max-width:min(100vw - 2rem,100% + 20rem)}.hx\:max-w-full{max-width:100%}.hx\:max-w-none{max-width:none}.hx\:max-w-screen-xl{max-width:var(--hx-breakpoint-xl)}.hx\:min-w-0{min-width:calc(var(--hx-spacing)*0)}.hx\:min-w-\[18px\]{min-width:18px}.hx\:min-w-\[24px\]{min-width:24px}.hx\:min-w-full{min-width:100%}.hx\:shrink-0{flex-shrink:0}.hx\:grow{flex-grow:1}.hx\:origin-center{transform-origin:50%}.hx\:-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hx\:cursor-default{cursor:default}.hx\:cursor-pointer{cursor:pointer}.hx\:scroll-my-6{scroll-margin-block:calc(var(--hx-spacing)*6)}.hx\:scroll-py-6{scroll-padding-block:calc(var(--hx-spacing)*6)}.hx\:list-none{list-style-type:none}.hx\:appearance-none{appearance:none}.hx\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.hx\:flex-col{flex-direction:column}.hx\:flex-wrap{flex-wrap:wrap}.hx\:items-baseline{align-items:baseline}.hx\:items-center{align-items:center}.hx\:items-start{align-items:flex-start}.hx\:justify-between{justify-content:space-between}.hx\:justify-center{justify-content:center}.hx\:justify-end{justify-content:flex-end}.hx\:justify-start{justify-content:flex-start}.hx\:justify-items-start{justify-items:start}.hx\:gap-1{gap:calc(var(--hx-spacing)*1)}.hx\:gap-1\.5{gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-2{gap:calc(var(--hx-spacing)*2)}.hx\:gap-4{gap:calc(var(--hx-spacing)*4)}.hx\:gap-x-1\.5{column-gap:calc(var(--hx-spacing)*1.5)}.hx\:gap-x-2{column-gap:calc(var(--hx-spacing)*2)}.hx\:gap-y-1{row-gap:calc(var(--hx-spacing)*1)}.hx\:gap-y-2{row-gap:calc(var(--hx-spacing)*2)}.hx\:overflow-auto{overflow:auto}.hx\:overflow-hidden{overflow:hidden}.hx\:overflow-x-auto{overflow-x:auto}.hx\:overflow-x-hidden{overflow-x:hidden}.hx\:overflow-y-auto{overflow-y:auto}.hx\:overflow-y-hidden{overflow-y:hidden}.hx\:overscroll-contain{overscroll-behavior:contain}.hx\:overscroll-x-contain{overscroll-behavior-x:contain}.hx\:rounded-3xl{border-radius:var(--hx-radius-3xl)}.hx\:rounded-full{border-radius:340282e33px}.hx\:rounded-lg{border-radius:var(--hx-radius-lg)}.hx\:rounded-md{border-radius:var(--hx-radius-md)}.hx\:rounded-sm{border-radius:var(--hx-radius-sm)}.hx\:rounded-xl{border-radius:var(--hx-radius-xl)}.hx\:rounded-xs{border-radius:var(--hx-radius-xs)}.hx\:rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.hx\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:border-0{border-style:var(--tw-border-style);border-width:0}.hx\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.hx\:border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.hx\:border-amber-200{border-color:var(--hx-color-amber-200)}.hx\:border-black\/5{border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:border-black\/5{border-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:border-blue-200{border-color:var(--hx-color-blue-200)}.hx\:border-gray-200{border-color:var(--hx-color-gray-200)}.hx\:border-gray-500{border-color:var(--hx-color-gray-500)}.hx\:border-green-200{border-color:var(--hx-color-green-200)}.hx\:border-indigo-200{border-color:var(--hx-color-indigo-200)}.hx\:border-orange-100{border-color:var(--hx-color-orange-100)}.hx\:border-purple-200{border-color:var(--hx-color-purple-200)}.hx\:border-red-200{border-color:var(--hx-color-red-200)}.hx\:border-transparent{border-color:#0000}.hx\:border-yellow-100{border-color:var(--hx-color-yellow-100)}.hx\:bg-amber-100{background-color:var(--hx-color-amber-100)}.hx\:bg-black\/\[\.05\]{background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-black\/\[\.05\]{background-color:color-mix(in oklab,var(--hx-color-black)5%,transparent)}}.hx\:bg-blue-100{background-color:var(--hx-color-blue-100)}.hx\:bg-gray-100{background-color:var(--hx-color-gray-100)}.hx\:bg-gray-200{background-color:var(--hx-color-gray-200)}.hx\:bg-green-100{background-color:var(--hx-color-green-100)}.hx\:bg-indigo-100{background-color:var(--hx-color-indigo-100)}.hx\:bg-neutral-50{background-color:var(--hx-color-neutral-50)}.hx\:bg-neutral-900{background-color:var(--hx-color-neutral-900)}.hx\:bg-orange-50{background-color:var(--hx-color-orange-50)}.hx\:bg-primary-100{background-color:var(--hx-color-primary-100)}.hx\:bg-primary-400{background-color:var(--hx-color-primary-400)}.hx\:bg-primary-600{background-color:var(--hx-color-primary-600)}.hx\:bg-primary-700\/5{background-color:var(--hx-color-primary-700)}@supports(color:color-mix(in lab,red,red)){.hx\:bg-primary-700\/5{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hx\:bg-purple-100{background-color:var(--hx-color-purple-100)}.hx\:bg-red-100{background-color:var(--hx-color-red-100)}.hx\:bg-transparent{background-color:#0000}.hx\:bg-white{background-color:var(--hx-color-white)}.hx\:bg-yellow-50{background-color:var(--hx-color-yellow-50)}.hx\:bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.hx\:from-gray-900{--tw-gradient-from:var(--hx-color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:to-gray-600{--tw-gradient-to:var(--hx-color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:bg-clip-text{-webkit-background-clip:text;background-clip:text}.hx\:p-0{padding:calc(var(--hx-spacing)*0)}.hx\:p-0\.5{padding:calc(var(--hx-spacing)*.5)}.hx\:p-1{padding:calc(var(--hx-spacing)*1)}.hx\:p-1\.5{padding:calc(var(--hx-spacing)*1.5)}.hx\:p-2{padding:calc(var(--hx-spacing)*2)}.hx\:p-4{padding:calc(var(--hx-spacing)*4)}.hx\:p-6{padding:calc(var(--hx-spacing)*6)}.hx\:px-1\.5{padding-inline:calc(var(--hx-spacing)*1.5)}.hx\:px-2{padding-inline:calc(var(--hx-spacing)*2)}.hx\:px-2\.5{padding-inline:calc(var(--hx-spacing)*2.5)}.hx\:px-3{padding-inline:calc(var(--hx-spacing)*3)}.hx\:px-4{padding-inline:calc(var(--hx-spacing)*4)}.hx\:px-6{padding-inline:calc(var(--hx-spacing)*6)}.hx\:px-8{padding-inline:calc(var(--hx-spacing)*8)}.hx\:py-1{padding-block:calc(var(--hx-spacing)*1)}.hx\:py-1\.5{padding-block:calc(var(--hx-spacing)*1.5)}.hx\:py-2{padding-block:calc(var(--hx-spacing)*2)}.hx\:py-2\.5{padding-block:calc(var(--hx-spacing)*2.5)}.hx\:py-3{padding-block:calc(var(--hx-spacing)*3)}.hx\:py-4{padding-block:calc(var(--hx-spacing)*4)}.hx\:py-12{padding-block:calc(var(--hx-spacing)*12)}.hx\:pt-4{padding-top:calc(var(--hx-spacing)*4)}.hx\:pt-6{padding-top:calc(var(--hx-spacing)*6)}.hx\:pt-8{padding-top:calc(var(--hx-spacing)*8)}.hx\:pr-2{padding-right:calc(var(--hx-spacing)*2)}.hx\:pr-4{padding-right:calc(var(--hx-spacing)*4)}.hx\:pr-\[calc\(env\(safe-area-inset-right\)-1\.5rem\)\]{padding-right:calc(env(safe-area-inset-right) - 1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-left),1.5rem)}.hx\:pr-\[max\(env\(safe-area-inset-right\)\,1\.5rem\)\]{padding-right:max(env(safe-area-inset-right),1.5rem)}.hx\:pb-8{padding-bottom:calc(var(--hx-spacing)*8)}.hx\:pb-\[env\(safe-area-inset-bottom\)\]{padding-bottom:env(safe-area-inset-bottom)}.hx\:pb-px{padding-bottom:1px}.hx\:pl-6{padding-left:calc(var(--hx-spacing)*6)}.hx\:pl-\[max\(env\(safe-area-inset-left\)\,1\.5rem\)\]{padding-left:max(env(safe-area-inset-left),1.5rem)}.hx\:text-center{text-align:center}.hx\:text-left{text-align:left}.hx\:align-\[-2\.5px\]{vertical-align:-2.5px}.hx\:align-baseline{vertical-align:baseline}.hx\:align-middle{vertical-align:middle}.hx\:align-text-bottom{vertical-align:text-bottom}.hx\:font-mono{font-family:var(--hx-font-mono)}.hx\:text-2xl{font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height))}.hx\:text-4xl{font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height))}.hx\:text-base{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height))}.hx\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}.hx\:text-xs{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height))}.hx\:text-\[\.65rem\]{font-size:.65rem}.hx\:text-\[10px\]{font-size:10px}.hx\:leading-5{--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5)}.hx\:leading-6{--tw-leading:calc(var(--hx-spacing)*6);line-height:calc(var(--hx-spacing)*6)}.hx\:leading-7{--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hx\:leading-none{--tw-leading:1;line-height:1}.hx\:leading-tight{--tw-leading:var(--hx-leading-tight);line-height:var(--hx-leading-tight)}.hx\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:font-extrabold{--tw-font-weight:var(--hx-font-weight-extrabold);font-weight:var(--hx-font-weight-extrabold)}.hx\:font-medium{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:font-normal{--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal)}.hx\:font-semibold{--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hx\:tracking-tight{--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight)}.hx\:tracking-tighter{--tw-tracking:var(--hx-tracking-tighter);letter-spacing:var(--hx-tracking-tighter)}.hx\:break-words,.hx\:wrap-break-word{overflow-wrap:break-word}.hx\:text-ellipsis{text-overflow:ellipsis}.hx\:whitespace-nowrap{white-space:nowrap}.hx\:text-\[color\:hsl\(var\(--primary-hue\)\,100\%\,50\%\)\]{color:hsl(var(--primary-hue),100%,50%)}.hx\:text-amber-900{color:var(--hx-color-amber-900)}.hx\:text-blue-900{color:var(--hx-color-blue-900)}.hx\:text-current{color:currentColor}.hx\:text-gray-100{color:var(--hx-color-gray-100)}.hx\:text-gray-500{color:var(--hx-color-gray-500)}.hx\:text-gray-600{color:var(--hx-color-gray-600)}.hx\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:text-green-900{color:var(--hx-color-green-900)}.hx\:text-indigo-900{color:var(--hx-color-indigo-900)}.hx\:text-orange-800{color:var(--hx-color-orange-800)}.hx\:text-primary-800{color:var(--hx-color-primary-800)}.hx\:text-purple-900{color:var(--hx-color-purple-900)}.hx\:text-red-900{color:var(--hx-color-red-900)}.hx\:text-slate-50{color:var(--hx-color-slate-50)}.hx\:text-slate-900{color:var(--hx-color-slate-900)}.hx\:text-transparent{color:#0000}.hx\:text-white{color:var(--hx-color-white)}.hx\:text-yellow-900{color:var(--hx-color-yellow-900)}.hx\:capitalize{text-transform:capitalize}.hx\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.hx\:no-underline{text-decoration-line:none}.hx\:underline{text-decoration-line:underline}.hx\:decoration-from-font{text-decoration-thickness:from-font}.hx\:underline-offset-2{text-underline-offset:2px}.hx\:opacity-0{opacity:0}.hx\:opacity-50{opacity:.5}.hx\:opacity-80{opacity:.8}.hx\:shadow-\[0_-12px_16px_\#fff\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_-12px_16px_white\]{--tw-shadow:0 -12px 16px var(--tw-shadow-color,white);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-\[0_2px_4px_rgba\(0\,0\,0\,\.02\)\,0_1px_0_rgba\(0\,0\,0\,\.06\)\]{--tw-shadow:0 2px 4px var(--tw-shadow-color,#00000005),0 1px 0 var(--tw-shadow-color,#0000000f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hextra-focus-visible:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--hx-color-primary-300);--tw-outline-style:none;outline-style:none}.hx\:hextra-focus-visible:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800);--tw-ring-offset-color:var(--hx-color-primary-700)}.hx\:hextra-focus-visible-inset:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--hx-color-primary-200);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}.hx\:hextra-focus-visible-inset:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--hx-color-primary-800)}.hx\:shadow-gray-100{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:shadow-gray-100{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}.hx\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:duration-75{--tw-duration:75ms;transition-duration:75ms}.hx\:duration-150{--tw-duration:.15s;transition-duration:.15s}.hx\:duration-200{--tw-duration:.2s;transition-duration:.2s}.hx\:ease-in{--tw-ease:var(--hx-ease-in);transition-timing-function:var(--hx-ease-in)}.hx\:ease-in-out{--tw-ease:var(--hx-ease-in-out);transition-timing-function:var(--hx-ease-in-out)}.hx\:outline-none{--tw-outline-style:none;outline-style:none}.hx\:select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.hx\:group-hover\:text-gray-900:is(:where(.hx\:group):hover *){color:var(--hx-color-gray-900)}.hx\:group-hover\:underline:is(:where(.hx\:group):hover *){text-decoration-line:underline}.hx\:group-hover\/code\:opacity-100:is(:where(.hx\:group\/code):hover *){opacity:1}}.hx\:group-data-\[active\=true\]\:text-primary-800:is(:where(.hx\:group)[data-active=true] *){color:var(--hx-color-primary-800)}.hx\:group-data-\[theme\=dark\]\:hidden:is(:where(.hx\:group)[data-theme=dark] *),.hx\:group-data-\[theme\=light\]\:hidden:is(:where(.hx\:group)[data-theme=light] *),.hx\:group-data-\[theme\=system\]\:hidden:is(:where(.hx\:group)[data-theme=system] *){display:none}.hx\:group-\[\.copied\]\/copybtn\:block:is(:where(.hx\:group\/copybtn).copied *){display:block}.hx\:group-\[\.copied\]\/copybtn\:hidden:is(:where(.hx\:group\/copybtn).copied *){display:none}.hx\:placeholder\:text-gray-500::placeholder{color:var(--hx-color-gray-500)}.hx\:before\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.hx\:before\:absolute:before{content:var(--tw-content);position:absolute}.hx\:before\:inset-0:before{content:var(--tw-content);inset:calc(var(--hx-spacing)*0)}.hx\:before\:inset-y-1:before{content:var(--tw-content);inset-block:calc(var(--hx-spacing)*1)}.hx\:before\:mr-1:before{content:var(--tw-content);margin-right:calc(var(--hx-spacing)*1)}.hx\:before\:inline-block:before{content:var(--tw-content);display:inline-block}.hx\:before\:w-px:before{content:var(--tw-content);width:1px}.hx\:before\:bg-gray-200:before{content:var(--tw-content);background-color:var(--hx-color-gray-200)}.hx\:before\:opacity-25:before{content:var(--tw-content);opacity:.25}.hx\:before\:transition-transform:before{content:var(--tw-content);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.hx\:before\:content-\[\"\"\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\'\#\'\]:before{--tw-content:"#";content:var(--tw-content)}.hx\:before\:content-\[\'\'\]:before{--tw-content:"";content:var(--tw-content)}.hx\:before\:content-\[\\\"\\\"\]:before{--tw-content:\"\";content:var(--tw-content)}.hx\:group-open\:before\:rotate-90:is(:where(.hx\:group):is([open],:popover-open,:open) *):before{content:var(--tw-content);rotate:90deg}.hx\:first\:mt-0:first-child{margin-top:calc(var(--hx-spacing)*0)}.hx\:last-of-type\:mb-0:last-of-type{margin-bottom:calc(var(--hx-spacing)*0)}@media(hover:hover){.hx\:hover\:border-gray-200:hover{border-color:var(--hx-color-gray-200)}.hx\:hover\:border-gray-300:hover{border-color:var(--hx-color-gray-300)}.hx\:hover\:border-gray-400:hover{border-color:var(--hx-color-gray-400)}.hx\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}.hx\:hover\:bg-gray-100:hover{background-color:var(--hx-color-gray-100)}.hx\:hover\:bg-gray-800\/5:hover{background-color:var(--hx-color-gray-800)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:bg-gray-800\/5:hover{background-color:color-mix(in oklab,var(--hx-color-gray-800)5%,transparent)}}.hx\:hover\:bg-primary-700:hover{background-color:var(--hx-color-primary-700)}.hx\:hover\:bg-slate-50:hover{background-color:var(--hx-color-slate-50)}.hx\:hover\:text-black:hover{color:var(--hx-color-black)}.hx\:hover\:text-gray-700:hover{color:var(--hx-color-gray-700)}.hx\:hover\:text-gray-800:hover{color:var(--hx-color-gray-800)}.hx\:hover\:text-gray-900:hover{color:var(--hx-color-gray-900)}.hx\:hover\:text-primary-600:hover{color:var(--hx-color-primary-600)}.hx\:hover\:opacity-60:hover{opacity:.6}.hx\:hover\:opacity-75:hover{opacity:.75}.hx\:hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:hover\:shadow-gray-100:hover{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-100)var(--tw-shadow-alpha),transparent)}}}.hx\:focus-visible\:not-sr-only:focus-visible{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.hx\:focus-visible\:fixed:focus-visible{position:fixed}.hx\:focus-visible\:top-2:focus-visible{top:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:left-2:focus-visible{left:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:z-50:focus-visible{z-index:50}.hx\:focus-visible\:rounded-md:focus-visible{border-radius:var(--hx-radius-md)}.hx\:focus-visible\:bg-primary-500:focus-visible{background-color:var(--hx-color-primary-500)}.hx\:focus-visible\:bg-white:focus-visible{background-color:var(--hx-color-white)}.hx\:focus-visible\:px-4:focus-visible{padding-inline:calc(var(--hx-spacing)*4)}.hx\:focus-visible\:py-2:focus-visible{padding-block:calc(var(--hx-spacing)*2)}.hx\:focus-visible\:text-sm:focus-visible{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.hx\:focus-visible\:font-medium:focus-visible{--tw-font-weight:var(--hx-font-weight-medium);font-weight:var(--hx-font-weight-medium)}.hx\:focus-visible\:text-white:focus-visible{color:var(--hx-color-white)}.hx\:active\:bg-gray-400\/20:active{background-color:var(--hx-color-gray-400)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:bg-gray-400\/20:active{background-color:color-mix(in oklab,var(--hx-color-gray-400)20%,transparent)}}.hx\:active\:opacity-50:active{opacity:.5}.hx\:active\:shadow-sm:active{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:active\:shadow-gray-200:active{--tw-shadow-color:var(--hx-color-gray-200)}@supports(color:color-mix(in lab,red,red)){.hx\:active\:shadow-gray-200:active{--tw-shadow-color:color-mix(in oklab,var(--hx-color-gray-200)var(--tw-shadow-alpha),transparent)}}.hx\:data-\[state\=closed\]\:hidden[data-state=closed],.hx\:data-\[state\=open\]\:hidden[data-state=open]{display:none}.hx\:data-\[state\=selected\]\:block[data-state=selected]{display:block}.hx\:data-\[state\=selected\]\:border-primary-500[data-state=selected]{border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:text-primary-600[data-state=selected]{color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:border{border-style:var(--tw-border-style);border-width:1px}.hx\:contrast-more\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.hx\:contrast-more\:border-current{border-color:initial}.hx\:contrast-more\:border-gray-800{border-color:var(--hx-color-gray-800)}.hx\:contrast-more\:border-gray-900{border-color:var(--hx-color-gray-900)}.hx\:contrast-more\:border-neutral-400{border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:border-primary-500{border-color:var(--hx-color-primary-500)}.hx\:contrast-more\:border-transparent{border-color:#0000}.hx\:contrast-more\:font-bold{--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold)}.hx\:contrast-more\:text-current{color:currentColor}.hx\:contrast-more\:text-gray-700{color:var(--hx-color-gray-700)}.hx\:contrast-more\:text-gray-800{color:var(--hx-color-gray-800)}.hx\:contrast-more\:text-gray-900{color:var(--hx-color-gray-900)}.hx\:contrast-more\:underline{text-decoration-line:underline}.hx\:contrast-more\:shadow-\[0_0_0_1px_\#000\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,#000);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:hover\:border-gray-900:hover{border-color:var(--hx-color-gray-900)}}}@media not all and (min-width:80rem){.hx\:max-xl\:hidden{display:none}}@media not all and (min-width:64rem){.hx\:max-lg\:min-h-\[340px\]{min-height:340px}}@media not all and (min-width:48rem){.hx\:max-md\:sticky{position:sticky}.hx\:max-md\:hidden{display:none}.hx\:max-md\:min-h-\[340px\]{min-height:340px}.hx\:max-md\:\[transform\:translate3d\(0\,-100\%\,0\)\]{transform:translateY(-100%)}.hx\:max-md\:\[transform\:translate3d\(0\,0\,0\)\]{transform:translate(0)}}@media not all and (min-width:40rem){.hx\:max-sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}}@media(min-width:40rem){.hx\:sm\:right-0{right:calc(var(--hx-spacing)*0)}.hx\:sm\:left-auto{left:auto}.hx\:sm\:block{display:block}.hx\:sm\:flex{display:flex}.hx\:sm\:w-\[110\%\]{width:110%}.hx\:sm\:flex-row{flex-direction:row}.hx\:sm\:items-center{align-items:center}.hx\:sm\:items-start{align-items:flex-start}.hx\:sm\:justify-between{justify-content:space-between}.hx\:sm\:text-xl{font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height))}@media not all and (min-width:64rem){.hx\:sm\:max-lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@media(min-width:48rem){.hx\:md\:sticky{position:sticky}.hx\:md\:top-16{top:calc(var(--hx-spacing)*16)}.hx\:md\:mr-0{margin-right:calc(var(--hx-spacing)*0)}.hx\:md\:hidden{display:none}.hx\:md\:inline-block{display:inline-block}.hx\:md\:inline-flex{display:inline-flex}.hx\:md\:aspect-\[1\.1\/1\]{aspect-ratio:1.1}.hx\:md\:h-\[calc\(100vh-var\(--navbar-height\)-var\(--menu-height\)\)\]{height:calc(100vh - var(--navbar-height) - var(--menu-height))}.hx\:md\:max-h-\[min\(calc\(100vh-5rem-env\(safe-area-inset-bottom\)\)\,400px\)\]{max-height:min(calc(100vh - 5rem - env(safe-area-inset-bottom)),400px)}.hx\:md\:w-64{width:calc(var(--hx-spacing)*64)}.hx\:md\:shrink-0{flex-shrink:0}.hx\:md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.hx\:md\:justify-start{justify-content:flex-start}.hx\:md\:self-start{align-self:flex-start}.hx\:md\:overflow-auto{overflow:auto}.hx\:md\:px-12{padding-inline:calc(var(--hx-spacing)*12)}.hx\:md\:pt-12{padding-top:calc(var(--hx-spacing)*12)}.hx\:md\:text-3xl{font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height))}.hx\:md\:text-5xl{font-size:var(--hx-text-5xl);line-height:var(--tw-leading,var(--hx-text-5xl--line-height))}.hx\:md\:text-lg{font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height))}.hx\:md\:text-sm{font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}}@media(min-width:64rem){.hx\:lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:80rem){.hx\:xl\:block{display:block}.hx\:xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.hx\:ltr\:right-1\.5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*1.5)}.hx\:ltr\:right-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*2)}.hx\:ltr\:right-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){right:calc(var(--hx-spacing)*3)}.hx\:ltr\:-mr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:calc(var(--hx-spacing)*-4)}.hx\:ltr\:mr-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-right:auto}.hx\:ltr\:ml-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.hx\:ltr\:ml-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:ml-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:auto}.hx\:ltr\:rotate-180:where(:dir(ltr),[dir=ltr],[dir=ltr] *){rotate:180deg}.hx\:ltr\:rounded-l-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:ltr\:rounded-r-lg:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:ltr\:border-l:where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);border-left-width:1px}.hx\:ltr\:pr-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*0)}.hx\:ltr\:pr-1:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*1)}.hx\:ltr\:pr-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*2)}.hx\:ltr\:pr-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:ltr\:pr-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:ltr\:pr-9:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-right:calc(var(--hx-spacing)*9)}.hx\:ltr\:pl-3:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*3)}.hx\:ltr\:pl-4:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:ltr\:pl-5:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*5)}.hx\:ltr\:pl-6:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*6)}.hx\:ltr\:pl-8:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:ltr\:pl-12:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*12)}.hx\:ltr\:pl-16:where(:dir(ltr),[dir=ltr],[dir=ltr] *){padding-left:calc(var(--hx-spacing)*16)}.hx\:ltr\:text-right:where(:dir(ltr),[dir=ltr],[dir=ltr] *){text-align:right}.hx\:ltr\:before\:left-0:where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);left:calc(var(--hx-spacing)*0)}@media(min-width:48rem){.hx\:ltr\:md\:left-auto:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:auto}}.hx\:rtl\:left-1\.5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*1.5)}.hx\:rtl\:left-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*2)}.hx\:rtl\:left-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){left:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}.hx\:rtl\:mr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:mr-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:auto}.hx\:rtl\:-ml-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:calc(var(--hx-spacing)*-4)}.hx\:rtl\:ml-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-left:auto}.hx\:rtl\:-rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:-180deg}.hx\:rtl\:rotate-270:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:270deg}.hx\:rtl\:rounded-l-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-left-radius:var(--hx-radius-lg);border-bottom-left-radius:var(--hx-radius-lg)}.hx\:rtl\:rounded-r-lg:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-top-right-radius:var(--hx-radius-lg);border-bottom-right-radius:var(--hx-radius-lg)}.hx\:rtl\:border-r:where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);border-right-width:1px}.hx\:rtl\:pr-3:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*3)}.hx\:rtl\:pr-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*4)}.hx\:rtl\:pr-5:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*5)}.hx\:rtl\:pr-6:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*6)}.hx\:rtl\:pr-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*8)}.hx\:rtl\:pr-12:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*12)}.hx\:rtl\:pr-16:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-right:calc(var(--hx-spacing)*16)}.hx\:rtl\:pl-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*0)}.hx\:rtl\:pl-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*1)}.hx\:rtl\:pl-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*2)}.hx\:rtl\:pl-4:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*4)}.hx\:rtl\:pl-8:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*8)}.hx\:rtl\:pl-9:where(:dir(rtl),[dir=rtl],[dir=rtl] *){padding-left:calc(var(--hx-spacing)*9)}.hx\:rtl\:text-left:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:left}.hx\:rtl\:before\:right-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);right:calc(var(--hx-spacing)*0)}.hx\:rtl\:before\:rotate-180:where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);rotate:180deg}@media(min-width:48rem){.hx\:rtl\:md\:right-auto:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:auto}}.hx\:dark\:block:where(.dark,.dark *){display:block}.hx\:dark\:hidden:where(.dark,.dark *){display:none}.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:var(--hx-color-amber-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-amber-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-amber-200)30%,transparent)}}.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:var(--hx-color-blue-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-blue-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-blue-200)30%,transparent)}}.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-gray-100\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-gray-100)20%,transparent)}}.hx\:dark\:border-gray-400:where(.dark,.dark *){border-color:var(--hx-color-gray-400)}.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:var(--hx-color-green-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-green-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-green-200)30%,transparent)}}.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:var(--hx-color-indigo-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-indigo-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-indigo-200)30%,transparent)}}.hx\:dark\:border-neutral-700:where(.dark,.dark *){border-color:var(--hx-color-neutral-700)}.hx\:dark\:border-neutral-800:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-orange-400\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-orange-400)30%,transparent)}}.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:var(--hx-color-purple-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-purple-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-purple-200)30%,transparent)}}.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:var(--hx-color-red-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-red-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-red-200)30%,transparent)}}.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:var(--hx-color-yellow-200)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:border-yellow-200\/30:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-yellow-200)30%,transparent)}}.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:var(--hx-color-amber-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-amber-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-amber-900)30%,transparent)}}.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:var(--hx-color-blue-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-blue-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-blue-900)30%,transparent)}}.hx\:dark\:bg-dark:where(.dark,.dark *),.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:var(--hx-color-dark)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-dark\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)50%,transparent)}}.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:var(--hx-color-gray-50)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-gray-50\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-50)10%,transparent)}}.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:var(--hx-color-green-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-green-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-green-900)30%,transparent)}}.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:var(--hx-color-indigo-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-indigo-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-indigo-900)30%,transparent)}}.hx\:dark\:bg-neutral-700:where(.dark,.dark *){background-color:var(--hx-color-neutral-700)}.hx\:dark\:bg-neutral-800:where(.dark,.dark *){background-color:var(--hx-color-neutral-800)}.hx\:dark\:bg-neutral-900:where(.dark,.dark *){background-color:var(--hx-color-neutral-900)}.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:var(--hx-color-orange-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-orange-400\/20:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-orange-400)20%,transparent)}}.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-300\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--hx-color-primary-400)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-400)10%,transparent)}}.hx\:dark\:bg-primary-600:where(.dark,.dark *){background-color:var(--hx-color-primary-600)}.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:var(--hx-color-purple-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-purple-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-purple-900)30%,transparent)}}.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:var(--hx-color-red-900)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-red-900\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-red-900)30%,transparent)}}.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:var(--hx-color-yellow-700)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:bg-yellow-700\/30:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-yellow-700)30%,transparent)}}.hx\:dark\:from-gray-100:where(.dark,.dark *){--tw-gradient-from:var(--hx-color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:to-gray-400:where(.dark,.dark *){--tw-gradient-to:var(--hx-color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hx\:dark\:text-amber-200:where(.dark,.dark *){color:var(--hx-color-amber-200)}.hx\:dark\:text-blue-200:where(.dark,.dark *){color:var(--hx-color-blue-200)}.hx\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:dark\:text-gray-200:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hx\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:dark\:text-gray-400:where(.dark,.dark *){color:var(--hx-color-gray-400)}.hx\:dark\:text-green-200:where(.dark,.dark *){color:var(--hx-color-green-200)}.hx\:dark\:text-indigo-200:where(.dark,.dark *){color:var(--hx-color-indigo-200)}.hx\:dark\:text-neutral-200:where(.dark,.dark *){color:var(--hx-color-neutral-200)}.hx\:dark\:text-neutral-400:where(.dark,.dark *){color:var(--hx-color-neutral-400)}.hx\:dark\:text-orange-300:where(.dark,.dark *){color:var(--hx-color-orange-300)}.hx\:dark\:text-primary-600:where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:text-purple-200:where(.dark,.dark *){color:var(--hx-color-purple-200)}.hx\:dark\:text-red-200:where(.dark,.dark *){color:var(--hx-color-red-200)}.hx\:dark\:text-slate-100:where(.dark,.dark *){color:var(--hx-color-slate-100)}.hx\:dark\:text-white:where(.dark,.dark *){color:var(--hx-color-white)}.hx\:dark\:text-yellow-200:where(.dark,.dark *){color:var(--hx-color-yellow-200)}.hx\:dark\:opacity-80:where(.dark,.dark *){opacity:.8}.hx\:dark\:shadow-\[0_-1px_0_rgba\(255\,255\,255\,\.1\)_inset\]:where(.dark,.dark *){--tw-shadow:0 -1px 0 var(--tw-shadow-color,#ffffff1a)inset;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-\[0_-12px_16px_\#111\]:where(.dark,.dark *){--tw-shadow:0 -12px 16px var(--tw-shadow-color,#111);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:dark\:group-hover\:text-gray-50:where(.dark,.dark *):is(:where(.hx\:group):hover *){color:var(--hx-color-gray-50)}}.hx\:group-data-\[active\=true\]\:dark\:text-primary-600:is(:where(.hx\:group)[data-active=true] *):where(.dark,.dark *){color:var(--hx-color-primary-600)}.hx\:dark\:placeholder\:text-gray-400:where(.dark,.dark *)::placeholder{color:var(--hx-color-gray-400)}.hx\:dark\:before\:bg-neutral-800:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hx\:dark\:before\:invert:where(.dark,.dark *):before{content:var(--tw-content);--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}@media(hover:hover){.hx\:dark\:hover\:border-gray-100:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-100)}.hx\:dark\:hover\:border-gray-600:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-600)}.hx\:dark\:hover\:border-neutral-500:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-500)}.hx\:dark\:hover\:border-neutral-700:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:border-neutral-800:where(.dark,.dark *):hover{border-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-gray-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-gray-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-gray-100)5%,transparent)}}.hx\:dark\:hover\:bg-neutral-700:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-700)}.hx\:dark\:hover\:bg-neutral-800:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-800)}.hx\:dark\:hover\:bg-neutral-900:where(.dark,.dark *):hover{background-color:var(--hx-color-neutral-900)}.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hx\:dark\:hover\:bg-primary-100\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--hx-color-primary-100)5%,transparent)}}.hx\:dark\:hover\:bg-primary-700:where(.dark,.dark *):hover{background-color:var(--hx-color-primary-700)}.hx\:dark\:hover\:text-gray-50:where(.dark,.dark *):hover{color:var(--hx-color-gray-50)}.hx\:dark\:hover\:text-gray-100:where(.dark,.dark *):hover{color:var(--hx-color-gray-100)}.hx\:dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--hx-color-gray-200)}.hx\:dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--hx-color-gray-300)}.hx\:dark\:hover\:text-neutral-50:where(.dark,.dark *):hover{color:var(--hx-color-neutral-50)}.hx\:dark\:hover\:text-primary-400:where(.dark,.dark *):hover{color:var(--hx-color-primary-400)}.hx\:dark\:hover\:text-white:where(.dark,.dark *):hover{color:var(--hx-color-white)}.hx\:dark\:hover\:shadow-none:where(.dark,.dark *):hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.hx\:dark\:focus-visible\:bg-dark:where(.dark,.dark *):focus-visible{background-color:var(--hx-color-dark)}.hx\:data-\[state\=selected\]\:dark\:border-primary-500[data-state=selected]:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:data-\[state\=selected\]\:dark\:text-primary-600[data-state=selected]:where(.dark,.dark *){color:var(--hx-color-primary-600)}@media(prefers-contrast:more){.hx\:contrast-more\:dark\:border-current:where(.dark,.dark *){border-color:initial}.hx\:contrast-more\:dark\:border-gray-50:where(.dark,.dark *){border-color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:border-primary-500:where(.dark,.dark *){border-color:var(--hx-color-primary-500)}.hx\:dark\:contrast-more\:border-neutral-400:where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}.hx\:contrast-more\:dark\:text-current:where(.dark,.dark *){color:currentColor}.hx\:contrast-more\:dark\:text-gray-50:where(.dark,.dark *){color:var(--hx-color-gray-50)}.hx\:contrast-more\:dark\:text-gray-100:where(.dark,.dark *){color:var(--hx-color-gray-100)}.hx\:contrast-more\:dark\:text-gray-300:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hx\:contrast-more\:dark\:shadow-\[0_0_0_1px_\#fff\]:where(.dark,.dark *){--tw-shadow:0 0 0 1px var(--tw-shadow-color,#fff);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hx\:contrast-more\:dark\:shadow-none:where(.dark,.dark *){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.hx\:contrast-more\:dark\:hover\:border-gray-50:where(.dark,.dark *):hover{border-color:var(--hx-color-gray-50)}}}@media print{.hx\:print\:\[display\:none\],.hx\:print\:hidden{display:none}.hx\:print\:bg-transparent{background-color:#0000}}}html{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--hx-color-white);width:100%}body:where(.dark,.dark *){background-color:var(--hx-color-dark);color:var(--hx-color-gray-100)}:root{--primary-hue:212deg;--primary-saturation:100%;--primary-lightness:50%;--navbar-height:4rem;--hextra-banner-height:2rem;--menu-height:3.75rem}.dark{--primary-hue:204deg;--primary-saturation:100%;--primary-lightness:50%}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*2);font-size:var(--hx-text-4xl);line-height:var(--tw-leading,var(--hx-text-4xl--line-height));--tw-font-weight:var(--hx-font-weight-bold);font-weight:var(--hx-font-weight-bold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-neutral-200)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-neutral-200)70%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){padding-bottom:calc(var(--hx-spacing)*1);font-size:var(--hx-text-3xl);line-height:var(--tw-leading,var(--hx-text-3xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--hx-color-neutral-400)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)10%,transparent)}}.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}@media(prefers-contrast:more){.content :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-400)}}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-2xl);line-height:var(--tw-leading,var(--hx-text-2xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-xl);line-height:var(--tw-leading,var(--hx-text-xl--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-lg);line-height:var(--tw-leading,var(--hx-text-lg--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h5):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*8);font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);--tw-tracking:var(--hx-tracking-tight);letter-spacing:var(--hx-tracking-tight);color:var(--hx-color-slate-900)}.content :where(h6):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){color:var(--hx-color-slate-100)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.content :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--hx-color-primary-600);text-decoration-line:underline;text-decoration-thickness:from-font}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);border-color:var(--hx-color-gray-300);color:var(--hx-color-gray-700);font-style:italic}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){border-left-style:var(--tw-border-style);padding-left:calc(var(--hx-spacing)*6);border-left-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){border-right-style:var(--tw-border-style);padding-right:calc(var(--hx-spacing)*6);border-right-width:2px}.content :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-gray-700);color:var(--hx-color-gray-400)}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){padding-block:calc(var(--hx-spacing)*4);--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.content :where(pre):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(code):not(:where(.hextra-code-block code,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*6);border-collapse:collapse;width:100%;padding:calc(var(--hx-spacing)*0);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);display:block;overflow-x:auto}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead{background-color:var(--hx-color-gray-50)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:var(--hx-color-gray-600)}@supports(color:color-mix(in lab,red,red)){.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) thead:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-gray-600)20%,transparent)}}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr{margin:calc(var(--hx-spacing)*0);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*0)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) tr:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) th:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td{margin:calc(var(--hx-spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-gray-300);padding:calc(var(--hx-spacing)*2)}.content :where(table):not(:where(.hextra-code-block table,[class~=not-prose],[class~=not-prose] *)) td:where(.dark,.dark *){border-color:var(--hx-color-gray-600)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:decimal}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*6);list-style-type:disc}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*6)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)) li{margin-block:calc(var(--hx-spacing)*2)}.content :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)):has(li input[type=checkbox]){list-style-type:none}.content :where(ul,ol)>li>:where(ul,ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:calc(var(--hx-spacing)*0)}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:var(--hx-radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:color-mix(in oklab,var(--hx-color-black)4%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:color-mix(in oklab,var(--hx-color-black)3%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline:.25em;padding-block:calc(var(--hx-spacing)*.5);overflow-wrap:break-word;font-size:.9em}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.content :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-white)10%,transparent)}}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)){background-color:#0000;border-radius:0}.content :where(pre.mermaid):not(:where(.hextra-code-block pre,[class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){background-color:#0000}.content :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-inline:auto;margin-block:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-md)}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption{margin-top:calc(var(--hx-spacing)*2);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-500);display:block}.content :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)) figcaption:where(.dark,.dark *){color:var(--hx-color-gray-400)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dt{margin-top:calc(var(--hx-spacing)*6);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.content :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)) dd{margin-block:calc(var(--hx-spacing)*2);padding-inline-start:calc(var(--hx-spacing)*6)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-block:calc(var(--hx-spacing)*10);border-color:var(--hx-color-gray-200)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):last-child{margin-bottom:calc(var(--hx-spacing)*0)}.content :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)):where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .footnotes{margin-top:calc(var(--hx-spacing)*12);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height))}.content .footnotes hr{border-color:var(--hx-color-gray-200)}.content .footnotes hr:where(.dark,.dark *){border-color:var(--hx-color-neutral-800)}.content .subheading-anchor{opacity:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration))}.content .subheading-anchor:where(:dir(ltr),[dir=ltr],[dir=ltr] *){margin-left:calc(var(--hx-spacing)*1)}.content .subheading-anchor:where(:dir(rtl),[dir=rtl],[dir=rtl] *){margin-right:calc(var(--hx-spacing)*1)}span:target+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor),.content .subheading-anchor:focus-visible{opacity:1}span+:is(.content .subheading-anchor),:hover>:is(.content .subheading-anchor){text-decoration-line:none!important}.content .subheading-anchor:after{content:var(--tw-content);color:var(--hx-color-gray-300)}.content .subheading-anchor:where(.dark,.dark *):after{content:var(--tw-content);color:var(--hx-color-neutral-700)}.content .subheading-anchor:after{padding-inline:calc(var(--hx-spacing)*1);--tw-content:"#";content:var(--tw-content)}span:target+:is(){color:var(--hx-color-gray-400)}span:target+:is():where(.dark,.dark *){color:var(--hx-color-neutral-500)}article details>summary::-webkit-details-marker{display:none}article details>summary:before{vertical-align:-4px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='hx:h-5 hx:w-5' viewBox='0 0 20 20' fill='currentColor'%3E%3Cpath fill-rule='evenodd' d='M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z' clip-rule='evenodd' /%3E%3C/svg%3E");width:1.2em;height:1.2em;padding:0 .6em}:lang(fa) ol{list-style-type:persian}.highlight .chroma .err{color:#a61717;background-color:#e3d2d2}.highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.highlight .chroma .line{display:flex}.highlight .chroma .k,.highlight .chroma .kc,.highlight .chroma .kd,.highlight .chroma .kn,.highlight .chroma .kp,.highlight .chroma .kr{color:#000;font-weight:700}.highlight .chroma .kt{color:#458;font-weight:700}.highlight .chroma .na{color:teal}.highlight .chroma .nb{color:#0086b3}.highlight .chroma .bp{color:#999}.highlight .chroma .nc{color:#458;font-weight:700}.highlight .chroma .no{color:teal}.highlight .chroma .nd{color:#3c5d5d;font-weight:700}.highlight .chroma .ni{color:purple}.highlight .chroma .ne,.highlight .chroma .nf,.highlight .chroma .nl{color:#900;font-weight:700}.highlight .chroma .nn{color:#555}.highlight .chroma .nt{color:navy}.highlight .chroma .nv,.highlight .chroma .vc,.highlight .chroma .vg,.highlight .chroma .vi{color:teal}.highlight .chroma .s,.highlight .chroma .sa,.highlight .chroma .sb,.highlight .chroma .sc,.highlight .chroma .dl,.highlight .chroma .sd,.highlight .chroma .s2,.highlight .chroma .se,.highlight .chroma .sh,.highlight .chroma .si,.highlight .chroma .sx{color:#d14}.highlight .chroma .sr{color:#009926}.highlight .chroma .s1{color:#d14}.highlight .chroma .ss{color:#990073}.highlight .chroma .m,.highlight .chroma .mb,.highlight .chroma .mf,.highlight .chroma .mh,.highlight .chroma .mi,.highlight .chroma .il,.highlight .chroma .mo{color:#099}.highlight .chroma .o,.highlight .chroma .ow{color:#000;font-weight:700}.highlight .chroma .c,.highlight .chroma .ch,.highlight .chroma .cm,.highlight .chroma .c1{color:#998;font-style:italic}.highlight .chroma .cs,.highlight .chroma .cp,.highlight .chroma .cpf{color:#999;font-style:italic;font-weight:700}.highlight .chroma .gd{color:#000;background-color:#fdd}.highlight .chroma .ge{color:#000;font-style:italic}.highlight .chroma .gr{color:#a00}.highlight .chroma .gh{color:#999}.highlight .chroma .gi{color:#000;background-color:#dfd}.highlight .chroma .go{color:#888}.highlight .chroma .gp{color:#555}.highlight .chroma .gs{font-weight:700}.highlight .chroma .gu{color:#aaa}.highlight .chroma .gt{color:#a00}.highlight .chroma .gl{text-decoration:underline}.highlight .chroma .w{color:#bbb}.dark .highlight .chroma .err{color:#f85149}.dark .highlight .chroma .lnlinks{color:inherit;outline:none;text-decoration:none}.dark .highlight .chroma .line{display:flex}.dark .highlight .chroma .k{color:#ff7b72}.dark .highlight .chroma .kc{color:#79c0ff}.dark .highlight .chroma .kd,.dark .highlight .chroma .kn{color:#ff7b72}.dark .highlight .chroma .kp{color:#79c0ff}.dark .highlight .chroma .kr,.dark .highlight .chroma .kt{color:#ff7b72}.dark .highlight .chroma .nc{color:#f0883e;font-weight:700}.dark .highlight .chroma .no{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nd{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .ni{color:#ffa657}.dark .highlight .chroma .ne{color:#f0883e;font-weight:700}.dark .highlight .chroma .nf{color:#d2a8ff;font-weight:700}.dark .highlight .chroma .nl{color:#79c0ff;font-weight:700}.dark .highlight .chroma .nn{color:#ff7b72}.dark .highlight .chroma .py{color:#79c0ff}.dark .highlight .chroma .nt{color:#7ee787}.dark .highlight .chroma .nv{color:#79c0ff}.dark .highlight .chroma .l{color:#a5d6ff}.dark .highlight .chroma .ld{color:#79c0ff}.dark .highlight .chroma .s{color:#a5d6ff}.dark .highlight .chroma .sa{color:#79c0ff}.dark .highlight .chroma .sb,.dark .highlight .chroma .sc{color:#a5d6ff}.dark .highlight .chroma .dl{color:#79c0ff}.dark .highlight .chroma .sd,.dark .highlight .chroma .s2{color:#a5d6ff}.dark .highlight .chroma .se,.dark .highlight .chroma .sh{color:#79c0ff}.dark .highlight .chroma .si,.dark .highlight .chroma .sx{color:#a5d6ff}.dark .highlight .chroma .sr{color:#79c0ff}.dark .highlight .chroma .s1,.dark .highlight .chroma .ss,.dark .highlight .chroma .m,.dark .highlight .chroma .mb,.dark .highlight .chroma .mf,.dark .highlight .chroma .mh,.dark .highlight .chroma .mi,.dark .highlight .chroma .il,.dark .highlight .chroma .mo{color:#a5d6ff}.dark .highlight .chroma .o,.dark .highlight .chroma .ow{color:#ff7b72;font-weight:700}.dark .highlight .chroma .c,.dark .highlight .chroma .ch,.dark .highlight .chroma .cm,.dark .highlight .chroma .c1{color:#8b949e;font-style:italic}.dark .highlight .chroma .cs,.dark .highlight .chroma .cp,.dark .highlight .chroma .cpf{color:#8b949e;font-style:italic;font-weight:700}.dark .highlight .chroma .gd{color:#ffa198;background-color:#490202}.dark .highlight .chroma .ge{color:inherit;font-style:italic}.dark .highlight .chroma .gr{color:#ffa198}.dark .highlight .chroma .gh{color:#79c0ff;font-weight:700}.dark .highlight .chroma .gi{color:#56d364;background-color:#0f5323}.dark .highlight .chroma .go,.dark .highlight .chroma .gp{color:#8b949e}.dark .highlight .chroma .gs{font-weight:700}.dark .highlight .chroma .gu{color:#79c0ff}.dark .highlight .chroma .gt{color:#ff7b72}.dark .highlight .chroma .gl{text-decoration:underline}.dark .highlight .chroma .w{color:#6e7681}.hextra-code-block{--tw-leading:calc(var(--hx-spacing)*5);font-size:.9em;line-height:calc(var(--hx-spacing)*5)}.hextra-code-block pre{background-color:var(--hx-color-primary-700);overflow-x:auto}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block pre{--tw-font-weight:var(--hx-font-weight-medium);font-size:.9em;font-weight:var(--hx-font-weight-medium);-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}@media(prefers-contrast:more){.hextra-code-block pre{border-style:var(--tw-border-style);border-width:1px;border-color:var(--hx-color-primary-900)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre{border-color:color-mix(in oklab,var(--hx-color-primary-900)20%,transparent)}}.hextra-code-block pre{--tw-contrast:contrast(150%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}}.hextra-code-block pre:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}@media(prefers-contrast:more){.hextra-code-block pre:where(.dark,.dark *){border-color:var(--hx-color-primary-100)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block pre:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-primary-100)40%,transparent)}}}.hextra-code-block .hextra-code-filename{top:calc(var(--hx-spacing)*0);z-index:1;text-overflow:ellipsis;white-space:nowrap;border-top-left-radius:var(--hx-radius-xl);border-top-right-radius:var(--hx-radius-xl);background-color:var(--hx-color-primary-700);width:100%;position:absolute;overflow:hidden}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename{background-color:color-mix(in oklab,var(--hx-color-primary-700)5%,transparent)}}.hextra-code-block .hextra-code-filename{padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*2);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));color:var(--hx-color-gray-700)}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:var(--hx-color-primary-300)}@supports(color:color-mix(in lab,red,red)){.hextra-code-block .hextra-code-filename:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-primary-300)10%,transparent)}}.hextra-code-block .hextra-code-filename:where(.dark,.dark *){color:var(--hx-color-gray-200)}.hextra-code-block .hextra-code-filename+pre:not(.lntable pre){padding-top:calc(var(--hx-spacing)*12)}.hextra-code-block pre:not(.lntable pre){margin-bottom:calc(var(--hx-spacing)*4);border-radius:var(--hx-radius-xl);padding-inline:calc(var(--hx-spacing)*4);padding-block:calc(var(--hx-spacing)*4)}.hextra-code-block div:nth-of-type(2) pre{padding-top:calc(var(--hx-spacing)*12);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .lntable{margin:calc(var(--hx-spacing)*0);border-radius:var(--hx-radius-xl);width:auto;display:block;overflow:auto}.chroma .lntable pre{padding-top:calc(var(--hx-spacing)*4);padding-bottom:calc(var(--hx-spacing)*4)}.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line){min-width:2.6rem;padding-right:calc(var(--hx-spacing)*4);padding-left:calc(var(--hx-spacing)*4);color:var(--hx-color-neutral-600)}:is(.chroma .ln,.chroma .lnt:not(.hl>.lnt),.chroma .hl:not(.line)):where(.dark,.dark *){color:var(--hx-color-neutral-300)}.chroma .lntd{padding:calc(var(--hx-spacing)*0);vertical-align:top}.chroma .lntd:last-of-type{width:100%}.chroma .hl{background-color:var(--hx-color-primary-800);width:100%;display:block}@supports(color:color-mix(in lab,red,red)){.chroma .hl{background-color:color-mix(in oklab,var(--hx-color-primary-800)10%,transparent)}}.hextra-cards{grid-template-columns:repeat(auto-fill,minmax(max(250px,calc((100% - 1rem*2)/var(--hextra-cards-grid-cols))),1fr))}.hextra-card{position:relative}.hextra-card img{-webkit-user-select:none;user-select:none}.hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card .hextra-card-icon svg{color:#0003;width:1.5rem;transition:color .3s}.hextra-card p{margin-top:.5rem;position:relative}.dark .hextra-card .hextra-card-icon svg{color:#fff6}.dark .hextra-card:hover .hextra-card-icon svg{color:currentColor}.hextra-card-tag{z-index:10;position:absolute;top:5px}.hextra-card-tag:where(:dir(ltr)){right:5px}.hextra-card-tag:where(:dir(rtl)){left:5px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker){counter-increment:step}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(ltr),[dir=ltr],[dir=ltr] *):before{content:var(--tw-content);margin-left:-41px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(:dir(rtl),[dir=rtl],[dir=rtl] *):before{content:var(--tw-content);margin-right:-44px}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);background-color:var(--hx-color-gray-100)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--hx-color-neutral-800)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:var(--tw-content);border-style:var(--tw-border-style);content:var(--tw-content);border-width:4px;border-color:var(--hx-color-white)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):where(.dark,.dark *):before{content:var(--tw-content);border-color:var(--hx-color-dark)}.hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step);text-align:center;text-indent:-1px;width:33px;height:33px;font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-font-weight:var(--hx-font-weight-normal);font-weight:var(--hx-font-weight-normal);color:var(--hx-color-neutral-400);border-radius:340282e33px;position:absolute}:lang(fa) .hextra-steps :where(h2,h3,h4,h5,h6):not(.no-step-marker):before{content:counter(step,persian)}.hextra-search-wrapper li{margin-inline:calc(var(--hx-spacing)*2.5);border-radius:var(--hx-radius-md);overflow-wrap:break-word;color:var(--hx-color-gray-800)}@media(prefers-contrast:more){.hextra-search-wrapper li{border-style:var(--tw-border-style);border-width:1px;border-color:#0000}}.hextra-search-wrapper li:where(.dark,.dark *){color:var(--hx-color-gray-300)}.hextra-search-wrapper li a{scroll-margin:calc(var(--hx-spacing)*12);padding-inline:calc(var(--hx-spacing)*2.5);padding-block:calc(var(--hx-spacing)*2);display:block}.hextra-search-wrapper li a:focus-visible{--tw-outline-style:none;outline-style:none}.hextra-search-wrapper li .hextra-search-title{font-size:var(--hx-text-base);line-height:var(--tw-leading,var(--hx-text-base--line-height));--tw-leading:calc(var(--hx-spacing)*5);line-height:calc(var(--hx-spacing)*5);--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold)}.hextra-search-wrapper li .hextra-search-active{border-radius:var(--hx-radius-md);background-color:var(--hx-color-primary-500)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper li .hextra-search-active{background-color:color-mix(in oklab,var(--hx-color-primary-500)10%,transparent)}}@media(prefers-contrast:more){.hextra-search-wrapper li .hextra-search-active{border-color:var(--hx-color-primary-500)}}.hextra-search-wrapper .hextra-search-no-result{padding:calc(var(--hx-spacing)*8);text-align:center;font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));color:var(--hx-color-gray-400);-webkit-user-select:none;user-select:none;display:block}.hextra-search-wrapper .hextra-search-prefix{margin-inline:calc(var(--hx-spacing)*2.5);margin-top:calc(var(--hx-spacing)*6);margin-bottom:calc(var(--hx-spacing)*2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix{border-color:color-mix(in oklab,var(--hx-color-black)10%,transparent)}}.hextra-search-wrapper .hextra-search-prefix{padding-inline:calc(var(--hx-spacing)*2.5);padding-bottom:calc(var(--hx-spacing)*1.5);font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));--tw-font-weight:var(--hx-font-weight-semibold);font-weight:var(--hx-font-weight-semibold);color:var(--hx-color-gray-500);text-transform:uppercase;-webkit-user-select:none;user-select:none}.hextra-search-wrapper .hextra-search-prefix:first-child{margin-top:calc(var(--hx-spacing)*0)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix{border-color:var(--hx-color-gray-600);color:var(--hx-color-gray-900)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)20%,transparent)}}.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){color:var(--hx-color-gray-300)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-prefix:where(.dark,.dark *){border-color:var(--hx-color-gray-50);color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{margin-top:calc(var(--hx-spacing)*1);font-size:var(--hx-text-sm);line-height:var(--tw-leading,var(--hx-text-sm--line-height));--tw-leading:1.35rem;text-overflow:ellipsis;color:var(--hx-color-gray-600);line-height:1.35rem;overflow:hidden}.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-400)}@media(prefers-contrast:more){.hextra-search-wrapper .hextra-search-excerpt:where(.dark,.dark *){color:var(--hx-color-gray-50)}}.hextra-search-wrapper .hextra-search-excerpt{line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box}.hextra-search-wrapper .hextra-search-match{color:var(--hx-color-primary-600)}@media(max-width:48rem){.hextra-sidebar-container{top:calc(var(--hx-spacing)*0);bottom:calc(var(--hx-spacing)*0);z-index:15;overscroll-behavior:contain;background-color:var(--hx-color-white);width:100%;padding-top:calc(var(--navbar-height) + var(--hextra-banner-height));position:fixed}.hextra-sidebar-container:where(.dark,.dark *){background-color:var(--hx-color-dark)}.hextra-sidebar-container{will-change:transform,opacity;contain:layout style;backface-visibility:hidden;transition:transform .4s cubic-bezier(.52,.16,.04,1)}}.hextra-sidebar-container li>.hextra-sidebar-children{height:calc(var(--hx-spacing)*0)}.hextra-sidebar-container li.open>.hextra-sidebar-children{height:auto;padding-top:calc(var(--hx-spacing)*1)}.hextra-sidebar-container li.open>.hextra-sidebar-item>.hextra-sidebar-collapsible-button>svg>path{rotate:90deg}.hextra-banner-hidden .hextra-banner{display:none}.hextra-banner :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:underline;text-decoration-thickness:from-font}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){--tw-leading:calc(var(--hx-spacing)*7);line-height:calc(var(--hx-spacing)*7)}.hextra-banner :where(p):not(:where([class~=not-prose],[class~=not-prose] *)):first-child{margin-top:calc(var(--hx-spacing)*0)}nav .hextra-search-wrapper{display:none}@media(min-width:48rem){nav .hextra-search-wrapper{display:inline-block}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-nav-container-blur{background-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur{background-color:color-mix(in oklab,var(--hx-color-white)85%,transparent)}}.hextra-nav-container-blur{--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-nav-container-blur:where(.dark,.dark *){background-color:var(--hx-color-dark)!important}@supports(color:color-mix(in lab,red,red)){.hextra-nav-container-blur:where(.dark,.dark *){background-color:color-mix(in oklab,var(--hx-color-dark)80%,transparent)!important}}}.hextra-hamburger-menu svg g{transform-origin:50%;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg path{opacity:1;transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open path{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:0s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open g{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.1s;--tw-ease:var(--hx-ease-out);transition-duration:.1s;transition-delay:.1s;transition-timing-function:var(--hx-ease-out)}.hextra-hamburger-menu svg.open>path{opacity:0}.hextra-hamburger-menu svg.open>g:first-of-type{rotate:45deg}.hextra-hamburger-menu svg.open>g:first-of-type path{--tw-translate-y:calc(var(--hx-spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-hamburger-menu svg.open>g:nth-of-type(2){rotate:-45deg}.hextra-hamburger-menu svg.open>g:nth-of-type(2) path{--tw-translate-y:calc(var(--hx-spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.hextra-scrollbar,.hextra-scrollbar *{scrollbar-width:thin;scrollbar-color:oklch(55.55% 0 0/.4)transparent;scrollbar-gutter:stable}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar{height:calc(var(--hx-spacing)*3);width:calc(var(--hx-spacing)*3)}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-track{background-color:#0000}:is(.hextra-scrollbar,.hextra-scrollbar *)::-webkit-scrollbar-thumb{border-radius:10px}:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{background-color:var(--tw-shadow-color);--tw-shadow-color:var(--hx-color-neutral-500);background-clip:content-box;border:3px solid #0000}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)20%,transparent)var(--tw-shadow-alpha),transparent)}}@media(hover:hover){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:var(--hx-color-neutral-500)}@supports(color:color-mix(in lab,red,red)){:is(.hextra-scrollbar,.hextra-scrollbar *):hover::-webkit-scrollbar-thumb:hover{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--hx-color-neutral-500)40%,transparent)var(--tw-shadow-alpha),transparent)}}}@supports((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.hextra-code-copy-btn{opacity:.85;--tw-backdrop-blur:blur(var(--hx-blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.hextra-code-copy-btn:where(.dark,.dark *){opacity:.8}}@media(min-width:1024px){.hextra-feature-grid{grid-template-columns:repeat(var(--hextra-feature-grid-cols),minmax(0,1fr))}}.hextra-jupyter-code-cell{scrollbar-gutter:auto;margin-top:calc(var(--hx-spacing)*6)}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container{font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:hidden}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs{max-height:50vh;overflow:auto}.hextra-jupyter-code-cell .hextra-jupyter-code-cell-outputs-container .hextra-jupyter-code-cell-outputs pre{max-width:100%;font-size:var(--hx-text-xs);line-height:var(--tw-leading,var(--hx-text-xs--line-height));overflow:auto}.hextra-badge{align-items:center;display:inline-flex}.hextra-toc a.hextra-toc-active{transition-property:all;transition-timing-function:var(--tw-ease,var(--hx-default-transition-timing-function));transition-duration:var(--tw-duration,var(--hx-default-transition-duration));--tw-duration:.2s;transition-duration:.2s;color:var(--hx-color-gray-900)!important}.hextra-toc a.hextra-toc-active:where(.dark,.dark *){color:var(--hx-color-gray-50)!important}.hextra-archive-timeline{border-left-style:var(--tw-border-style);border-left-width:2px;border-color:var(--hx-color-black)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline{border-color:color-mix(in oklab,var(--hx-color-black)15%,transparent)}}.hextra-archive-timeline:where(.dark,.dark *){border-color:var(--hx-color-white)}@supports(color:color-mix(in lab,red,red)){.hextra-archive-timeline:where(.dark,.dark *){border-color:color-mix(in oklab,var(--hx-color-white)15%,transparent)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}:root{--hextra-navbar-height:168px}.hextra-nav-container nav{min-height:168px!important;height:168px!important} \ No newline at end of file diff --git a/public/css/custom.css b/public/css/custom.css new file mode 100644 index 0000000..639180b --- /dev/null +++ b/public/css/custom.css @@ -0,0 +1,8 @@ +:root { + --hextra-navbar-height: 80px; +} + +.hextra-nav-container nav { + min-height: 80px !important; + height: 80px !important; +} diff --git a/public/css/variables.css b/public/css/variables.css new file mode 100644 index 0000000..f4f9ef7 --- /dev/null +++ b/public/css/variables.css @@ -0,0 +1,26 @@ +/* Hugo template to derive CSS variables from site and page parameters */ + +/* Do not remove the following comment. It is used by Hugo to render CSS variables.*/ + +:root { + --hextra-max-page-width: 80rem; + --hextra-max-content-width: 72rem; + --hextra-max-navbar-width: 80rem; + --hextra-max-footer-width: 80rem; +} + +.hextra-max-page-width { + max-width: var(--hextra-max-page-width); +} + +.hextra-max-content-width { + max-width: var(--hextra-max-content-width); +} + +.hextra-max-navbar-width { + max-width: var(--hextra-max-navbar-width); +} + +.hextra-max-footer-width { + max-width: var(--hextra-max-footer-width); +} diff --git a/public/en.search-data.json b/public/en.search-data.json new file mode 100644 index 0000000..8530a1d --- /dev/null +++ b/public/en.search-data.json @@ -0,0 +1 @@ +{"/about/":{"data":{"":"Kushidou 什么都学,却什么都不精通的一个小白 :P\nGitHub Bilibili","技能与方向#技能与方向":"嵌入式开发 — Linux 系统开发、驱动调试、交叉编译 开源软件 — Docker、Hugo、各类 CLI 工具 网络与服务 — 自建 NAS、内网穿透、家庭服务器 摄影 — 风光 / 街拍 / 生活记录","自我介绍#自我介绍":"我是一名嵌入式工程师,平时喜欢折腾各种开源软件和硬件。这个博客用来记录技术探索过程中的笔记和心得,也包括一些日常生活和摄影的分享。"},"title":"关于我"},"/glossary":{"data":{},"title":"Glossary"},"/posts/210218-teamspeakwithdocker/":{"data":{"":"TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。","docker及其安装#Docker及其安装":"docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。\n我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:\nsudo yum install docker\t#安装docker程序,会自动选择合适的 sudo systemctl start docker\t#启动docker服务 sudo systemctl enable docker\t#允许docker开机自启","docker安装ts#Docker安装TS":"docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak\n直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。","teamspeak#Teamspeak":"TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。\n所以,选择docker安装ts就是一个很不错的选择。","端口#端口":"从指令可以看出,docker需要使用到9987,10011,30033三个端口,所以需要到服务器后台开启防火墙上对应端口。","管理员密钥#管理员密钥":"TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。\ndocker ps -a查找所有容器。\ndocker logs -f [CONTATINER ID]查看log,找到token即可。客户端设置之后再说。\n密钥只会在第一次运行时打印,注意保存。\n参考:https://woolio.cn/683/"},"title":"使用Docker安装TeamSpeak"},"/posts/210311-multitouchscreeninlinux/":{"data":{"":"由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。\n2021-03-22更新部分内容 2021-04-28再次更新内容\n2021-09-14 文章已有新版本,请访问《Linux处理多触屏的终极解决方案》","参考#参考:":"[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 [2]https://blog.csdn.net/qq_33406883/article/details/106649087 [3]https://segmentfault.com/a/1190000020334077","后续问题#后续问题":"当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。 当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数 如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。","尝试解决#尝试解决":"首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。 然后xinput list-props $id查看设备的VID,这个在后续自动化处理有用。 xrandr查看$显示器名称","自动化处理#自动化处理":"# 2021-04-28更新: # 本部分后面的都可以不看了! xinput map-to-output \"DEVICE_NAME\" DISPLAY # 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, # 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~ 仅作归档!下面这些方法过于繁琐!\n利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。bash this.sh VID1 VID2 ScreenID\n#!/bin/bash # 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。 # 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。 MYID_FIRST=$1 MYID_SECOND=$2 OUT_PUT=$3 for I in $(xinput list --id-only) do #[1] CUR_FIRST=$(xinput list-props $I|grep \"Device Product ID\"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}') CUR_SECOND=$(xinput list-props $I|grep \"Device Product ID\"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}') if [ \"$MYID_FIRST\" == \"$CUR_FIRST\" ] \u0026\u0026 [ \"$MYID_SECOND\" == \"$CUR_SECOND\" ]; then MY_INDEX=$I break; fi done xinput map-to-output $MY_INDEX $OUT_PUT #./this.sh VID VID HDMI2 #./this.sh VID VID HDMI1 #!/bin/bash # 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。 TOUCH_ID=$(xinput | grep -iw DEVICE_NAME) TOUCH_ID=$(echo ${TOUCH_ID#*id=}) TOUCH_ID=$(echo ${TOUCH_ID%% *}) xinput map-to-output $TOUCH_ID SCREEN_NAME 此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id。\n经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在~/.bashrc中,这样只需要打开一次终端,就可以实现屏幕重新映射。","触屏校准如果需要#触屏校准(如果需要)":"网上说的利用校准来区分多触摸屏的方法是完全错误的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)\nsudo apt install xinput-calibrator,需要更新源 sudo xinput_calibrator 将校准信息保存到/usr/share/X11/xorg.conf.d/10-evdev.conf的\"Driver evdev”下面一行,重启生效。","问题来源#问题来源":"最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为\nxinput map-to-output $触屏设备id $显示屏名称\n2021-04-28更新\nxinput map-to-output指令支持触摸屏名称作为参数,比如我通过xinput指令看到有一个触摸屏的设备名称为ILITEK XXXX,那么我映射的时候直接输入\nxinput map-to-output \"ILITEK XXX\" HDMI-1"},"title":"Linux多显示器触屏调试与暂行解决方案"},"/posts/210311-serialmodeandudevinlinux/":{"data":{"":"配置串口权限,顺便看一下linux的udev规则。","udev规则#udev规则":"udev是什么?udev是Linux下的设备文件管理方式,运行在用户侧(devfs在内核侧管理设备文件)。有了udev,/dev目录下的文件就都是真实存在的已连接的设备了。\nudev的规则都保存在/etc/udev/rules.d目录下,文件名的格式为数字-名称.rules,数字表示加载顺序,一般是从小往大加载;名称按功能自定义,最后以.rules结尾。这些规则会在设备接入时生效。\n规则文件按行执行,每行分为两个部分:匹配和赋值,每个参数使用逗号,分割。\n(这段复制自引用2,仅供参考)。 1、udev 规则的所有操作符 == 比较键、值,若等于,则该条件满足; != 比较键、值,若不等于,则该条件满足; = 对一个键赋值; += 为一个表示多个条目的键赋值。 := 对一个键赋值,并拒绝之后所有对该键的改动,防止后面的规则文件对该键重赋值。\n2、udev 规则的匹配键 ACTION 事件 (uevent) 的行为,例如:add、remove。 KERNEL 内核设备名称,例如:sda, cdrom。 DEVPATH 设备的 devpath 路径。 SUBSYSTEM 设备的子系统名称,例如:sda 的子系统为 block。 BUS 设备在 devpath 里的总线名称,例如:usb。 DRIVER 设备在 devpath 里的设备驱动名称,例如:ide-cdrom。 ID 设备在 devpath 里的识别号。 SYSFS{filename} 设备的 devpath 路径下,设备的属性文件“filename”里的内容。 ENV{key} 环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。 PROGRAM 调用外部命令。 RESULT 外部命令 PROGRAM 的返回结果。\n3、udev 的重要赋值键 NAME /dev目录下显示的文件名重命名 SYMLINK 为 /dev下文件进行符号链接,防止覆盖系统默认产生的文件 OWNER, GROUP, MODE 为设备设定权限。 ENV{key} 导入一个环境变量。\n4、udev 的值和可调用的替换操作符 Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符: $kernel, %k 设备的内核设备名称,例如:sda、cdrom。 $number, %n 设备的内核号码,例如:sda3 的内核号码是 3。 $devpath, %p 设备的 devpath路径。 $id, %b 设备在 devpath里的 ID 号。 $sysfs{file}, %s{file} 设备的 sysfs里 file 的内容。其实就是设备的属性值。 $env{key}, %E{key} 一个环境变量的值。 $major, %M 设备的 major 号。 $minor %m 设备的 minor 号。 $result, %c PROGRAM 返回的结果。 $parent, %P 父设备的设备文件名。 $root, %r udev_root的值,默认是 /dev/。 $tempnode, %N 临时设备名。 %% 符号 % 本身。 $$ 符号 $ 本身。","xxxrules#xxx.rules":"sudo vi /etc/udev/rules.d/70-tty-usb-rules,写入以下内容:\nKERNEL==\"ttyS*\",MODE=\"0666\" KERNEL==\"ttyUSB*\",MODE=\"0666\" 这一段的意思是匹配所有ttyS和ttyUSB,插入时都赋予0666的权限,即所有用户都可读可写。重新加载一下设备,就可以按规则配置权限进行操作了。","串口的问题#串口的问题":"在现场开发的同事反映UOS工控机没有配置串口,每次都需要sudo才能使用。(明明在家里你们都会自己配置好环境与权限,为什么到了现场反而不会了需要我提供协助呢?)\n我之前没考虑过这个问题,认为串口就是得root权限操作才安全。但是既然提出了需求,就赶紧百度并测试一下吧。","参考#参考":"[1]https://www.cnblogs.com/qixianyu/p/6869744.html [2]https://www.cnblogs.com/cslunatic/p/3171837.html","赋予普通用户串口权限的两种方法#赋予普通用户串口权限的两种方法":"在百度上,给普通用户串口权限有两种主流方法(可能2会了,Google上不去),一种是将用户放入特殊的用户组,从而获得权限。另一种是加入udev规则,在串口插入时自动更改权限。\n我个人更倾向于使用第二种方案,因为公司中不同的人装系统就可能起不同的名字,之后进入生产环境可能又会起新的名字,所以采用第二种设置规则的办法,便于之后制作恢复镜像和一键部署。"},"title":"Linux串口和udev理解"},"/posts/210328-linuxchangemacip/":{"data":{"linux调整网卡mac地址指令#Linux调整网卡MAC地址(指令)":"当Linux的图形界面没有或失效的时候,该如何更改mac地址?\nLinux调整网卡MAC地址(指令)有的时候需要使用欺骗的方法为电脑设置新的mac,比如存在上网网卡限制,或者想要隐藏自己的真实mac地址。这里列举三种修改mac的方法,适用于Debian系的Linux,其他的应该类似。","备注#备注:":"以上两种方法是软件层面的修改,不会对硬件造成损伤,重启之后就能恢复如初。如果是要长期有效,可以把这些命令写入脚本开机自启。除了这两个,还有另一种软修改的方法,因为我总是提示失败就遗忘了。\n有些方法会提到修改/etc目录下的文件实现MAC地址的修改,但是我一个文件都没找到(UOS),这个是因系统而异的,不同的发行版可以选择性的编译这些文件,就会造成有人有有人没有,因为没有实践,所以就不写在这里了。","如果以上方法都无法修改则建议申请新的ip#如果以上方法都无法修改,则建议申请新的ip。。。":"文章首发于香风家的小别墅,转载请携带出处。","方法一ifconfig#方法一:ifconfig":"# ifconfig 查看网卡名称$NAME # 需要sudo权限 ifconfig $NAME down ifconfig $NAME hw ether $NEW_MAC ifconfig $NAME up # 使用ifconfig检查有没有改变","方法三ethtool慎用#方法三:ethtool(慎用)":"以上方法都失效,可以直接修改网卡的物理MAC地址,修改后无法恢复,慎用\n# 首先检查mac是否支持修改 ethtool -i $NAME # 如果 supports-eeprom-access: no,大概率不支持修改 # 查看网卡的mac寄存器 ethtool -e $NAME # 如果无法读取,大概率不支持修改 # 修改mac,按字节修改 ethtool -E $NAME offset 0 value 0xAA ethtool -E $NAME offset 1 value 0xBB ethtool -E $NAME offset 2 value 0xCC ...共六行 # 如果修改无效,则驱动不支持修改","方法二macchanger#方法二:macchanger":"sudo apt install macchanger,安装过程提示是否自启,随便。\nifconfig $NAME down macchanger -m $NEW_MAC $NAME ifconfig $NAME up ps:macchanger可以附加多种参数,如-m指定mac地址,-s查看状态,-r随机地址"},"title":"Linux修改mac地址"},"/posts/210420-linuxmorethan4serialport/":{"data":{"":"工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。","先说结论#先说结论":"# 1.修改开机grub文件 sudo vi /etc/default/grub # 2.在\"GRUB_CMDLINE_LINUX_DEFAULT=\"一行的结尾空格,添加内容 8250.nr_uarts=8 # 3.更新开机引导并重启 sudo update-grub","再说细节#再说细节":"给带有多串口的工控机安装国产Linux操作系统UOS,发现/dev/ttyS只有四个,而且一个都不通,其实这是Linux 的通病,如果发行商没有提前配置,我们只能看到四个不能用的串口。但是工控机都有8-16个串口,不得不想办法让他们能够正常使用,而许多串口芯片只需要基本的驱动就能运行,也不提供专有驱动的源代码编译(一般安装专用驱动后,串口名称会改变,比如xr17v358芯片的驱动安装后名称变成ttyXR0),我们只有从linux本身下手了。\n经过多番查找资料,虽然不知道为什么,但是我们应该修改/etc/default/grub文件,其中有一行是这样的:\nGRUB_CMDLINE_LINUX_DEFAULT=\"这里面 的内容 先不管 它\" 我看了UOS(含Deepin)、Ubuntu和服务器的Centos7都有,在这一行的最后添加8250.nr_uart=32,其中数字\u003c=32都行,效果如下:\nGRUB_CMDLINE_LINUX_DEFAULT=\"这里面 的内容 先不管 它 8250.nr_uarts=32\" 然后更新一下系统的引导\nsudo update-gurb reboot 重启完了你就能看到更多的串口啦!配合修改udev规则,你就可以用普通用户的权限使用串口通信了。但是请注意其他隐患,比如这个芯片配合基本驱动只能完成基础的收发功能,流控的RTS、CTS等还是需要专用驱动实现的!","参考#参考:":"https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/\n核心步骤就参考了这个,在Linux方面大家还是多用google,多用英文去搜索吧!","最后吐槽#最后吐槽":"为了串口问题我找了一下午的资料,最后才在外网找到了一个能用的。国内的教程都是说要修改/boot/gurb/gurb2,宝友,这可不兴改啊,这个改坏了系统可就没了啊!这些文件普通用户连浏览权限都没就可见其重要性。"},"title":"Linux启用四个以上的串口"},"/posts/210429-deepinautorun/":{"data":{"":"修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。","uosdeepin和ubuntu#UOS/Deepin和Ubuntu":"cp ~/Desktop/刚刚的.desktop ~/.config/autostart\n需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(lightdm.service)执行,导致error从而无法进入桌面,将脚本放进/etc/profile.d之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。\n经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在~/.config/下手动创建一个autostart目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。","开始菜单#开始菜单":"sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。","开机自动执行#开机自动执行":"","桌面快捷方式#桌面快捷方式":"deepin的桌面快捷方式是以*.desktop文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容\n[Desktop Entry]\rName=ddd\rExec=\"/usr/sbin/xx.sh\"\rType=Application 其中Desktop Entry表示声明该文件为快捷方式文件。Name是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。Exec是程序的启动脚本,或者启动指令。Type是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。\n把这个文件放在~/Desktop,就可以看到桌面上多了一个快捷方式了。","麒麟和其他使用x11桌面的linux#麒麟和其他使用X11桌面的Linux":"本条目包含UOS和Ubuntu、deepin系统\n自启动目录为/etc/xdg/autostart,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。"},"title":"deepin和UOS,快捷方式,开机自启动"},"/posts/210630-sudowithoutpasswd/":{"data":{"":"记录一种可以免密码使用sudo的方法。\n每次使用sudo提权,常常要输密码太烦了,所以想给自己赋予免密码的特权。\n首先登录root用户: sudo -i\n这个操作的核心文件是/etc/sudoers,可以看到这个文件是只读的,要修改他需要先赋予写入权限。\nchmod 666 /etc/sudoers 使用vim修改文件,vi /etc/sudoers,把当前用户和用户组加入,如图 # 表示为该用户免密码 yourname\tALL=(ALL:ALL) NOPASSWD:ALL # 表示为该用户组免密码 %yourname\tALL=(ALL:ALL) NOPASSWD:ALL # 不用严格按照次形式,与你的这个文件中root的格式对应即可。 :wq保存,把sodoers改回只读 chmod 440 /etc/sudoers 完成~~~ 把终端关掉再次打开就应该能看到效果了。 PS. 不可使用sudo chmod\n一定要直接sudo -i进入root用户,如果使用sudo chmod以普通用户的身份修改sudoers文件的话,改完会发现无法使用sudo指令把权限改回去了,提示“任何人可读写,无法找到有效的sudoers”。其实还有一种更加安全的操作,但是那个编辑器用起来没有vim顺手,\n参考:https://blog.csdn.net/wxqee/article/details/72718869"},"title":"免密码使用sudo"},"/posts/210719-gpuinfoinlinux/":{"data":{"":"在Linux下获得AMD GPU的状态和温度信息。\n在Linux下如果要查看Nvidia GPU的状态信息,可以使用Nvidia驱动自带的工具。而AMD显卡一般都是使用的开源驱动,这种情况下如何获得显卡的状态和占用呢?\n文章部分图片之后再附","1获取gpu信息#1、获取GPU信息":"如需获得GPU的信息(型号、驱动)等,如果系统提供类似*“设备管理器”*的工具可以打开查看。或者就是用lspci指令查看。\n# 获得所有PCIe设备,在其中找到AMD GPU [exam@ple]:~$ lspci 00:02.0 VGA compatible controller: xxxxxxxx [exam@ple]:~$ lspci -v -s 00:02.0 # 这里就会显示显卡的具体信息了,包括型号、驱动等,之后补充","2gpu占用率#2、GPU占用率":"显卡占用率是一个能反映负载的直观指标,AMD显卡可以通过安装radeontop软件查看。\n最上面是GPU管道使用,大概类似于总体使用率的东西;最下面是内存使用率,使用sudo权限能看到更多信息。其他项目可以到radeontop的Github页面查看。","3温度#3、温度":"特指GPU温度。查阅这篇文章所涉及的知识点的最初目的,也是确认机箱过热是否是GPU的问题。\nGPU温度使用第三方软件查看,并使其实时更新。查看温度使用sensors指令,通过安装lm-sensors实现。执行sensors时只会显示一次,我们可以利用watch指令定时刷新温度显示:\nwatch -n 2 sensors # 每隔2秒执行sensors指令显示结果 ![watch执行结果](图片占位)"},"title":"Linux中获得AMD显卡的状态信息"},"/posts/210824-linuxtouchscreenfin/":{"data":{"":"一般Linux操作系统接入多个触摸屏的最终解!\n从21年3月中旬开始,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。\n重要: 本文全文基于软件xinput\n更重要: 如简介所说,仍有一种方法无法对应,具体为【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。","0核心xinput#0、核心:xinput":"xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)\nxinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级\nxinput list –id-only : 仅仅列出设备ID(序号,每次开机都可能改变)\nxinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕\nxinput list-props : 列出某个设备的详细信息,后接参数为设备ID。","1简约两个完全不同的触摸显示器#1、简约:两个完全不同的触摸显示器":"xinput map-to-output \"$DEVICE NAME A\" PORT-A\n这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。\n首先通过xinput查看所有的输入设备:\n~$ xinput ┌ Virtual core pointer\tid=2 [master pointer (3)] |\tILITEK TLITEK-TP\tid=9 [slave pointer (2)] |\tILITEK Multi-Touch\tid=7 [slave pointer (2)] (下略) 可以看到ILITEK ILITEK-TP和ILITEK Multi-Touch显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:\nxinput map-to-output \"ILITEK ILITEK-TP\" HDMI-0\nxinput map-to-output \"ILITEK Multi-Touch\" HDMI-1\n这种方式的要点是1.必须带英文\"\",2.引号内名称须完整。map-to-output所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。","2复杂但通用vidpid不同的显示器#2、复杂但通用:VID:PID不同的显示器":"不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。\n通过xinput list-props [id]可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。list-props的结果示例如下:\n(后续再补) 其中Device Product ID一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:\n#!/bin/bash # 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息: # 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段 VIDA=222a PIDA=0001 PORTA=VGA-0 # 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行 # 或者这里直接填写Device Product ID里面的结果,先VID后PID。 # 字母O,不是数字0,多屏重复本段 VIDA_O=$((16#$VIDA)) PIDA_O=$((16#$PIDA)) # =========自动处理步骤,请勿随意修改========== for I in $(xinput list --id-only) do # 根据设备ID取其序号 CUR_FIRST=$(xinput list-props $I|grep \"Device Product ID\"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}') CUR_SECOND=$(xinput list-props $I|grep \"Device Product ID\"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}') # 3、屏映射,多屏重复本段 if [ \"$VIDA_O\" == \"$CUR_FIRST\" ] \u0026\u0026 [ \"$PIDA_O\" == \"$CUR_SECOND\" ]; then xinput map-to-output $I $PORTA fi done exit 0 这个脚本使用xinput list --id-only仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用map-to-output进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。","32的进阶版#3、[2]的进阶版":"前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。\n在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是pyudev库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装python3+pyudev,并且使用desktop文件在开机时启动,即可。\n#!/usr/bin/python3 # -- coding: UTF-8 -- # -- Author: Blues -- import pyudev import os context = pyudev.Context() monitor = pyudev.Monitor.from_netlink(context) monitor.filter_by(subsystem='hid') for device in iter(monitor.poll, None): if device.driver != \"hid-multitouch\": continue if device.action == \"add\" or device.action == \"change\": os.system(\"/var/my-touchscreen/remap-screen\") 上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照设备名、VID、UID、物理接口四大特征,自动映射到对应的显示屏上了。"},"title":"Linux处理多触屏的终极解决方案"},"/posts/210914-linuxbackupdd/":{"data":{"":"害,谁不是个DD (誰でも 大好き)了呢!\n但是这玩意儿太慢了。。。","dd备份#DD备份":"dd if=/dev/sda of=/dev/sdb\t=\u003e 备份整个磁盘到另外一个磁盘 dd if=/dev/sdb of=sda.img\t=\u003e 备份整个磁盘为某个文件 dd if=/dev/sda | gzip \u003e sda.img =\u003e 备份并且压缩 dd if=/dev/sda bs=1M | gzip \u003e sda.img\t=\u003e 指定块大小,备份并压缩(据说能提速)","dd复原#DD复原":"dd if=/dev/sdb of=/dev/sda\t=\u003e从另一个磁盘恢复回来 gzip -dc sda.img | dd of=/dev/sda\t=\u003e从压缩的文件恢复出来 gzip -dc sda.img | dd of=/dev/sda bs=1M\t=\u003e前面指定了块大小,这里也需要","dd说明#DD说明":"dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。\ndd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。\n实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。","参考#参考:":"本文参考(抄)了以下内容:\nLinux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像\nlinux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)\n还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ )","起因#起因":"使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。"},"title":"使用dd指令备份和恢复Linux系统(硬盘级)"},"/posts/210914-mdadm/":{"data":{"":"解决Linux系统异地恢复后开机mdadm报错导致开机时间变长。","从这儿抄的#从这儿抄的:":"W: mdadm: no arrays found in config file or automatically导致开机变慢的解决方案(deepin)","方案#方案":"步骤一:删除mdadm.conf\nsudo rm /etc/mdadm/mdadm.conf 步骤二:生成新的mdadm.conf\nsudo update-initramfs -u ps:如果是swap分区问题导致等待90s开机慢,不归这儿管。","现象#现象":"使用deepin-clone恢复UOS镜像后,开机会不断跳一条日志:\nmdadm: no arrays found in config file or automatically 大概蹦个15条后,系统才真正开机。mdadm好像是一个磁盘raid管理工具,当磁盘变化后其配置也不适配了,必须等到超时才会加载默认配置。那么我们就为其生成一个新的配置即可解决问题。"},"title":"Linux恢复操作系统后mdadm导致开机变慢的方法"},"/posts/210928-rpm_package/":{"data":{"":"Linux如Centos使用的RPM打包方法。本文仅涉及二进制打包,且采用取巧的方法简化操作。(笔记,而非教程)\n正文开始"},"title":"RPM安装包制作方法"},"/posts/211019-dpkg_package_guide/":{"data":{"":"简单介绍deb打包的方法,包括目录树格式和打包注意事项。","control#control":"control文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。\nNAME Description Package 软件名,dpkg -l 列出的名字 Version 版本号-修订号 Section 软件类型,比如mail, test, x11等 Priority 优先级/重要性,比如optional 可选的 Architecture 软件架构,比如x86, arm64 Depends 依赖环境,用 , 隔开,用(\u003e=\u003c )指定版本号 Suggests 推荐安装,指非必要依赖,可以提供更多功能 Maintainer 作者 \u003c邮箱或者其他联系方式\u003e Descritprion 简介,可以换行书写","debian#DEBIAN":"DEBIAN目录下有一个control文件和至多四个脚本文件。","md5sums#md5sums":"为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。","其他信息#其他信息":"一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。","打包#打包":"所有文件配置完成后,进入工作空间上级目录,执行指令\ndpkg -b ./PackageDIR packagename.deb deb包命名的一般规则为 软件名_版本-修订号_架构.deb","文件结构#文件结构":"首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录/。一个工作空间的目录树如下:\nPackageDIR\r├─DEBIAN\r├─opt\r│ └─kushidou-touchscreen\r│ └─desktops\r├─usr\r│ ├─bin\r│ └─share\r└─var 其中,DEBIAN目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。","额外脚本#额外脚本":"DEBIAN目录下之多放置四个可执行脚本,包括安装前preinst、安装后postinst、卸载前prerm、卸载前postrm,四个脚本都需要以#!/bin/bash开头,内容与普通脚本无异,文件名不需要后缀。\n这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以root身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。\n此外postinst可以用来启动已安装的程序,prerm常用来停止正在运行的程序。"},"title":"DEB打包方法简介"},"/posts/211115-fixnasdriver/":{"data":{"":"更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据?\n图片稍后补充。","v-0-起因#v 0 起因":"双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0.\n考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。\n为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。","v-1-曙光#v 1 曙光":"群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。\n我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?!\n开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。\n把小硬盘用易驱线连上树莓派,lsblk看到这个硬盘有三个分区,其中/dev/sda3下面还有一个md127,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。","v-1-杯具#v-1 杯具":"当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。","v-2-尝试#v-2 尝试":"DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内写入任何内容,也没有格式化。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。\n为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。\n经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。\n搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。\n难道就这么放弃了么?","v-2-得到数据#v 2 得到数据":"安装mdadm软件包,我的树莓派不带这个,然后就能挂载raid盘了。\nsudo mount /dev/md127 ~/disk disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个NasData目录,这正是我当时保存文件的地方。cd进去,在!都还在!","v-3-移动数据#v 3 移动数据":"我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。\n在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs\nsudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata 主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。\nrsync -vrultD --exclude '文件名' --exclude '目录名' ~/disk/NasData/* ~/Nasdata/临时目录 # -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息 # 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败 经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。","v-4-结论#v 4 结论":"现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。\n这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。\n总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢?"},"title":"记一次拯救NAS硬盘"},"/posts/211203-linuxchangeusername/":{"data":{"":"如何在Linux下完整修改当前用户名和主目录,还不影响其他文件?\nLinux的用户名是一个用户的字符串表示,是可以修改的。只要用户的id不改变就不会被系统认作新用户,所有的权限都可以继承。下面介绍如何修改用户的名称、所属组,并更改主目录的名称与对应关系。","一进入单用户模式#一、进入“单用户模式”":"为了修改用户名时不影响程序运行,需要把当前用户打开的所有程序都关闭。该方法可以快速注销当前用户。\ninit 1 执行后输入当前用户密码 进入黑屏终端后需要输入root密码,可以使用sudo passwd 提前设置 对于屏蔽init指令的系统,一般情况下注销用户就可以了,但是有些GUI桌面环境就是以当前用户的身份运行的,所以提供如下几个方式关闭当前用户,挨个尝试即可。\n# 1 桌面环境中点击注销 # 2 杀掉当前用户所有进程 pkill -kill -u old_name # 3 杀掉进程后可能桌面重启,关闭Lightdm管理器 # Ctrl + Alt + F2 进入第二页面,以root用户登录(可能需要提前设置root密码) systemctl stop lightdm # 然后再次执行第二步","二修改信息#二、修改信息":"init 0可以直接进入黑屏的终端,或者使用 ctrl + alt + f2进入(远程连接可以),以root用户登录。\n# 1 修改组的名称 groupmod -n new_name old_name # 2 修改用户名 -l 改名字 -d 改目录 -g 改组 usermod -l new_name -d /home/new_name -g new_name old_name # 3 修改主目录 mv /home/old_name /home/new_name 最后回到图形化界面,输入init 5 ,或者重启电脑。\n修改完主目录后记得检查是不是有其他软连接指向旧目录,需要进行更新,否则还会遇到问题。","参考#参考:":"# linux用户家目录怎么修改,Linux 更改用户名,用户组和主目录\n# 菜鸟教程-usermod指令\n# linux组的管理:修改组名字,删除组,使用sudo权限"},"title":"在Linux环境下修改用户名和主目录"},"/posts/220104-diylinuxiso/":{"data":{"":"想要在原版的Linux镜像上添加自定义的软件,但是不想、不会编译,那么就可以基于Linux的iso文件进行修改,定制自己的安装试用镜像。\n起因是因为对付信创操作系统的备份恢复时,UOS、麒麟自带的工具都超级不好用,两个系统的镜像不统一,导致实际使用的时候各种问题和不方便。所以使用clonezilla再生龙工具自己做一个PE镜像。这个镜像不仅可以备份、安装系统,还能够修改硬盘大小、应急拯救系统等多种功能。并且它是一个完整的系统可以安装使用。","基本计划#基本计划":"之前尝试过使用deepin魔改lub工具制作系统的完整镜像,但是启动总是失败。LUB全称是Live Ubuntu Backup,是Ubuntu的一个工具,到了同为Debian系的deepin魔改的UOS上就水土不服了。(好绕,套娃呢搁这儿\n所以采取另一种办法,用已有的Linux iso,通过解压其系统文件filesystem.squashfs,然后替换关键文件,最后实现自定义系统。","解压修改打包linux文件系统#解压、修改、打包Linux文件系统":"首先把Linux iso挂载到系统中。把里面的filesystem.squashfs文件复制出来(因为iso是只读的,复制出来后也方便管理和备份)。我这里以UOS1040为例,其文件系统在./live目录下\nsudo mount -o loop /xxx/xxx.iso /media/usrname/isofolder cd /media/usrname/isofolder/live/ cp ./filesystem.squashfs ~ 安装squashfs-tools工具,用来压缩解压归档文件。\nsudo apt install squashfs-tools #回到主目录后,解压filesystem.squashfs文件 sudo unsquashfs filesystem.squashfs 解压后可以看到主目录多一个squashfs-root目录,里面存放的就是一个Linux的文件系统了。这时候把你需要的文件拷贝进去,比如我替换了/usr、/opt、/etc三个目录,这三个目录保存了我安装的clonezilla应用及其配置程序,以及我编写的辅助脚本和自启动脚本。\n修改好了就可以打包了。第一个参数是刚刚解压出来修改好的文件系统,第二个参数是输出的名称。建议把之前拷贝出来的squashfs文件改名进行保存。\nsudo mksquashfs squashfs-root/ filesystem.squashfs","重建linux-iso#重建Linux iso":"最后我们需要重建ISO文件。由于我们是基于原来的ISO修改的,所以只需要替换iso里面的filesystem.squashfs文件即可。否则还需要更换系统内核img/lz、重写引导。\n把新生成的filesystem.squashfs文件拷贝到Windows系统里,打开UltraISO软件,直接替换Live目录下的filesystem.squashfs文件。同时我们也可以修改EFI/grub.cfg文件,毕竟这个系统主要是使用而不是安装。我调换了安装和试用的次序,并把等待时间增加到60s。\n最后进行修改ISO名称等小操作,保存为ISO文件就可以了。\n新生成的ISO文件既可以用rufus等工具烧录到U盘中制作PE盘,也可以试用ventoy工具制作成多系统启动盘,甚至可以烧录一张光盘来使用。"},"title":"定制自己的Linux ISO镜像"},"/posts/220223-deepin_partsinddefilemanager/":{"data":{"":"Deepin/UOS 里处理文件管理器显示太多盘符的问题。","原理原因#原理/原因":"这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。","处理方法#处理方法":"打开/etc/udev/rules.d目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。\n打开终端,使用root权限打开编辑\nsudo deepin-editor /etc/udev/rules.d/80-udisk2.rules 打开新的终端,输入执行 blkid -f ,记录LABLE、UUID两列\n​\t对比80-udisks2.rules的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。\n(#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换)\n​\t保存文件并重启计算机,即可看到效果。\n该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。","现象#现象":"在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在/etc/fstab文件添加或删除“”并没有效果。"},"title":"Deepin/UOS文件管理器隐藏不需要的盘符"},"/posts/220714-unpackclonezillaimage/":{"data":{"":"从clonezilla的镜像直接提取文件,免于先全盘全区恢复。\n再生龙(clonezilla)是一个常用的Linux系统备份工具,适用于多个版本、多种CPU架构的电脑。\n再生龙生成的镜像是比较特殊的格式,如果想要从里面提取文件,通常只能进行镜像恢复后进入系统提取。但是往往我们没有那么多硬盘来恢复系统。\n再生龙的原理简单来说就是把硬盘分区使用partclone工具打包成img文件,然后使用gzip进行分卷压缩。我们只需要逆向操作,利用工具,把镜像解包挂载,直接在当前系统挂载,就可以得到原分区的文件了。","1-环境需求#1. 环境需求":"我们操作再生龙镜像时,最好是使用Linux环境。这个环境中,需要安装partclone(也就是再生龙生成镜像的核心工具)。\nsudo apt install partclone # 以Debian系为例 并且我们需要准备一个足够大的硬盘,用来存储镜像包和解包出的img文件。硬盘/分区的空间需要大于镜像本来的分区空间。","2-合并镜像#2. 合并镜像":"再生龙默认情况下会把一个硬盘分区分按4GB的大小进行分包,通常以gz.aa、gz.ab等结尾,我们需要把这些包重新整合成一个gz包。\n整合成gz包后,我们就可以解压gz来得到镜像img了。我们可以把两个步骤用流的方式写入一条命令,节省硬盘空间。\ncat sda1.ext4-ptcl-img.gz.* | gzip -d -c \u003e sda1.img 这一步生成的img文件不能直接使用(google上也有说能用的,但是我试下来不能),需要进行一次转换。经过实际操作,这个文件所占用的空间是镜像原分区的大小,但是统计硬盘使用量时却只计算实际大小,希望了解的人可以帮忙解释一下。\npartclone.extfs -r -s sda1.img -o sda1-ex.img --restore_raw_file 当然上面两条指令也可以使用流的方式再次封装到一条,但是消耗的时间、失败后重新来过的时间,都会更长。","3挂载镜像#3.挂载镜像":"将最后生成的img镜像挂载到我们的/mnt(或其他)目录,进入这个目录就可以看到原来全部的文件了。\nsudo mount -o loop -t ext4 sda1-ex.img /mnt 最后我们只需要进入 /mnt 目录,就可以想直接操作硬盘一样把文件复制出来了。","挂载方法#挂载方法:":""},"title":"从再生龙(clonezilla)镜像直接提取文件"},"/posts/230508-remake-deb/":{"data":{"":"Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用?\n之前有一个需求,要从UOS里面提取一个软件,并且将其安装到另一台设备中。当时找了一圈都没有找到相关的提取教程。但是从逻辑上来讲,dpkg安装的软件会保存deb包的相关信息,所以理论上讲是可以提取出软件包的所有内容,然后我们重新打包的。\n今天在一个机缘巧合下(其实是懒得研究dpkg原理,瞎猫撞上死耗子),发现了dpkg保存文件信息的地点,于是接下来可以展开畅想:提取已经安装的软件并重新打包。","局限性#局限性":"以上毕竟只是设想,没有进行试验,所以估计会出现这些问题,希望有需要的人尝试后可以解决。\n提取信息的难度: 由于/usr/lib/dpkg-db/status文件是一个包含几乎所有deb信息的文本文件,体积大内容乱,对于自动化脚本来说有些难度。加上一些软件的衍生品会在软件名上直接加后缀,对于提取的脚本来讲可能造成混淆,提取出不必要的东西。\n获取源文件的难度: 不排除一些软件安装时会删除、修改自身的一些文件,可能打包会后会缺失内容。一些软件安装过程中会进行手动配置从而造成内容改变,重做出来的软件包不一定完整。\n签名问题: 我们打包的软件没有原作者的数字签名,可能无法安装,也可能时候安装官方软件时冲突。\n这个方案只能处理普通的软件,一些deb打包源码,安装时进行编译的,应该是不能用这种方案复原的。(apt的deb-src源)\n最后,希望有大佬可以验证一下这个方案。","文件的保存地点#文件的保存地点":"我在找fcitx有关的配置文件进行全盘搜索的时候,找到了一条目录/usr/lib/dpkg-db/info,这个目录里面保留了fcitx的\"conffiles\", “md5sums”, “postinst”, “prerm\"四个文件,以及一个list文件。恰好fcitx.deb解包出来的DEBIAN文件夹内也是这四个文件加上额外的control文件,。\n那么合理猜测,dpkg安装软件的时候,不仅把软件本身的文件移动到对应位置,还把DEBIAN里面的信息和脚本也转移到/usr/lib/dpkg-db/里面保存了。\n而fcitx.list文件中记录的是所有的文件及其路径。想必这里面就是全部的文件了。\n至于control文件,则都被保存到了/usr/lib/dpkg-db/status这个文本文件中。","流程设想#流程设想":"既然deb包所有必要的文件都找到了,那么就可以尝试一下还原过程了。在这篇文章里我研究过deb包的组成、格式和打包方法。\n比如对于软件包A:\n创建一个工作目录,并在其中新建DEBIAN目录。\n将/usr/lib/dpkg-db/info中所有该软件的脚本和信息(除了A.list)转移到DEBIAN中,并重命名成规范名称。\n将/usr/lib/dpkg-db/status中与软件A有关的部分提取出来,保存到DEBIAN/control文件中。\n根据A.list文件,找到软件包含的所有文件,将其按原样整理到工作目录内。\n使用dpkg -b指令重新打包。"},"title":"讨论在Debian系操作系统中重新提取已安装的软件并重新打包"},"/posts/230908-py-post/":{"data":{"":"使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况\n最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。\n服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。\nsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((self.host, self.port)) sock.listen(128) (client, address) = self.sock.accept() th = Thread(target=self.accept_request, args=(client, address)) def accept_request(client, address): data = client_sock.recv(8192) req = data.decode(\"utf-8\") # 处理数据的具体方法 response = process_response(req, client_addr) client_sock.send(response) # clean up client_sock.shutdown(1) client_sock.close() 上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。\n=====2024.01.04更新=======\n下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。\n其实用flask接收和处理数据会更加方便快捷:\nfrom flask import Flask, request # 实例化flask app = Flask(__name__) # 接收和处理post的方法 @app.route('/test', methods=['POST']) def recv_file_www(): # post的传入参数 print(request.args.to_dict()) # post所承载的数据 print(request.data) return 'OK' if __name__ == '__main__': app.run(port=31408, host=\"0.0.0.0\",debug=True) 上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。\n======以下是原来的内容==========\n经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,必须不断的recv才能完整接收数据。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。\n但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:\nPOST的报文头会包含一个Content-length的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。\n那么将上面代码的client_sock.recv()部分改写成下面的方法:\ndef recv(self, client_sock): lrecved = 8192 # 记录每次接收的长度 lrecv_all=0 # 记录数据总长度 data_body = bytes() # 接收的所有数据存入这里 payload_len = 0 # 解析的payload长度 payload_flag = 0 # 用于记录是否接收到了payload,以及实时记录接收到payload的长度 while True : part_body= client_sock.recv(8192) # 循环接收数据 data_body += part_body # 将接收的数据写入data_body lrecved = len(part_body) # 获取本次接收长度 lrecv_all += lrecved # 计算总长度 if payload_flag == 0: # 没有接收到payload部分时,才进入下面的判断流程 if data_body[0:3].decode() == 'GET': # 由于我get方法没出现过断开的问题,因此直接返回即可。 break elif data_body[0:4].decode() == 'POST': payload_len = count_payload_len(data_body) # 解析头部,计算payload部分的长度 if lrecved \u003c payload_len: # 偶尔会出现能接收到payload的情况,所以多加了一个判断。 # 能接收payload肯定总长度大于payload_len的 print(\"no payloads, keep recving...\") # 计算post报文的总长度,借用payload_flag变量 payload_flag = lrecved + payload_len # 当接收到的总长度 \u003e= POST的计算长度,表示所有数据接收完成,退出 if payload_flag \u003c= lrecv_all: break return data_body 里面解析headers的代码如下:\ndef count_payload_len(data): for d in data.split(b'\\r\\n'): # 通过\\r\\n来分隔报文头 if \"LENGTH\" in d.upper().decode(): # 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了 return int(d.decode().split(\":\")[1]) # 分隔出payload长度并返回 return 0 上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。\n对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。"},"title":"Python socket 收取POST协议内容时没有payload"},"/posts/231127-vncserver/":{"data":{"":"(笔记)在Linux系统里安装远程桌面VNC客户端并连接。","1-安装nvc服务端#1. 安装NVC服务端":"sudo apt install x11vnc","2-设置远程连接密码#2. 设置远程连接密码":"x11vnc --storepasswd # 输入两次连接密码以设定 # 询问 Write password to /home/xxx/.vnc/passwd # 时,输入y并回车保存密码。 该步骤不需要使用sudo!","3-使vnc服务开机自启动#3. 使VNC服务开机自启动":"使用service的方法,将vnc作为一个服务开机启动\n创建/lib/systemd/system/x11vnc.service文件并填入下面的内容\n[Unit] Description=Start x11vnc at startup. After=multi-user.target [Service] Type=simple ExecStart=/usr/bin/x11vnc -auth guess -rfbauth /home/xxx/.vnc/passwd [Install] WantedBy=multi-user.target 注意,/home/xxx 需要替换成实际的用户名!","使用vnc客户端连接#使用vnc客户端连接":"我使用Windows上的VncViewer,官网RealVNC,这个软件假冒的很多,下载需要注意甄别。"},"title":"Linux安装VNC服务端"},"/posts/231225-dockernote/":{"data":{"":"学习docker,使用docker,编写docker","docker-compose#docker-compose":"docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。\n首先创建一个工作目录,并创建一个docker-compose.yml的文件,默认情况下docker compose指令只认这个文件名。\nversion: '3'\rservices:\r[NAME1]:\rimage: \"image/name\"\rports:\r- 81:80\r- 8080:8080\renvironment:\rHOSTNAME: \"cc\"\rPORT: 8181\rvolumes:\r- nginx-data:/etc/nginx\r[NAME2]:\rimage: 'xxx' docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,ports字段指定了内外端口的映射,即’外部:内部’,卷映射同理。environment不需要 - 开头。\n编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动\ndocker compose up 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。\ndocker compose down 停止容器,并删除对应的容器和网络\ndocker compose exec NAME command 在ymal文件内其中一个容器里运行指令\ndocker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。\n目前浅学了一下,只有学一点更新一点吧。","docker-基本使用方法#docker 基本使用方法":"使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。\n# 拉取一个镜像 docker pull xxx/yyy # 创建容器并运行 docker run --name aaa xxx/yyy # 停止容器 docker stop docker-id 关于运行容器,有很多常用的参数:\n-d 容器转入后台运行\n-e 设置容器的环境变量\n–name 容器名称\n-p 80:80 将暴露的端口映射到容器内端口,外部:内部\n-v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内\n-e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。\n停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。\n查询容器id,可以使用指令docker ps。docker还有其他几个查询和控制指令\ndocker exec 容器 指令 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。\ndocker ps 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。\ndocker images 列出已经下载的镜像列表。与 docker image 指令不同。\ndocker kill 容器名/id 杀死一个无法stop的容器\ndocker rm 容器 删除一个容器\ndocker rmi 镜像名 删除一个镜像\ndocker restart 容器ID 重启一个容器,毕竟我们不需要每次都创建新容器。\n关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。","前言#前言":"祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。\n不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。\n由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。","安装docker和docker-compose#安装docker和docker compose":"虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。\napt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。\n# 添加docker官方的gpg curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加 echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release \u0026\u0026 echo \"$VERSION_CODENAME\") stable\" | sudo tee /etc/apt/sources.list.d/docker.list \u003e /dev/null # 我这边添加的结果是 deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu jammy stable 然后可以通过apt源安装docker了\nsudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin *注意,上面的指令同时安装了docker-compose。如果不安装docker,直接安装docker-compose也是可以的。\nwget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose 更加具体的步骤和细节可以到官网查看:docker安装、docker-compose安装","更换docker镜像源#更换docker镜像源":"和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。\n修改/etc/docker/daemon.json文件,添加下面的内容:\n{ \"registry-mirrors\": [ \"https://docker.mirrors.ustc.edu.cn\" ] } 编辑完成后需要重启docker服务。\n具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。"},"title":"docker简单笔记"},"/posts/about_hugo_website/":{"data":{"hugo的安装与首次创建#hugo的安装与首次创建":"安装使用chocolatey,一个windows下的仿apt、yum的软件包管理器。安装好chocolatey后,根据hugo官网的内容,choco install hugo -confirm安装基础的hugo(好处是不用再配置环境变量了)。\n为hugo创建目录,进入后hugo site new website,hugo会自动建立它所需要的层级结构。\n添加第一个页面,`hugo new xxx/xxx.md’,创建的内容会出现在content下,创建多级目录也适合将来归类。","主题皮肤#主题/皮肤":"主题放在themes下,我在知乎上找到了一个高赞且顺眼的,有哪些好看的Hugo主题? - 飞雪无情的回答 - 知乎,文章中附了github连接,那么我们直接git过来就完事了。","关于使用hugo建立博客#关于使用HUGO建立博客":"使用hugo建立本站的规划和记录。\n关于使用HUGO建立博客在经历了worldpress的性能消耗和慢速加载后,我现在决定换用hugo+caddy的建站模式。昨天已经完成了caddy2的基础设置,现在开始测试使用hugo。","生成网站#生成网站":"文章+主题都有了,现在可以生成第一个网站了(虽然写这些字的时候,网站还没生成)。hugo就能直接产生网页,保存至public文件夹。hugo server可以启动一个服务器预览静态网页。","编写文章#编写文章":"hugo的文章是基于markdown文档的,创建的文章会自动加入以下抬头\n--- title: \"$文章标题\" date: $时间 draft: true --- 这个可以修改为json或ymal格式,理应可以添加更多项目,这个之后再研究。"},"title":"关于使用HUGO建立博客站点"}} \ No newline at end of file diff --git a/public/en.search.js b/public/en.search.js new file mode 100644 index 0000000..f4ba18c --- /dev/null +++ b/public/en.search.js @@ -0,0 +1,490 @@ +// Search functionality using FlexSearch. + +// Change shortcut key to cmd+k on Mac, iPad or iPhone. +document.addEventListener("DOMContentLoaded", function () { + if (/iPad|iPhone|Macintosh/.test(navigator.userAgent)) { + // select the kbd element under the .hextra-search-wrapper class + const keys = document.querySelectorAll(".hextra-search-wrapper kbd"); + keys.forEach(key => { + key.innerHTML = 'K'; + }); + } +}); + +// Render the search data as JSON. +// +// +// +// + +(function () { + const searchDataURL = '/en.search-data.json'; + const resultsFoundTemplate = '找到 %d 个结果'; + + const inputElements = document.querySelectorAll('.hextra-search-input'); + for (const el of inputElements) { + el.addEventListener('focus', init); + el.addEventListener('keyup', search); + el.addEventListener('keydown', handleKeyDown); + el.addEventListener('input', handleInputChange); + } + + const shortcutElements = document.querySelectorAll('.hextra-search-wrapper kbd'); + + function setShortcutElementsOpacity(opacity) { + shortcutElements.forEach(el => { + el.style.opacity = opacity; + }); + } + + function handleInputChange(e) { + const opacity = e.target.value.length > 0 ? 0 : 100; + setShortcutElementsOpacity(opacity); + } + + // Get the search wrapper, input, and results elements. + function getActiveSearchElement() { + const inputs = Array.from(document.querySelectorAll('.hextra-search-wrapper')).filter(el => el.clientHeight > 0); + if (inputs.length === 1) { + return { + wrapper: inputs[0], + inputElement: inputs[0].querySelector('.hextra-search-input'), + resultsElement: inputs[0].querySelector('.hextra-search-results') + }; + } + return undefined; + } + + const INPUTS = ['input', 'select', 'button', 'textarea'] + + // Focus the search input when pressing ctrl+k/cmd+k or /. + document.addEventListener('keydown', function (e) { + const { inputElement } = getActiveSearchElement(); + if (!inputElement) return; + + const activeElement = document.activeElement; + const tagName = activeElement && activeElement.tagName; + if ( + inputElement === activeElement || + !tagName || + INPUTS.includes(tagName) || + (activeElement && activeElement.isContentEditable)) + return; + + if ( + e.key === '/' || + (e.key === 'k' && + (e.metaKey /* for Mac */ || /* for non-Mac */ e.ctrlKey)) + ) { + e.preventDefault(); + inputElement.focus(); + } else if (e.key === 'Escape' && inputElement.value) { + inputElement.blur(); + } + }); + + // Dismiss the search results when clicking outside the search box. + document.addEventListener('mousedown', function (e) { + const { inputElement, resultsElement } = getActiveSearchElement(); + if (!inputElement || !resultsElement) return; + if ( + e.target !== inputElement && + e.target !== resultsElement && + !resultsElement.contains(e.target) + ) { + setShortcutElementsOpacity(100); + hideSearchResults(); + } + }); + + // Get the currently active result and its index. + function getActiveResult() { + const { resultsElement } = getActiveSearchElement(); + if (!resultsElement) return { result: undefined, index: -1 }; + + const result = resultsElement.querySelector('.hextra-search-active'); + if (!result) return { result: undefined, index: -1 }; + + const index = parseInt(result.dataset.index, 10); + return { result, index }; + } + + // Set the active result by index. + function setActiveResult(index) { + const { resultsElement } = getActiveSearchElement(); + if (!resultsElement) return; + + const { result: activeResult } = getActiveResult(); + activeResult && activeResult.classList.remove('hextra-search-active'); + const result = resultsElement.querySelector(`[data-index="${index}"]`); + if (result) { + result.classList.add('hextra-search-active'); + result.focus(); + } + } + + // Get the number of search results from the DOM. + function getResultsLength() { + const { resultsElement } = getActiveSearchElement(); + if (!resultsElement) return 0; + return resultsElement.dataset.count; + } + + // Finish the search by hiding the results and clearing the input. + function finishSearch() { + const { inputElement } = getActiveSearchElement(); + if (!inputElement) return; + hideSearchResults(); + inputElement.value = ''; + inputElement.blur(); + } + + function hideSearchResults() { + const { resultsElement } = getActiveSearchElement(); + if (!resultsElement) return; + resultsElement.classList.add('hx:hidden'); + } + + // Handle keyboard events. + function handleKeyDown(e) { + const { inputElement } = getActiveSearchElement(); + if (!inputElement) return; + + const resultsLength = getResultsLength(); + const { result: activeResult, index: activeIndex } = getActiveResult(); + + switch (e.key) { + case 'ArrowUp': + e.preventDefault(); + if (activeIndex > 0) setActiveResult(activeIndex - 1); + break; + case 'ArrowDown': + e.preventDefault(); + if (activeIndex + 1 < resultsLength) setActiveResult(activeIndex + 1); + break; + case 'Enter': + e.preventDefault(); + if (activeResult) { + activeResult.click(); + } + finishSearch(); + case 'Escape': + e.preventDefault(); + hideSearchResults(); + // Clear the input when pressing escape + inputElement.value = ''; + inputElement.dispatchEvent(new Event('input')); + // Remove focus from the input + inputElement.blur(); + break; + } + } + + // Initializes the search. + function init(e) { + e.target.removeEventListener('focus', init); + if (!(window.pageIndex && window.sectionIndex)) { + preloadIndex(); + } + } + + /** + * Preloads the search index by fetching data and adding it to the FlexSearch index. + * @returns {Promise} A promise that resolves when the index is preloaded. + */ + async function preloadIndex() { + const tokenize = 'forward'; + + // https://github.com/TryGhost/Ghost/pull/21148 + const regex = new RegExp( + `[\u{4E00}-\u{9FFF}\u{3040}-\u{309F}\u{30A0}-\u{30FF}\u{AC00}-\u{D7A3}\u{3400}-\u{4DBF}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B73F}\u{2B740}-\u{2B81F}\u{2B820}-\u{2CEAF}\u{2CEB0}-\u{2EBEF}\u{30000}-\u{3134F}\u{31350}-\u{323AF}\u{2EBF0}-\u{2EE5F}\u{F900}-\u{FAFF}\u{2F800}-\u{2FA1F}]|[0-9A-Za-zа-я\u00C0-\u017F\u0400-\u04FF\u0600-\u06FF\u0980-\u09FF\u1E00-\u1EFF\u0590-\u05FF]+`, + 'mug' + ); + const encode = (str) => { return ('' + str).toLowerCase().match(regex) ?? []; } + + window.pageIndex = new FlexSearch.Document({ + tokenize, + encode, + cache: 100, + document: { + id: 'id', + store: ['title', 'crumb'], + index: "content" + } + }); + + window.sectionIndex = new FlexSearch.Document({ + tokenize, + encode, + cache: 100, + document: { + id: 'id', + store: ['title', 'content', 'url', 'display', 'crumb'], + index: "content", + tag: [{ + field: "pageId" + }] + } + }); + + const resp = await fetch(searchDataURL); + const data = await resp.json(); + let pageId = 0; + for (const route in data) { + let pageContent = ''; + ++pageId; + const urlParts = route.split('/').filter(x => x != "" && !x.startsWith('#')); + + let crumb = ''; + let searchUrl = '/'; + for (let i = 0; i < urlParts.length; i++) { + const urlPart = urlParts[i]; + searchUrl += urlPart + '/' + + const crumbData = data[searchUrl]; + if (!crumbData) { + console.debug('Excluded page', searchUrl, '- will not be included for search result breadcrumb for', route); + continue; + } + + let title = data[searchUrl].title; + if (title == "_index") { + title = urlPart.split("-").map(x => x).join(" "); + } + crumb += title; + + if (i < urlParts.length - 1) { + crumb += ' > '; + } + } + + for (const heading in data[route].data) { + const [hash, text] = heading.split('#'); + const url = route.trimEnd('/') + (hash ? '#' + hash : ''); + const title = text || data[route].title; + + const content = data[route].data[heading] || ''; + const paragraphs = content.split('\n').filter(Boolean); + + sectionIndex.add({ + id: url, + url, + title, + crumb, + pageId: `page_${pageId}`, + content: title, + ...(paragraphs[0] && { display: paragraphs[0] }) + }); + + for (let i = 0; i < paragraphs.length; i++) { + sectionIndex.add({ + id: `${url}_${i}`, + url, + title, + crumb, + pageId: `page_${pageId}`, + content: paragraphs[i] + }); + } + + pageContent += ` ${title} ${content}`; + } + + window.pageIndex.add({ + id: pageId, + title: data[route].title, + crumb, + content: pageContent + }); + + } + } + + /** + * Performs a search based on the provided query and displays the results. + * @param {Event} e - The event object. + */ + function search(e) { + const query = e.target.value; + if (!e.target.value) { + hideSearchResults(); + return; + } + + const { resultsElement } = getActiveSearchElement(); + while (resultsElement.firstChild) { + resultsElement.removeChild(resultsElement.firstChild); + } + resultsElement.classList.remove('hx:hidden'); + + // Configurable search limits with sensible defaults + const maxPageResults = parseInt('20', 10); + const maxSectionResults = parseInt('10', 10); + const pageResults = window.pageIndex.search(query, maxPageResults, { enrich: true, suggest: true })[0]?.result || []; + + const results = []; + const pageTitleMatches = {}; + + for (let i = 0; i < pageResults.length; i++) { + const result = pageResults[i]; + pageTitleMatches[i] = 0; + + const sectionResults = window.sectionIndex.search(query, + { enrich: true, suggest: true, tag: { 'pageId': `page_${result.id}` } })[0]?.result || []; + let isFirstItemOfPage = true + const occurred = {} + + const nResults = Math.min(sectionResults.length, maxSectionResults); + for (let j = 0; j < nResults; j++) { + const { doc } = sectionResults[j] + const isMatchingTitle = doc.display !== undefined + if (isMatchingTitle) { + pageTitleMatches[i]++ + } + const { url, title } = doc + const content = doc.display || doc.content + + if (occurred[url + '@' + content]) continue + occurred[url + '@' + content] = true + results.push({ + _page_rk: i, + _section_rk: j, + route: url, + prefix: isFirstItemOfPage ? result.doc.crumb : undefined, + children: { title, content } + }) + isFirstItemOfPage = false + } + } + const sortedResults = results + .sort((a, b) => { + // Sort by number of matches in the title. + if (a._page_rk === b._page_rk) { + return a._section_rk - b._section_rk + } + if (pageTitleMatches[a._page_rk] !== pageTitleMatches[b._page_rk]) { + return pageTitleMatches[b._page_rk] - pageTitleMatches[a._page_rk] + } + return a._page_rk - b._page_rk + }) + .map(res => ({ + id: `${res._page_rk}_${res._section_rk}`, + route: res.route, + prefix: res.prefix, + children: res.children + })); + displayResults(sortedResults, query); + } + + /** + * Displays the search results on the page. + * + * @param {Array} results - The array of search results. + * @param {string} query - The search query. + */ + function displayResults(results, query) { + const { resultsElement } = getActiveSearchElement(); + if (!resultsElement) return; + + if (!results.length) { + resultsElement.innerHTML = `无结果`; + // Announce no results to screen readers + const wrapper = resultsElement.closest('.hextra-search-wrapper'); + const statusEl = wrapper ? wrapper.querySelector('.hextra-search-status') : null; + if (statusEl) { + statusEl.textContent = '无结果'; + } + return; + } + + // Append text with highlighted matches using safe text nodes. + function appendHighlightedText(container, text, query) { + if (!text) return; + if (!query) { + container.textContent = text; + return; + } + + const escapedQuery = query.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); + if (!escapedQuery) { + container.textContent = text; + return; + } + + const regex = new RegExp(escapedQuery, 'gi'); + let lastIndex = 0; + let match; + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + container.appendChild(document.createTextNode(text.slice(lastIndex, match.index))); + } + const span = document.createElement('span'); + span.className = 'hextra-search-match'; + span.textContent = match[0]; + container.appendChild(span); + lastIndex = match.index + match[0].length; + } + if (lastIndex < text.length) { + container.appendChild(document.createTextNode(text.slice(lastIndex))); + } + } + + function handleMouseMove(e) { + const target = e.target.closest('a'); + if (target) { + const active = resultsElement.querySelector('a.hextra-search-active'); + if (active) { + active.classList.remove('hextra-search-active'); + } + target.classList.add('hextra-search-active'); + } + } + + const fragment = document.createDocumentFragment(); + for (let i = 0; i < results.length; i++) { + const result = results[i]; + if (result.prefix) { + const prefix = document.createElement('div'); + prefix.className = 'hextra-search-prefix'; + prefix.textContent = result.prefix; + fragment.appendChild(prefix); + } + const li = document.createElement('li'); + const link = document.createElement('a'); + link.dataset.index = i; + link.href = result.route; + if (i === 0) { + link.classList.add('hextra-search-active'); + } + + const title = document.createElement('div'); + title.className = 'hextra-search-title'; + appendHighlightedText(title, result.children.title, query); + link.appendChild(title); + + if (result.children.content) { + const excerpt = document.createElement('div'); + excerpt.className = 'hextra-search-excerpt'; + appendHighlightedText(excerpt, result.children.content, query); + link.appendChild(excerpt); + } + + li.appendChild(link); + li.addEventListener('mousemove', handleMouseMove); + li.addEventListener('keydown', handleKeyDown); + link.addEventListener('click', finishSearch); + fragment.appendChild(li); + } + resultsElement.appendChild(fragment); + resultsElement.dataset.count = results.length; + + // Announce results count to screen readers + const wrapper = resultsElement.closest('.hextra-search-wrapper'); + const statusEl = wrapper ? wrapper.querySelector('.hextra-search-status') : null; + if (statusEl) { + statusEl.textContent = results.length > 0 + ? resultsFoundTemplate.replace('%d', results.length.toString()) + : '无结果'; + } + } +})(); diff --git a/public/en.search.min.2cc417b6975b6621e049223d9f505ea2e6bd026e4cae8052a858eafc78cf230e.js b/public/en.search.min.2cc417b6975b6621e049223d9f505ea2e6bd026e4cae8052a858eafc78cf230e.js new file mode 100644 index 0000000..76d519d --- /dev/null +++ b/public/en.search.min.2cc417b6975b6621e049223d9f505ea2e6bd026e4cae8052a858eafc78cf230e.js @@ -0,0 +1,2 @@ +document.addEventListener("DOMContentLoaded",function(){if(/iPad|iPhone|Macintosh/.test(navigator.userAgent)){const e=document.querySelectorAll(".hextra-search-wrapper kbd");e.forEach(e=>{e.innerHTML='K'})}}),function(){const f="/en.search-data.json",h="找到 %d 个结果",c=document.querySelectorAll(".hextra-search-input");for(const e of c)e.addEventListener("focus",o),e.addEventListener("keyup",g),e.addEventListener("keydown",i),e.addEventListener("input",m);const u=document.querySelectorAll(".hextra-search-wrapper kbd");function n(e){u.forEach(t=>{t.style.opacity=e})}function m(e){const t=e.target.value.length>0?0:100;n(t)}function e(){const e=Array.from(document.querySelectorAll(".hextra-search-wrapper")).filter(e=>e.clientHeight>0);return e.length===1?{wrapper:e[0],inputElement:e[0].querySelector(".hextra-search-input"),resultsElement:e[0].querySelector(".hextra-search-results")}:0[0]}const l=["input","select","button","textarea"];document.addEventListener("keydown",function(t){const{inputElement:n}=e();if(!n)return;const s=document.activeElement,o=s&&s.tagName;if(n===s||!o||l.includes(o)||s&&s.isContentEditable)return;t.key==="/"||t.key==="k"&&(t.metaKey||t.ctrlKey)?(t.preventDefault(),n.focus()):t.key==="Escape"&&n.value&&n.blur()}),document.addEventListener("mousedown",function(s){const{inputElement:i,resultsElement:o}=e();if(!i||!o)return;s.target!==i&&s.target!==o&&!o.contains(s.target)&&(n(100),t())});function s(){const{resultsElement:n}=e();if(!n)return{result:0[0],index:-1};const t=n.querySelector(".hextra-search-active");if(!t)return{result:0[0],index:-1};const s=parseInt(t.dataset.index,10);return{result:t,index:s}}function r(t){const{resultsElement:o}=e();if(!o)return;const{result:i}=s();i&&i.classList.remove("hextra-search-active");const n=o.querySelector(`[data-index="${t}"]`);n&&(n.classList.add("hextra-search-active"),n.focus())}function d(){const{resultsElement:t}=e();return t?t.dataset.count:0}function a(){const{inputElement:n}=e();if(!n)return;t(),n.value="",n.blur()}function t(){const{resultsElement:t}=e();if(!t)return;t.classList.add("hx:hidden")}function i(n){const{inputElement:o}=e();if(!o)return;const{result:c,index:i}=s(),l=d();switch(n.key){case"ArrowUp":n.preventDefault(),i>0&&r(i-1);break;case"ArrowDown":n.preventDefault(),i+1(""+e).toLowerCase().match(o)??[];window.pageIndex=new FlexSearch.Document({tokenize:n,encode:s,cache:100,document:{id:"id",store:["title","crumb"],index:"content"}}),window.sectionIndex=new FlexSearch.Document({tokenize:n,encode:s,cache:100,document:{id:"id",store:["title","content","url","display","crumb"],index:"content",tag:[{field:"pageId"}]}});const i=await fetch(f),e=await i.json();let t=0;for(const n in e){let a="";++t;const i=n.split("/").filter(e=>e!=""&&!e.startsWith("#"));let s="",o="/";for(let t=0;te).join(" ")),s+=a,t ")}for(const c in e[n].data){const[l,u]=c.split("#"),o=n.trimEnd("/")+(l?"#"+l:""),i=u||e[n].title,d=e[n].data[c]||"",r=d.split(` +`).filter(Boolean);sectionIndex.add({id:o,url:o,title:i,crumb:s,pageId:`page_${t}`,content:i,...r[0]&&{display:r[0]}});for(let e=0;ee._page_rk===t._page_rk?e._section_rk-t._section_rk:s[e._page_rk]!==s[t._page_rk]?s[t._page_rk]-s[e._page_rk]:e._page_rk-t._page_rk).map(e=>({id:`${e._page_rk}_${e._section_rk}`,route:e.route,prefix:e.prefix,children:e.children}));v(d,i)}function v(t,n){const{resultsElement:s}=e();if(!s)return;if(!t.length){s.innerHTML=`无结果`;const e=s.closest(".hextra-search-wrapper"),t=e?e.querySelector(".hextra-search-status"):null;t&&(t.textContent="无结果");return}function r(e,t,n){if(!t)return;if(!n){e.textContent=t;return}const i=n.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&");if(!i){e.textContent=t;return}const a=new RegExp(i,"gi");let o=0,s;for(;(s=a.exec(t))!==null;){s.index>o&&e.appendChild(document.createTextNode(t.slice(o,s.index)));const n=document.createElement("span");n.className="hextra-search-match",n.textContent=s[0],e.appendChild(n),o=s.index+s[0].length}o0?h.replace("%d",t.length.toString()):"无结果")}}() \ No newline at end of file diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..0f2dd2b Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..5c1aea5 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..553fa15 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..6a08d10 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,13 @@ + + + diff --git a/public/images/logo-dark.svg b/public/images/logo-dark.svg new file mode 100644 index 0000000..2857264 --- /dev/null +++ b/public/images/logo-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/logo.svg b/public/images/logo.svg new file mode 100644 index 0000000..1ed7daf --- /dev/null +++ b/public/images/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..11adb89 --- /dev/null +++ b/public/index.html @@ -0,0 +1,684 @@ + + + + + + + + +香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + +
    + + + +
    +
    + + + + + + + + diff --git a/public/index.xml b/public/index.xml new file mode 100644 index 0000000..a65cbc1 --- /dev/null +++ b/public/index.xml @@ -0,0 +1,1927 @@ + + + 香风家的火柴盒 – 香风家的火柴盒 + https://www.small09.top/ + Recent content on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 25 Dec 2023 09:06:25 +0800 + + + + + + + + + + docker简单笔记 + https://www.small09.top/posts/231225-dockernote/ + Mon, 25 Dec 2023 09:06:25 +0800 + + https://www.small09.top/posts/231225-dockernote/ + + + + <p>学习docker,使用docker,编写docker</p> +<h2>前言<span class="hx:absolute hx:-mt-20" id="前言"></span> + <a href="#%e5%89%8d%e8%a8%80" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。</p> +<p>不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。</p> +<p>由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。</p> +<h2>安装docker和docker compose<span class="hx:absolute hx:-mt-20" id="安装docker和docker-compose"></span> + <a href="#%e5%ae%89%e8%a3%85docker%e5%92%8cdocker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。</p> +<p>apt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 添加docker官方的gpg</span> +</span></span><span style="display:flex;"><span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加</span> +</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;deb [arch=</span><span style="color:#66d9ef">$(</span>dpkg --print-architecture<span style="color:#66d9ef">)</span><span style="color:#e6db74"> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu </span><span style="color:#66d9ef">$(</span>. /etc/os-release <span style="color:#f92672">&amp;&amp;</span> echo <span style="color:#e6db74">&#34;</span>$VERSION_CODENAME<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74"> stable&#34;</span> | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 我这边添加的结果是</span> +</span></span><span style="display:flex;"><span>deb <span style="color:#f92672">[</span>arch<span style="color:#f92672">=</span>amd64 signed-by<span style="color:#f92672">=</span>/usr/share/keyrings/docker-archive-keyring.gpg<span style="color:#f92672">]</span> https://download.docker.com/linux/ubuntu jammy stable</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后可以通过apt源安装docker了</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update +</span></span><span style="display:flex;"><span>sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>*<em>注意,上面的指令同时安装了docker-compose</em>。如果不安装docker,直接安装docker-compose也是可以的。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 +</span></span><span style="display:flex;"><span>sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>更加具体的步骤和细节可以到官网查看:<a href="https://docs.docker.com/engine/install/ubuntu/"target="_blank" rel="noopener">docker安装</a>、<a href="https://docs.docker.com/compose/install/standalone/"target="_blank" rel="noopener">docker-compose安装</a></p> +<h2>docker 基本使用方法<span class="hx:absolute hx:-mt-20" id="docker-基本使用方法"></span> + <a href="#docker-%e5%9f%ba%e6%9c%ac%e4%bd%bf%e7%94%a8%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 拉取一个镜像</span> +</span></span><span style="display:flex;"><span>docker pull xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 创建容器并运行</span> +</span></span><span style="display:flex;"><span>docker run --name aaa xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 停止容器</span> +</span></span><span style="display:flex;"><span>docker stop docker-id</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>关于运行容器,有很多常用的参数:</p> +<blockquote> + <p>-d 容器转入后台运行</p> +<p>-e 设置容器的环境变量</p> +<p>&ndash;name 容器名称</p> +<p>-p 80:80 将暴露的端口映射到容器内端口,外部:内部</p> +<p>-v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内</p> +<p>-e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。</p> + +</blockquote> +<p>停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。</p> +<p>查询容器id,可以使用指令<code>docker ps</code>。docker还有其他几个查询和控制指令</p> +<blockquote> + <p><code>docker exec 容器 指令</code> 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。</p> +<p><code>docker ps</code> 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。</p> +<p><code>docker images</code> 列出已经下载的镜像列表。与 <code>docker image</code> 指令不同。</p> +<p><code>docker kill 容器名/id</code> 杀死一个无法stop的容器</p> +<p><code>docker rm 容器</code> 删除一个<strong>容器</strong></p> +<p><code>docker rmi 镜像名</code> 删除一个<strong>镜像</strong></p> +<p><code>docker restart 容器ID</code> 重启一个容器,毕竟我们不需要每次都创建新容器。</p> + +</blockquote> +<p>关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。</p> +<h2>更换docker镜像源<span class="hx:absolute hx:-mt-20" id="更换docker镜像源"></span> + <a href="#%e6%9b%b4%e6%8d%a2docker%e9%95%9c%e5%83%8f%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。</p> +<p>修改<code>/etc/docker/daemon.json</code>文件,添加下面的内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;registry-mirrors&#34;</span>: [ +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;https://docker.mirrors.ustc.edu.cn&#34;</span> +</span></span><span style="display:flex;"><span> ] +</span></span><span style="display:flex;"><span>}</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>编辑完成后需要重启docker服务。</p> +<p>具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。</p> +<h2>docker-compose<span class="hx:absolute hx:-mt-20" id="docker-compose"></span> + <a href="#docker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。</p> +<p>首先创建一个工作目录,并创建一个<code>docker-compose.yml</code>的文件,默认情况下docker compose指令只认这个文件名。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>version: &#39;3&#39; +services: + [NAME1]: + image: &#34;image/name&#34; + ports: + - 81:80 + - 8080:8080 + environment: + HOSTNAME: &#34;cc&#34; + PORT: 8181 + volumes: + - nginx-data:/etc/nginx + [NAME2]: + image: &#39;xxx&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,<code>ports</code>字段指定了内外端口的映射,即&rsquo;外部:内部&rsquo;,卷映射同理。<code>environment</code>不需要 - 开头。</p> +<p>编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动</p> +<blockquote> + <p><code>docker compose up</code> 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。</p> +<p><code>docker compose down</code> 停止容器,并删除对应的容器和网络</p> +<p><code>docker compose exec NAME command</code> 在ymal文件内其中一个容器里运行指令</p> + +</blockquote> +<p>docker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。</p> +<p>目前浅学了一下,只有学一点更新一点吧。</p> + + + + + Linux安装VNC服务端 + https://www.small09.top/posts/231127-vncserver/ + Mon, 27 Nov 2023 09:33:32 +0800 + + https://www.small09.top/posts/231127-vncserver/ + + + + <p><em>(笔记)在Linux系统里安装远程桌面VNC客户端并连接。</em></p> +<h3>1. 安装NVC服务端<span class="hx:absolute hx:-mt-20" id="1-安装nvc服务端"></span> + <a href="#1-%e5%ae%89%e8%a3%85nvc%e6%9c%8d%e5%8a%a1%e7%ab%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install x11vnc</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h3>2. 设置远程连接密码<span class="hx:absolute hx:-mt-20" id="2-设置远程连接密码"></span> + <a href="#2-%e8%ae%be%e7%bd%ae%e8%bf%9c%e7%a8%8b%e8%bf%9e%e6%8e%a5%e5%af%86%e7%a0%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>x11vnc --storepasswd +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 输入两次连接密码以设定</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 询问 Write password to /home/xxx/.vnc/passwd </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 时,输入y并回车保存密码。</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ul> +<li>该步骤不需要使用sudo!</li> +</ul> +<h3>3. 使VNC服务开机自启动<span class="hx:absolute hx:-mt-20" id="3-使vnc服务开机自启动"></span> + <a href="#3-%e4%bd%bfvnc%e6%9c%8d%e5%8a%a1%e5%bc%80%e6%9c%ba%e8%87%aa%e5%90%af%e5%8a%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>使用service的方法,将vnc作为一个服务开机启动</p> +<p>创建<code>/lib/systemd/system/x11vnc.service</code>文件并填入下面的内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Start x11vnc at startup.</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">simple</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/x11vnc -auth guess -rfbauth /home/xxx/.vnc/passwd</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ul> +<li>注意,/home/xxx 需要替换成实际的用户名!</li> +</ul> +<h3>使用vnc客户端连接<span class="hx:absolute hx:-mt-20" id="使用vnc客户端连接"></span> + <a href="#%e4%bd%bf%e7%94%a8vnc%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>我使用Windows上的VncViewer,官网<a href="https://www.realvnc.com/en/"target="_blank" rel="noopener">RealVNC</a>,这个软件假冒的很多,下载需要注意甄别。</p> + + + + + Python socket 收取POST协议内容时没有payload + https://www.small09.top/posts/230908-py-post/ + Fri, 08 Sep 2023 00:00:00 +0800 + + https://www.small09.top/posts/230908-py-post/ + + + + <p>使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况</p> +<p>最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。</p> +<p>服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sock <span style="color:#f92672">=</span> socket<span style="color:#f92672">.</span>socket(socket<span style="color:#f92672">.</span>AF_INET, socket<span style="color:#f92672">.</span>SOCK_STREAM) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>bind((self<span style="color:#f92672">.</span>host, self<span style="color:#f92672">.</span>port)) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>listen(<span style="color:#ae81ff">128</span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>(client, address) <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>sock<span style="color:#f92672">.</span>accept() +</span></span><span style="display:flex;"><span>th <span style="color:#f92672">=</span> Thread(target<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>accept_request, args<span style="color:#f92672">=</span>(client, address)) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">accept_request</span>(client, address): +</span></span><span style="display:flex;"><span> data <span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) +</span></span><span style="display:flex;"><span> req <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>decode(<span style="color:#e6db74">&#34;utf-8&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 处理数据的具体方法</span> +</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> process_response(req, client_addr) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>send(response) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># clean up</span> +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>shutdown(<span style="color:#ae81ff">1</span>) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>close()</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。</p> +<p><strong>=====2024.01.04更新=======</strong></p> +<p>下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。</p> +<p>其实用flask接收和处理数据会更加方便快捷:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> flask <span style="color:#f92672">import</span> Flask, request +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 实例化flask</span> +</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> Flask(__name__) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 接收和处理post的方法</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.route</span>(<span style="color:#e6db74">&#39;/test&#39;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#39;POST&#39;</span>]) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv_file_www</span>(): +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post的传入参数</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>args<span style="color:#f92672">.</span>to_dict()) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post所承载的数据</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>data) +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;OK&#39;</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;__main__&#39;</span>: +</span></span><span style="display:flex;"><span> app<span style="color:#f92672">.</span>run(port<span style="color:#f92672">=</span><span style="color:#ae81ff">31408</span>, host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0.0.0.0&#34;</span>,debug<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。</p> +<p><strong>======以下是原来的内容==========</strong></p> +<p>经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,<strong>必须不断的recv才能完整接收数据</strong>。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。</p> +<p>但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:</p> +<p>POST的报文头会包含一个<code>Content-length</code>的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。</p> +<p>那么将上面代码的<code>client_sock.recv()</code>部分改写成下面的方法:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv</span>(self, client_sock): +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> <span style="color:#ae81ff">8192</span> <span style="color:#75715e"># 记录每次接收的长度</span> +</span></span><span style="display:flex;"><span> lrecv_all<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> <span style="color:#75715e"># 记录数据总长度 </span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">=</span> bytes() <span style="color:#75715e"># 接收的所有数据存入这里</span> +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 解析的payload长度</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 用于记录是否接收到了payload,以及实时记录接收到payload的长度</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span> : +</span></span><span style="display:flex;"><span> part_body<span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) <span style="color:#75715e"># 循环接收数据</span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">+=</span> part_body <span style="color:#75715e"># 将接收的数据写入data_body</span> +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> len(part_body) <span style="color:#75715e"># 获取本次接收长度</span> +</span></span><span style="display:flex;"><span> lrecv_all <span style="color:#f92672">+=</span> lrecved <span style="color:#75715e"># 计算总长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>: <span style="color:#75715e"># 没有接收到payload部分时,才进入下面的判断流程</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">3</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;GET&#39;</span>: <span style="color:#75715e"># 由于我get方法没出现过断开的问题,因此直接返回即可。</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;POST&#39;</span>: +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> count_payload_len(data_body) <span style="color:#75715e"># 解析头部,计算payload部分的长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> lrecved <span style="color:#f92672">&lt;</span> payload_len: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 偶尔会出现能接收到payload的情况,所以多加了一个判断。</span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 能接收payload肯定总长度大于payload_len的</span> +</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;no payloads, keep recving...&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 计算post报文的总长度,借用payload_flag变量</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> lrecved <span style="color:#f92672">+</span> payload_len +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 当接收到的总长度 &gt;= POST的计算长度,表示所有数据接收完成,退出</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">&lt;=</span> lrecv_all: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> data_body</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>里面解析headers的代码如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">count_payload_len</span>(data): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> d <span style="color:#f92672">in</span> data<span style="color:#f92672">.</span>split(<span style="color:#e6db74">b</span><span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">&#39;</span>): <span style="color:#75715e"># 通过\r\n来分隔报文头</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;LENGTH&#34;</span> <span style="color:#f92672">in</span> d<span style="color:#f92672">.</span>upper()<span style="color:#f92672">.</span>decode(): <span style="color:#75715e"># 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> int(d<span style="color:#f92672">.</span>decode()<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;:&#34;</span>)[<span style="color:#ae81ff">1</span>]) <span style="color:#75715e"># 分隔出payload长度并返回</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。</p> +<p>对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。</p> + + + + + 讨论在Debian系操作系统中重新提取已安装的软件并重新打包 + https://www.small09.top/posts/230508-remake-deb/ + Mon, 08 May 2023 14:43:41 +0800 + + https://www.small09.top/posts/230508-remake-deb/ + + + + <p>Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用?</p> +<p>之前有一个需求,要从UOS里面提取一个软件,并且将其安装到另一台设备中。当时找了一圈都没有找到相关的提取教程。但是从逻辑上来讲,dpkg安装的软件会保存deb包的相关信息,所以理论上讲是可以提取出软件包的所有内容,然后我们重新打包的。</p> +<p>今天在一个机缘巧合下(其实是懒得研究dpkg原理,瞎猫撞上死耗子),发现了dpkg保存文件信息的地点,于是接下来可以展开畅想:提取已经安装的软件并重新打包。</p> +<h2>文件的保存地点<span class="hx:absolute hx:-mt-20" id="文件的保存地点"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%9a%84%e4%bf%9d%e5%ad%98%e5%9c%b0%e7%82%b9" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我在找fcitx有关的配置文件进行全盘搜索的时候,找到了一条目录<code>/usr/lib/dpkg-db/info</code>,这个目录里面保留了fcitx的&quot;conffiles&quot;, &ldquo;md5sums&rdquo;, &ldquo;postinst&rdquo;, &ldquo;prerm&quot;四个文件,以及一个list文件。恰好fcitx.deb解包出来的DEBIAN文件夹内也是这四个文件加上额外的control文件,。</p> +<p>那么合理猜测,dpkg安装软件的时候,不仅把软件本身的文件移动到对应位置,还把DEBIAN里面的信息和脚本也转移到<code>/usr/lib/dpkg-db/</code>里面保存了。</p> +<p>而<code>fcitx.list</code>文件中记录的是所有的文件及其路径。想必这里面就是全部的文件了。</p> +<p>至于control文件,则都被保存到了<code>/usr/lib/dpkg-db/status</code>这个文本文件中。</p> +<h2>流程设想<span class="hx:absolute hx:-mt-20" id="流程设想"></span> + <a href="#%e6%b5%81%e7%a8%8b%e8%ae%be%e6%83%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>既然deb包所有必要的文件都找到了,那么就可以尝试一下还原过程了。在<a href="https://www.small09.top/posts/211019-dpkg_package_guide/"target="_blank" rel="noopener">这篇文章</a>里我研究过deb包的组成、格式和打包方法。</p> +<p>比如对于软件包A:</p> +<ol> +<li> +<p>创建一个工作目录,并在其中新建<code>DEBIAN</code>目录。</p> +</li> +<li> +<p>将<code>/usr/lib/dpkg-db/info</code>中所有该软件的脚本和信息(除了A.list)转移到<code>DEBIAN</code>中,并重命名成规范名称。</p> +</li> +<li> +<p>将<code>/usr/lib/dpkg-db/status</code>中与软件A有关的部分提取出来,保存到<code>DEBIAN/control</code>文件中。</p> +</li> +<li> +<p>根据A.list文件,找到软件包含的所有文件,将其按原样整理到工作目录内。</p> +</li> +<li> +<p>使用<code>dpkg -b</code>指令重新打包。</p> +</li> +</ol> +<h2>局限性<span class="hx:absolute hx:-mt-20" id="局限性"></span> + <a href="#%e5%b1%80%e9%99%90%e6%80%a7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上毕竟只是设想,没有进行试验,所以估计会出现这些问题,希望有需要的人尝试后可以解决。</p> +<ul> +<li> +<p>提取信息的难度: 由于<code>/usr/lib/dpkg-db/status</code>文件是一个包含几乎所有deb信息的文本文件,体积大内容乱,对于自动化脚本来说有些难度。加上一些软件的衍生品会在软件名上直接加后缀,对于提取的脚本来讲可能造成混淆,提取出不必要的东西。</p> +</li> +<li> +<p>获取源文件的难度: 不排除一些软件安装时会删除、修改自身的一些文件,可能打包会后会缺失内容。一些软件安装过程中会进行手动配置从而造成内容改变,重做出来的软件包不一定完整。</p> +</li> +<li> +<p>签名问题: 我们打包的软件没有原作者的数字签名,可能无法安装,也可能时候安装官方软件时冲突。</p> +</li> +<li> +<p>这个方案只能处理普通的软件,一些deb打包源码,安装时进行编译的,应该是不能用这种方案复原的。(apt的deb-src源)</p> +</li> +</ul> +<p>最后,希望有大佬可以验证一下这个方案。</p> + + + + + 从再生龙(clonezilla)镜像直接提取文件 + https://www.small09.top/posts/220714-unpackclonezillaimage/ + Thu, 14 Jul 2022 13:05:51 +0800 + + https://www.small09.top/posts/220714-unpackclonezillaimage/ + + + + <p>从clonezilla的镜像直接提取文件,免于先全盘全区恢复。</p> +<p>再生龙(clonezilla)是一个常用的Linux系统备份工具,适用于多个版本、多种CPU架构的电脑。</p> +<p>再生龙生成的镜像是比较特殊的格式,如果想要从里面提取文件,通常只能进行镜像恢复后进入系统提取。但是往往我们没有那么多硬盘来恢复系统。</p> +<p>再生龙的原理简单来说就是把硬盘分区使用partclone工具打包成img文件,然后使用gzip进行分卷压缩。我们只需要逆向操作,利用工具,把镜像解包挂载,直接在当前系统挂载,就可以得到原分区的文件了。</p> +<h2>挂载方法:<span class="hx:absolute hx:-mt-20" id="挂载方法"></span> + <a href="#%e6%8c%82%e8%bd%bd%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>1. 环境需求<span class="hx:absolute hx:-mt-20" id="1-环境需求"></span> + <a href="#1-%e7%8e%af%e5%a2%83%e9%9c%80%e6%b1%82" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>我们操作再生龙镜像时,最好是使用Linux环境。这个环境中,需要安装partclone(也就是再生龙生成镜像的核心工具)。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install partclone +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以Debian系为例</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>并且我们需要准备一个足够大的硬盘,用来存储镜像包和解包出的img文件。硬盘/分区的空间需要大于镜像本来的<strong>分区</strong>空间。</p> +<h3>2. 合并镜像<span class="hx:absolute hx:-mt-20" id="2-合并镜像"></span> + <a href="#2-%e5%90%88%e5%b9%b6%e9%95%9c%e5%83%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>再生龙默认情况下会把一个硬盘分区分按4GB的大小进行分包,通常以<code>gz.aa</code>、<code>gz.ab</code>等结尾,我们需要把这些包重新整合成一个gz包。</p> +<p>整合成gz包后,我们就可以解压gz来得到镜像img了。我们可以把两个步骤用流的方式写入一条命令,节省硬盘空间。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat sda1.ext4-ptcl-img.gz.* | gzip -d -c &gt; sda1.img</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这一步生成的img文件不能直接使用(google上也有说能用的,但是我试下来不能),需要进行一次转换。经过实际操作,这个文件所占用的空间是镜像原分区的大小,但是统计硬盘使用量时却只计算实际大小,希望了解的人可以帮忙解释一下。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>partclone.extfs -r -s sda1.img -o sda1-ex.img --restore_raw_file</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>当然上面两条指令也可以使用流的方式再次封装到一条,但是消耗的时间、失败后重新来过的时间,都会更长。</p> +<h2>3.挂载镜像<span class="hx:absolute hx:-mt-20" id="3挂载镜像"></span> + <a href="#3%e6%8c%82%e8%bd%bd%e9%95%9c%e5%83%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>将最后生成的img镜像挂载到我们的/mnt(或其他)目录,进入这个目录就可以看到原来全部的文件了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -o loop -t ext4 sda1-ex.img /mnt</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>最后我们只需要进入 <code>/mnt</code> 目录,就可以想直接操作硬盘一样把文件复制出来了。</p> + + + + + Deepin/UOS文件管理器隐藏不需要的盘符 + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + Wed, 23 Feb 2022 09:18:55 +0800 + + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + + + + <p>Deepin/UOS 里处理文件管理器显示太多盘符的问题。</p> +<h2>现象<span class="hx:absolute hx:-mt-20" id="现象"></span> + <a href="#%e7%8e%b0%e8%b1%a1" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在<code>/etc/fstab</code>文件添加或删除“”并没有效果。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230924406.png" alt="image-20220223090517363" loading="lazy" /></p> +<h2>原理/原因<span class="hx:absolute hx:-mt-20" id="原理原因"></span> + <a href="#%e5%8e%9f%e7%90%86%e5%8e%9f%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。</p> +<h2>处理方法<span class="hx:absolute hx:-mt-20" id="处理方法"></span> + <a href="#%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li> +<p>打开<code>/etc/udev/rules.d</code>目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。</p> +</li> +<li> +<p>打开终端,使用root权限打开编辑</p> +</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo deepin-editor /etc/udev/rules.d/80-udisk2.rules</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ol start="3"> +<li> +<p><strong>打开新的终端</strong>,输入执行 <code>blkid -f</code> ,记录LABLE、UUID两列</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230928320.png" alt="截图录屏_选择区域_20220223090208" loading="lazy" /></p> +</li> +<li> +<p>对比<code>80-udisks2.rules</code>的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。</p> +<p>(#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换)</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230927888.png" alt="截图录屏_选择区域_20220223090606" loading="lazy" /></p> +</li> +<li> +<p>保存文件并重启计算机,即可看到效果。</p> +</li> +<li> +<p>该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。</p> +</li> +</ol> + + + + + 定制自己的Linux ISO镜像 + https://www.small09.top/posts/220104-diylinuxiso/ + Tue, 04 Jan 2022 09:17:38 +0800 + + https://www.small09.top/posts/220104-diylinuxiso/ + + + + <p>想要在原版的Linux镜像上添加自定义的软件,但是不想、不会编译,那么就可以基于Linux的iso文件进行修改,定制自己的安装试用镜像。</p> +<p>起因是因为对付信创操作系统的备份恢复时,UOS、麒麟自带的工具都超级不好用,两个系统的镜像不统一,导致实际使用的时候各种问题和不方便。所以使用clonezilla再生龙工具自己做一个PE镜像。这个镜像不仅可以备份、安装系统,还能够修改硬盘大小、应急拯救系统等多种功能。并且它是一个完整的系统可以安装使用。</p> +<h2>基本计划<span class="hx:absolute hx:-mt-20" id="基本计划"></span> + <a href="#%e5%9f%ba%e6%9c%ac%e8%ae%a1%e5%88%92" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>之前尝试过使用<a href="http://bbs.wuyou.net/forum.php?mod=viewthread&amp;tid=409980"target="_blank" rel="noopener">deepin魔改lub工具</a>制作系统的完整镜像,但是启动总是失败。<a href="https://forum.ubuntu.org.cn/viewtopic.php?t=206287"target="_blank" rel="noopener">LUB</a>全称是<em>Live Ubuntu Backup</em>,是Ubuntu的一个工具,到了同为Debian系的deepin魔改的UOS上就水土不服了。(好绕,套娃呢搁这儿</p> +<p>所以采取另一种办法,用已有的Linux iso,通过解压其系统文件filesystem.squashfs,然后替换关键文件,最后实现自定义系统。</p> +<h2>解压、修改、打包Linux文件系统<span class="hx:absolute hx:-mt-20" id="解压修改打包linux文件系统"></span> + <a href="#%e8%a7%a3%e5%8e%8b%e4%bf%ae%e6%94%b9%e6%89%93%e5%8c%85linux%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>首先把Linux iso挂载到系统中。把里面的filesystem.squashfs文件复制出来(因为iso是只读的,复制出来后也方便管理和备份)。我这里以UOS1040为例,其文件系统在<code>./live</code>目录下</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -o loop /xxx/xxx.iso /media/usrname/isofolder +</span></span><span style="display:flex;"><span>cd /media/usrname/isofolder/live/ +</span></span><span style="display:flex;"><span>cp ./filesystem.squashfs ~</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>安装<code>squashfs-tools</code>工具,用来压缩解压归档文件。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install squashfs-tools +</span></span><span style="display:flex;"><span><span style="color:#75715e">#回到主目录后,解压filesystem.squashfs文件</span> +</span></span><span style="display:flex;"><span>sudo unsquashfs filesystem.squashfs</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>解压后可以看到主目录多一个<code>squashfs-root</code>目录,里面存放的就是一个Linux的文件系统了。这时候把你需要的文件拷贝进去,比如我替换了/usr、/opt、/etc三个目录,这三个目录保存了我安装的clonezilla应用及其配置程序,以及我编写的辅助脚本和自启动脚本。</p> +<p>修改好了就可以打包了。第一个参数是刚刚解压出来修改好的文件系统,第二个参数是输出的名称。建议把之前拷贝出来的squashfs文件改名进行保存。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mksquashfs squashfs-root/ filesystem.squashfs</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>重建Linux iso<span class="hx:absolute hx:-mt-20" id="重建linux-iso"></span> + <a href="#%e9%87%8d%e5%bb%balinux-iso" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最后我们需要重建ISO文件。由于我们是基于原来的ISO修改的,所以只需要替换iso里面的filesystem.squashfs文件即可。否则还需要更换系统内核img/lz、重写引导。</p> +<p>把新生成的filesystem.squashfs文件拷贝到Windows系统里,打开UltraISO软件,直接替换Live目录下的filesystem.squashfs文件。同时我们也可以修改<code>EFI/grub.cfg</code>文件,毕竟这个系统主要是使用而不是安装。我调换了安装和试用的次序,并把等待时间增加到60s。</p> +<p>最后进行修改ISO名称等小操作,保存为ISO文件就可以了。</p> +<p>新生成的ISO文件既可以用rufus等工具烧录到U盘中制作PE盘,也可以试用ventoy工具制作成多系统启动盘,甚至可以烧录一张光盘来使用。</p> + + + + + 在Linux环境下修改用户名和主目录 + https://www.small09.top/posts/211203-linuxchangeusername/ + Fri, 03 Dec 2021 09:01:23 +0800 + + https://www.small09.top/posts/211203-linuxchangeusername/ + + + + <p>如何在Linux下完整修改当前用户名和主目录,还不影响其他文件?</p> +<p>Linux的用户名是一个用户的字符串表示,是可以修改的。只要用户的id不改变就不会被系统认作新用户,所有的权限都可以继承。下面介绍如何修改用户的名称、所属组,并更改主目录的名称与对应关系。</p> +<h2>一、进入“单用户模式”<span class="hx:absolute hx:-mt-20" id="一进入单用户模式"></span> + <a href="#%e4%b8%80%e8%bf%9b%e5%85%a5%e5%8d%95%e7%94%a8%e6%88%b7%e6%a8%a1%e5%bc%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>为了修改用户名时不影响程序运行,需要把当前用户打开的所有程序都关闭。该方法可以快速注销当前用户。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>init <span style="color:#ae81ff">1</span> +</span></span><span style="display:flex;"><span>执行后输入当前用户密码 +</span></span><span style="display:flex;"><span>进入黑屏终端后需要输入root密码,可以使用sudo passwd 提前设置</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>对于屏蔽init指令的系统,一般情况下注销用户就可以了,但是有些GUI桌面环境就是以当前用户的身份运行的,所以提供如下几个方式关闭当前用户,挨个尝试即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 1 桌面环境中点击注销</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2 杀掉当前用户所有进程</span> +</span></span><span style="display:flex;"><span>pkill -kill -u old_name +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3 杀掉进程后可能桌面重启,关闭Lightdm管理器</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># Ctrl + Alt + F2 进入第二页面,以root用户登录(可能需要提前设置root密码)</span> +</span></span><span style="display:flex;"><span>systemctl stop lightdm +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 然后再次执行第二步</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>二、修改信息<span class="hx:absolute hx:-mt-20" id="二修改信息"></span> + <a href="#%e4%ba%8c%e4%bf%ae%e6%94%b9%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>init 0</code>可以直接进入黑屏的终端,或者使用 <code>ctrl + alt + f2</code>进入(远程连接可以),以root用户登录。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 1 修改组的名称</span> +</span></span><span style="display:flex;"><span>groupmod -n new_name old_name +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2 修改用户名 -l 改名字 -d 改目录 -g 改组</span> +</span></span><span style="display:flex;"><span>usermod -l new_name -d /home/new_name -g new_name old_name +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3 修改主目录</span> +</span></span><span style="display:flex;"><span>mv /home/old_name /home/new_name</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>最后回到图形化界面,输入<code>init 5</code> ,或者重启电脑。</p> +<p>修改完主目录后记得检查是不是有其他软连接指向旧目录,需要进行更新,否则还会遇到问题。</p> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p># <a href="https://blog.csdn.net/weixin_30016961/article/details/116543982"target="_blank" rel="noopener">linux用户家目录怎么修改,Linux 更改用户名,用户组和主目录</a></p> +<p># <a href="https://www.runoob.com/linux/linux-comm-usermod.html"target="_blank" rel="noopener">菜鸟教程-usermod指令</a></p> +<p># <a href="https://blog.csdn.net/weixin_40001125/article/details/88903115"target="_blank" rel="noopener">linux组的管理:修改组名字,删除组,使用sudo权限</a></p> + + + + + 记一次拯救NAS硬盘 + https://www.small09.top/posts/211115-fixnasdriver/ + Mon, 15 Nov 2021 10:02:30 +0800 + + https://www.small09.top/posts/211115-fixnasdriver/ + + + + <p>更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据?</p> +<p><em>图片稍后补充。</em></p> +<h2>v 0 起因<span class="hx:absolute hx:-mt-20" id="v-0-起因"></span> + <a href="#v-0-%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0.</p> +<p>考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。</p> +<p>为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。</p> +<h2>v-1 杯具<span class="hx:absolute hx:-mt-20" id="v-1-杯具"></span> + <a href="#v-1-%e6%9d%af%e5%85%b7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。</p> +<h2>v-2 尝试<span class="hx:absolute hx:-mt-20" id="v-2-尝试"></span> + <a href="#v-2-%e5%b0%9d%e8%af%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内<strong>写入任何内容</strong>,也<strong>没有格式化</strong>。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。</p> +<p>为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。</p> +<p>经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。</p> +<p>搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。</p> +<p>难道就这么放弃了么?</p> +<h2>v 1 曙光<span class="hx:absolute hx:-mt-20" id="v-1-曙光"></span> + <a href="#v-1-%e6%9b%99%e5%85%89" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。</p> +<p>我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?!</p> +<p>开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。</p> +<p>把小硬盘用易驱线连上树莓派,<code>lsblk</code>看到这个硬盘有三个分区,其中<code>/dev/sda3</code>下面还有一个<code>md127</code>,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。</p> +<h2>v 2 得到数据<span class="hx:absolute hx:-mt-20" id="v-2-得到数据"></span> + <a href="#v-2-%e5%be%97%e5%88%b0%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>安装<code>mdadm</code>软件包,我的树莓派不带这个,然后就能挂载raid盘了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount /dev/md127 ~/disk</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个<code>NasData</code>目录,这正是我当时保存文件的地方。cd进去,在!都还在!</p> +<h2>v 3 移动数据<span class="hx:absolute hx:-mt-20" id="v-3-移动数据"></span> + <a href="#v-3-%e7%a7%bb%e5%8a%a8%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。</p> +<p>在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rsync -vrultD --exclude <span style="color:#e6db74">&#39;文件名&#39;</span> --exclude <span style="color:#e6db74">&#39;目录名&#39;</span> ~/disk/NasData/* ~/Nasdata/临时目录 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。</p> +<h2>v 4 结论<span class="hx:absolute hx:-mt-20" id="v-4-结论"></span> + <a href="#v-4-%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。</p> +<p>这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。</p> +<p>总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢?</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202111150957877.png" alt="image-20211115095410121" loading="lazy" /></p> + + + + + DEB打包方法简介 + https://www.small09.top/posts/211019-dpkg_package_guide/ + Tue, 19 Oct 2021 10:12:23 +0800 + + https://www.small09.top/posts/211019-dpkg_package_guide/ + + + + <p>简单介绍deb打包的方法,包括目录树格式和打包注意事项。</p> +<h2>文件结构<span class="hx:absolute hx:-mt-20" id="文件结构"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%bb%93%e6%9e%84" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录<code>/</code>。一个工作空间的目录树如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code> PackageDIR + ├─DEBIAN + ├─opt + │ └─kushidou-touchscreen + │ └─desktops + ├─usr + │ ├─bin + │ └─share + └─var</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中,<code>DEBIAN</code>目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。</p> +<h2>DEBIAN<span class="hx:absolute hx:-mt-20" id="debian"></span> + <a href="#debian" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>DEBIAN</code>目录下有一个control文件和至多四个脚本文件。</p> +<h3>control<span class="hx:absolute hx:-mt-20" id="control"></span> + <a href="#control" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>control</code>文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。</p> +<table> + <thead> + <tr> + <th>NAME</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td>Package</td> + <td>软件名,<code>dpkg -l</code> 列出的名字</td> + </tr> + <tr> + <td>Version</td> + <td>版本号-修订号</td> + </tr> + <tr> + <td>Section</td> + <td>软件类型,比如mail, test, x11等</td> + </tr> + <tr> + <td>Priority</td> + <td>优先级/重要性,比如optional 可选的</td> + </tr> + <tr> + <td>Architecture</td> + <td>软件架构,比如x86, arm64</td> + </tr> + <tr> + <td>Depends</td> + <td>依赖环境,用 <code>,</code> 隔开,用<code>(&gt;=&lt; )</code>指定版本号</td> + </tr> + <tr> + <td>Suggests</td> + <td>推荐安装,指非必要依赖,可以提供更多功能</td> + </tr> + <tr> + <td>Maintainer</td> + <td>作者 &lt;邮箱或者其他联系方式&gt;</td> + </tr> + <tr> + <td>Descritprion</td> + <td>简介,可以换行书写</td> + </tr> + </tbody> +</table> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20211019101901.png" alt="image-20211019094048011" loading="lazy" /></p> +<h3>额外脚本<span class="hx:absolute hx:-mt-20" id="额外脚本"></span> + <a href="#%e9%a2%9d%e5%a4%96%e8%84%9a%e6%9c%ac" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>DEBIAN</code>目录下之多放置四个可执行脚本,包括<strong>安装前<code>preinst</code></strong>、<strong>安装后<code>postinst</code></strong>、<strong>卸载前<code>prerm</code></strong>、<strong>卸载前<code>postrm</code></strong>,四个脚本都需要以<code>#!/bin/bash</code>开头,内容与普通脚本无异,文件名不需要后缀。</p> +<p>这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以<code>root</code>身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。</p> +<p>此外<code>postinst</code>可以用来启动已安装的程序,<code>prerm</code>常用来停止正在运行的程序。</p> +<h3>md5sums<span class="hx:absolute hx:-mt-20" id="md5sums"></span> + <a href="#md5sums" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。</p> +<h2>打包<span class="hx:absolute hx:-mt-20" id="打包"></span> + <a href="#%e6%89%93%e5%8c%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>所有文件配置完成后,进入工作空间上级目录,执行指令</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dpkg -b ./PackageDIR packagename.deb</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>deb包命名的一般规则为 <strong>软件名_版本-修订号_架构.deb</strong></p> +<h2>其他信息<span class="hx:absolute hx:-mt-20" id="其他信息"></span> + <a href="#%e5%85%b6%e4%bb%96%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。</p> + + + + + RPM安装包制作方法 + https://www.small09.top/posts/210928-rpm_package/ + Tue, 28 Sep 2021 11:06:54 +0800 + + https://www.small09.top/posts/210928-rpm_package/ + + + + <p>Linux如Centos使用的RPM打包方法。本文仅涉及二进制打包,且采用取巧的方法简化操作。(笔记,而非教程)</p> +<p><em>正文开始</em></p> + + + + + Linux恢复操作系统后mdadm导致开机变慢的方法 + https://www.small09.top/posts/210914-mdadm/ + Tue, 14 Sep 2021 15:16:13 +0800 + + https://www.small09.top/posts/210914-mdadm/ + + + + <p>解决Linux系统异地恢复后开机mdadm报错导致开机时间变长。</p> +<h2>现象<span class="hx:absolute hx:-mt-20" id="现象"></span> + <a href="#%e7%8e%b0%e8%b1%a1" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用deepin-clone恢复UOS镜像后,开机会不断跳一条日志:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mdadm: no arrays found in config file or automatically</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>大概蹦个15条后,系统才真正开机。mdadm好像是一个磁盘raid管理工具,当磁盘变化后其配置也不适配了,必须等到超时才会加载默认配置。那么我们就为其生成一个新的配置即可解决问题。</p> +<h2>方案<span class="hx:absolute hx:-mt-20" id="方案"></span> + <a href="#%e6%96%b9%e6%a1%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>步骤一:删除mdadm.conf</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo rm /etc/mdadm/mdadm.conf</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>步骤二:生成新的mdadm.conf</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo update-initramfs -u</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>ps:如果是swap分区问题导致等待90s开机慢,不归这儿管。</p> +<h2>从这儿抄的:<span class="hx:absolute hx:-mt-20" id="从这儿抄的"></span> + <a href="#%e4%bb%8e%e8%bf%99%e5%84%bf%e6%8a%84%e7%9a%84" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><a href="https://blog.csdn.net/KuXiaoQuShiHuai/article/details/99686620"target="_blank" rel="noopener">W: mdadm: no arrays found in config file or automatically导致开机变慢的解决方案(deepin)</a></p> + + + + + 使用dd指令备份和恢复Linux系统(硬盘级) + https://www.small09.top/posts/210914-linuxbackupdd/ + Tue, 14 Sep 2021 13:51:16 +0800 + + https://www.small09.top/posts/210914-linuxbackupdd/ + + + + <p>害,谁不是个DD (誰でも 大好き)了呢!</p> +<p>但是这玩意儿太慢了。。。</p> +<h2>起因<span class="hx:absolute hx:-mt-20" id="起因"></span> + <a href="#%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。</p> +<h2>DD说明<span class="hx:absolute hx:-mt-20" id="dd说明"></span> + <a href="#dd%e8%af%b4%e6%98%8e" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。</p> +<p>dd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。</p> +<p>实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。</p> +<h2>DD备份<span class="hx:absolute hx:-mt-20" id="dd备份"></span> + <a href="#dd%e5%a4%87%e4%bb%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda of<span style="color:#f92672">=</span>/dev/sdb <span style="color:#f92672">=</span>&gt; 备份整个磁盘到另外一个磁盘 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>sda.img <span style="color:#f92672">=</span>&gt; 备份整个磁盘为某个文件 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 备份并且压缩 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 指定块大小,备份并压缩(据说能提速)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>DD复原<span class="hx:absolute hx:-mt-20" id="dd复原"></span> + <a href="#dd%e5%a4%8d%e5%8e%9f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从另一个磁盘恢复回来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从压缩的文件恢复出来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M <span style="color:#f92672">=</span>&gt;前面指定了块大小,这里也需要</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>本文参考(抄)了以下内容:</p> +<p><a href="http://vpsxyz.com/archives/87"target="_blank" rel="noopener">Linux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像</a></p> +<p><a href="https://blog.csdn.net/weixin_33971130/article/details/85662628"target="_blank" rel="noopener">linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)</a></p> +<p>还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ )</p> + + + + + Linux处理多触屏的终极解决方案 + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + Tue, 24 Aug 2021 00:00:00 +0800 + + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + + + + <p>一般Linux操作系统接入多个触摸屏的最终解!</p> +<p>从<a href="https://www.small09.top/posts/210311-multitouchscreeninlinux/"target="_blank" rel="noopener">21年3月中旬开始</a>,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。</p> +<p><em>重要:</em> 本文全文基于软件<code>xinput</code></p> +<p><em>更重要:</em> 如简介所说,仍有一种方法无法对应,具体为<code>【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】</code>,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。</p> +<h2>0、核心:xinput<span class="hx:absolute hx:-mt-20" id="0核心xinput"></span> + <a href="#0%e6%a0%b8%e5%bf%83xinput" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)</p> +<blockquote> + <p>xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级</p> +<p>xinput list &ndash;id-only : 仅仅列出设备ID(序号,每次开机都可能改变)</p> +<p>xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕</p> +<p>xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。</p> + +</blockquote> +<h2>1、简约:两个完全不同的触摸显示器<span class="hx:absolute hx:-mt-20" id="1简约两个完全不同的触摸显示器"></span> + <a href="#1%e7%ae%80%e7%ba%a6%e4%b8%a4%e4%b8%aa%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c%e7%9a%84%e8%a7%a6%e6%91%b8%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>xinput map-to-output &quot;$DEVICE NAME A&quot; PORT-A</code></p> +<p>这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。</p> +<p>首先通过xinput查看所有的输入设备:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>~$ xinput +</span></span><span style="display:flex;"><span>┌ Virtual core pointer id<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">[</span>master pointer <span style="color:#f92672">(</span>3<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK TLITEK-TP id<span style="color:#f92672">=</span><span style="color:#ae81ff">9</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK Multi-Touch id<span style="color:#f92672">=</span><span style="color:#ae81ff">7</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">(</span>下略<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>可以看到<code>ILITEK ILITEK-TP</code>和<code>ILITEK Multi-Touch</code>显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:</p> +<p><code>xinput map-to-output &quot;ILITEK ILITEK-TP&quot; HDMI-0</code></p> +<p><code>xinput map-to-output &quot;ILITEK Multi-Touch&quot; HDMI-1</code></p> +<p>这种方式的要点是1.必须带英文<code>&quot;&quot;</code>,2.引号内名称须完整。<code>map-to-output</code>所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。</p> +<h2>2、复杂但通用:VID:PID不同的显示器<span class="hx:absolute hx:-mt-20" id="2复杂但通用vidpid不同的显示器"></span> + <a href="#2%e5%a4%8d%e6%9d%82%e4%bd%86%e9%80%9a%e7%94%a8vidpid%e4%b8%8d%e5%90%8c%e7%9a%84%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。</p> +<p>通过<code>xinput list-props [id]</code>可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。<code>list-props</code>的结果示例如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#f92672">(</span>后续再补<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Device Product ID</code>一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA<span style="color:#f92672">=</span>222a +</span></span><span style="display:flex;"><span>PIDA<span style="color:#f92672">=</span><span style="color:#ae81ff">0001</span> +</span></span><span style="display:flex;"><span>PORTA<span style="color:#f92672">=</span>VGA-0 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 或者这里直接填写Device Product ID里面的结果,先VID后PID。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 字母O,不是数字0,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$VIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span>PIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$PIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># =========自动处理步骤,请勿随意修改==========</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 根据设备ID取其序号</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3、屏映射,多屏重复本段</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$VIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$PIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> xinput map-to-output $I $PORTA +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span>exit <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这个脚本使用<code>xinput list --id-only</code>仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用<code>map-to-output</code>进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。</p> +<h2>3、[2]的进阶版<span class="hx:absolute hx:-mt-20" id="32的进阶版"></span> + <a href="#32%e7%9a%84%e8%bf%9b%e9%98%b6%e7%89%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。</p> +<p>在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是<code>pyudev</code>库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装<code>python3+pyudev</code>,并且使用desktop文件在开机时启动,即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/python3</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- coding: UTF-8 --</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- Author: Blues --</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pyudev +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>context <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Context() +</span></span><span style="display:flex;"><span>monitor <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Monitor<span style="color:#f92672">.</span>from_netlink(context) +</span></span><span style="display:flex;"><span>monitor<span style="color:#f92672">.</span>filter_by(subsystem<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;hid&#39;</span>) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> device <span style="color:#f92672">in</span> iter(monitor<span style="color:#f92672">.</span>poll, <span style="color:#66d9ef">None</span>): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>driver <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;hid-multitouch&#34;</span>: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;add&#34;</span> <span style="color:#f92672">or</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;change&#34;</span>: +</span></span><span style="display:flex;"><span> os<span style="color:#f92672">.</span>system(<span style="color:#e6db74">&#34;/var/my-touchscreen/remap-screen&#34;</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照<em>设备名、VID、UID、物理接口</em>四大特征,自动映射到对应的显示屏上了。</p> + + + + + Linux中获得AMD显卡的状态信息 + https://www.small09.top/posts/210719-gpuinfoinlinux/ + Mon, 19 Jul 2021 11:00:07 +0800 + + https://www.small09.top/posts/210719-gpuinfoinlinux/ + + + + <p>在Linux下获得AMD GPU的状态和温度信息。</p> +<p>在Linux下如果要查看Nvidia GPU的状态信息,可以使用Nvidia驱动自带的工具。而AMD显卡一般都是使用的开源驱动,这种情况下如何获得显卡的状态和占用呢?</p> +<p><em>文章部分图片之后再附</em></p> +<h2>1、获取GPU信息<span class="hx:absolute hx:-mt-20" id="1获取gpu信息"></span> + <a href="#1%e8%8e%b7%e5%8f%96gpu%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>如需获得GPU的信息(型号、驱动)等,如果系统提供类似*“设备管理器”*的工具可以打开查看。或者就是用<code>lspci</code>指令查看。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 获得所有PCIe设备,在其中找到AMD GPU</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>exam@ple<span style="color:#f92672">]</span>:~$ lspci +</span></span><span style="display:flex;"><span>00:02.0 VGA compatible controller: xxxxxxxx +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>exam@ple<span style="color:#f92672">]</span>:~$ lspci -v -s 00:02.0 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 这里就会显示显卡的具体信息了,包括型号、驱动等,之后补充</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>2、GPU占用率<span class="hx:absolute hx:-mt-20" id="2gpu占用率"></span> + <a href="#2gpu%e5%8d%a0%e7%94%a8%e7%8e%87" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>显卡占用率是一个能反映负载的直观指标,AMD显卡可以通过安装<code>radeontop</code>软件查看。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210719133426.png" alt="radeontop界面" loading="lazy" /></p> +<p>最上面是GPU管道使用,大概类似于总体使用率的东西;最下面是内存使用率,使用sudo权限能看到更多信息。其他项目可以到radeontop的<a href="https://github.com/clbr/radeontop"target="_blank" rel="noopener">Github页面</a>查看。</p> +<h2>3、温度<span class="hx:absolute hx:-mt-20" id="3温度"></span> + <a href="#3%e6%b8%a9%e5%ba%a6" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>特指GPU温度。查阅这篇文章所涉及的知识点的最初目的,也是确认机箱过热是否是GPU的问题。</p> +<p>GPU温度使用第三方软件查看,并使其实时更新。查看温度使用<code>sensors</code>指令,通过安装<code>lm-sensors</code>实现。执行sensors时只会显示一次,我们可以利用watch指令定时刷新温度显示:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>watch -n <span style="color:#ae81ff">2</span> sensors +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 每隔2秒执行sensors指令显示结果</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>![watch执行结果](图片占位)</p> + + + + + 免密码使用sudo + https://www.small09.top/posts/210630-sudowithoutpasswd/ + Wed, 30 Jun 2021 15:14:40 +0800 + + https://www.small09.top/posts/210630-sudowithoutpasswd/ + + + + <p>记录一种可以免密码使用sudo的方法。</p> +<p>每次使用sudo提权,常常要输密码太烦了,所以想给自己赋予免密码的特权。</p> +<ol> +<li> +<p>首先登录root用户: <code>sudo -i</code></p> +</li> +<li> +<p>这个操作的核心文件是<code>/etc/sudoers</code>,可以看到这个文件是只读的,要修改他需要先赋予写入权限。</p> +</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod <span style="color:#ae81ff">666</span> /etc/sudoers</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210705092601.png" alt="文件是只读权限" loading="lazy" /></p> +<ol start="3"> +<li>使用vim修改文件,<code>vi /etc/sudoers</code>,把当前用户和用户组加入,如图</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 表示为该用户免密码</span> +</span></span><span style="display:flex;"><span>yourname ALL<span style="color:#f92672">=(</span>ALL:ALL<span style="color:#f92672">)</span> NOPASSWD:ALL +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 表示为该用户组免密码</span> +</span></span><span style="display:flex;"><span>%yourname ALL<span style="color:#f92672">=(</span>ALL:ALL<span style="color:#f92672">)</span> NOPASSWD:ALL +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 不用严格按照次形式,与你的这个文件中root的格式对应即可。</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ol start="4"> +<li><code>:wq</code>保存,把sodoers改回只读</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod <span style="color:#ae81ff">440</span> /etc/sudoers</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210705092637.png" alt="文件修改的内容" loading="lazy" /></p> +<ol start="5"> +<li>完成~~~ 把终端关掉再次打开就应该能看到效果了。</li> +</ol> +<blockquote> + <p><strong>PS. 不可使用sudo chmod</strong></p> +<p>一定要直接sudo -i进入root用户,如果使用sudo chmod以普通用户的身份修改sudoers文件的话,改完会发现无法使用sudo指令把权限改回去了,提示“任何人可读写,无法找到有效的sudoers”。其实还有一种更加安全的操作,但是那个编辑器用起来没有vim顺手,</p> + +</blockquote> +<p>参考:https://blog.csdn.net/wxqee/article/details/72718869</p> + + + + + deepin和UOS,快捷方式,开机自启动 + https://www.small09.top/posts/210429-deepinautorun/ + Thu, 29 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210429-deepinautorun/ + + + + <p>修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。</p> +<h2>桌面快捷方式<span class="hx:absolute hx:-mt-20" id="桌面快捷方式"></span> + <a href="#%e6%a1%8c%e9%9d%a2%e5%bf%ab%e6%8d%b7%e6%96%b9%e5%bc%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>deepin的桌面快捷方式是以<code>*.desktop</code>文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>[Desktop Entry] +Name=ddd +Exec=&#34;/usr/sbin/xx.sh&#34; +Type=Application</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Desktop Entry</code>表示声明该文件为快捷方式文件。<code>Name</code>是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。<code>Exec</code>是程序的启动脚本,或者启动指令。<code>Type</code>是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。</p> +<p>把这个文件放在<code>~/Desktop</code>,就可以看到桌面上多了一个快捷方式了。</p> +<h2>开始菜单<span class="hx:absolute hx:-mt-20" id="开始菜单"></span> + <a href="#%e5%bc%80%e5%a7%8b%e8%8f%9c%e5%8d%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications</code>,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。</p> +<h2>开机自动执行<span class="hx:absolute hx:-mt-20" id="开机自动执行"></span> + <a href="#%e5%bc%80%e6%9c%ba%e8%87%aa%e5%8a%a8%e6%89%a7%e8%a1%8c" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>UOS/Deepin和Ubuntu<span class="hx:absolute hx:-mt-20" id="uosdeepin和ubuntu"></span> + <a href="#uosdeepin%e5%92%8cubuntu" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>cp ~/Desktop/刚刚的.desktop ~/.config/autostart</code></p> +<p>需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(<code>lightdm.service</code>)执行,导致error从而无法进入桌面,将脚本放进<code>/etc/profile.d</code>之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。</p> +<p>经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在<code>~/.config/</code>下手动创建一个<code>autostart</code>目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。</p> +<h3>麒麟和其他使用X11桌面的Linux<span class="hx:absolute hx:-mt-20" id="麒麟和其他使用x11桌面的linux"></span> + <a href="#%e9%ba%92%e9%ba%9f%e5%92%8c%e5%85%b6%e4%bb%96%e4%bd%bf%e7%94%a8x11%e6%a1%8c%e9%9d%a2%e7%9a%84linux" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>本条目包含UOS和Ubuntu、deepin系统</p> +<p>自启动目录为<code>/etc/xdg/autostart</code>,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。</p> + + + + + Linux启用四个以上的串口 + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + Tue, 20 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + + + + <p>工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。</p> +<h2>先说结论<span class="hx:absolute hx:-mt-20" id="先说结论"></span> + <a href="#%e5%85%88%e8%af%b4%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e"># 1.修改开机grub文件</span> +</span></span><span style="display:flex;"><span>sudo vi /etc/default/grub +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2.在&#34;GRUB_CMDLINE_LINUX_DEFAULT=&#34;一行的结尾空格,添加内容</span> +</span></span><span style="display:flex;"><span> 8250.nr_uarts<span style="color:#f92672">=</span><span style="color:#ae81ff">8</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3.更新开机引导并重启</span> +</span></span><span style="display:flex;"><span>sudo update-grub</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>再说细节<span class="hx:absolute hx:-mt-20" id="再说细节"></span> + <a href="#%e5%86%8d%e8%af%b4%e7%bb%86%e8%8a%82" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>给带有多串口的工控机安装国产Linux操作系统UOS,发现<code>/dev/ttyS</code>只有四个,而且一个都不通,其实这是Linux 的通病,如果发行商没有提前配置,我们只能看到四个不能用的串口。但是工控机都有8-16个串口,不得不想办法让他们能够正常使用,而许多串口芯片只需要基本的驱动就能运行,也不提供专有驱动的源代码编译<font size=2 color=#ff0000>(一般安装专用驱动后,串口名称会改变,比如xr17v358芯片的驱动安装后名称变成ttyXR0)</font>,我们只有从linux本身下手了。</p> +<p>经过多番查找资料,虽然不知道为什么,但是我们应该修改<code>/etc/default/grub</code>文件,其中有一行是这样的:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>GRUB_CMDLINE_LINUX_DEFAULT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;这里面 的内容 先不管 它&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>我看了UOS(含Deepin)、Ubuntu和服务器的Centos7都有,在这一行的最后添加<code>8250.nr_uart=32</code>,其中数字&lt;=32都行,效果如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>GRUB_CMDLINE_LINUX_DEFAULT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;这里面 的内容 先不管 它 8250.nr_uarts=32&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后更新一下系统的引导</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo update-gurb +</span></span><span style="display:flex;"><span>reboot</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>重启完了你就能看到更多的串口啦!配合<a href="https://www.small09.top/?p=24"target="_blank" rel="noopener">修改udev规则</a>,你就可以用普通用户的权限使用串口通信了。但是请注意其他隐患,比如这个芯片配合基本驱动只能完成基础的收发功能,流控的RTS、CTS等还是需要专用驱动实现的!</p> +<h2>最后吐槽<span class="hx:absolute hx:-mt-20" id="最后吐槽"></span> + <a href="#%e6%9c%80%e5%90%8e%e5%90%90%e6%a7%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>为了串口问题我找了一下午的资料,最后才在外网找到了一个能用的。国内的教程都是说要修改<code>/boot/gurb/gurb2</code>,宝友,这可不兴改啊,这个改坏了系统可就没了啊!这些文件普通用户连浏览权限都没就可见其重要性。</p> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><a href="https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/"target="_blank" rel="noopener">https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/</a></p> +<p>核心步骤就参考了这个,在Linux方面大家还是多用google,多用英文去搜索吧!</p> + + + + + Linux修改mac地址 + https://www.small09.top/posts/210328-linuxchangemacip/ + Sun, 28 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210328-linuxchangemacip/ + + + + <p>当Linux的图形界面没有或失效的时候,该如何更改mac地址?</p> +<h1>Linux调整网卡MAC地址(指令)</h1><p>有的时候需要使用欺骗的方法为电脑设置新的mac,比如存在上网网卡限制,或者想要隐藏自己的真实mac地址。这里列举三种修改mac的方法,适用于Debian系的Linux,其他的应该类似。</p> +<h2>方法一:ifconfig<span class="hx:absolute hx:-mt-20" id="方法一ifconfig"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%b8%80ifconfig" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># ifconfig 查看网卡名称$NAME</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 需要sudo权限</span> +</span></span><span style="display:flex;"><span>ifconfig $NAME down +</span></span><span style="display:flex;"><span>ifconfig $NAME hw ether $NEW_MAC +</span></span><span style="display:flex;"><span>ifconfig $NAME up +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 使用ifconfig检查有没有改变</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>方法二:macchanger<span class="hx:absolute hx:-mt-20" id="方法二macchanger"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%ba%8cmacchanger" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo apt install macchanger</code>,安装过程提示是否自启,随便。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ifconfig $NAME down +</span></span><span style="display:flex;"><span>macchanger -m $NEW_MAC $NAME +</span></span><span style="display:flex;"><span>ifconfig $NAME up</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>ps:macchanger可以附加多种参数,如-m指定mac地址,-s查看状态,-r随机地址</p> +<h2>备注:<span class="hx:absolute hx:-mt-20" id="备注"></span> + <a href="#%e5%a4%87%e6%b3%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上两种方法是软件层面的修改,不会对硬件造成损伤,重启之后就能恢复如初。如果是要长期有效,可以把这些命令写入脚本开机自启。除了这两个,还有另一种软修改的方法,因为我总是提示失败就遗忘了。</p> +<p>有些方法会提到修改/etc目录下的文件实现MAC地址的修改,但是我一个文件都没找到(UOS),这个是因系统而异的,不同的发行版可以选择性的编译这些文件,就会造成有人有有人没有,因为没有实践,所以就不写在这里了。</p> +<h2>方法三:ethtool(慎用)<span class="hx:absolute hx:-mt-20" id="方法三ethtool慎用"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%b8%89ethtool%e6%85%8e%e7%94%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上方法都失效,可以直接修改网卡的物理MAC地址,修改后无法恢复,慎用</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 首先检查mac是否支持修改</span> +</span></span><span style="display:flex;"><span>ethtool -i $NAME +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果 supports-eeprom-access: no,大概率不支持修改</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 查看网卡的mac寄存器</span> +</span></span><span style="display:flex;"><span>ethtool -e $NAME +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果无法读取,大概率不支持修改</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 修改mac,按字节修改</span> +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">0</span> value 0xAA +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">1</span> value 0xBB +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">2</span> value 0xCC +</span></span><span style="display:flex;"><span>...共六行 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果修改无效,则驱动不支持修改</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>如果以上方法都无法修改,则建议申请新的ip。。。<span class="hx:absolute hx:-mt-20" id="如果以上方法都无法修改则建议申请新的ip"></span> + <a href="#%e5%a6%82%e6%9e%9c%e4%bb%a5%e4%b8%8a%e6%96%b9%e6%b3%95%e9%83%bd%e6%97%a0%e6%b3%95%e4%bf%ae%e6%94%b9%e5%88%99%e5%bb%ba%e8%ae%ae%e7%94%b3%e8%af%b7%e6%96%b0%e7%9a%84ip" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><em><font size="1">文章首发于<a href="small09.top" target="前往火柴盒" title="前往Kushidou个人博客" rel="noopener">香风家的小别墅</a>,转载请携带出处。</font></em></p> + + + + + 关于使用HUGO建立博客站点 + https://www.small09.top/posts/about_hugo_website/ + Tue, 23 Mar 2021 09:15:09 +0800 + + https://www.small09.top/posts/about_hugo_website/ + + + + <p>使用hugo建立本站的规划和记录。</p> +<h1>关于使用HUGO建立博客</h1><p>在经历了worldpress的性能消耗和慢速加载后,我现在决定换用hugo+caddy的建站模式。昨天已经完成了caddy2的基础设置,现在开始测试使用hugo。</p> +<h2>hugo的安装与首次创建<span class="hx:absolute hx:-mt-20" id="hugo的安装与首次创建"></span> + <a href="#hugo%e7%9a%84%e5%ae%89%e8%a3%85%e4%b8%8e%e9%a6%96%e6%ac%a1%e5%88%9b%e5%bb%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>安装使用<em>chocolatey</em>,一个windows下的仿apt、yum的软件包管理器。安装好chocolatey后,根据hugo官网的内容,<code>choco install hugo -confirm</code>安装基础的hugo(好处是不用再配置环境变量了)。</p> +<p>为hugo创建目录,进入后<code>hugo site new website</code>,hugo会自动建立它所需要的层级结构。</p> +<p>添加第一个页面,`hugo new xxx/xxx.md&rsquo;,创建的内容会出现在content下,创建多级目录也适合将来归类。</p> +<h2>编写文章<span class="hx:absolute hx:-mt-20" id="编写文章"></span> + <a href="#%e7%bc%96%e5%86%99%e6%96%87%e7%ab%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>hugo的文章是基于markdown文档的,创建的文章会自动加入以下抬头</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-TOML" data-lang="TOML"><span style="display:flex;"><span><span style="color:#a6e22e">---</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">title</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;$文章标题&#34;</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">date</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#960050;background-color:#1e0010">$时间</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">draft</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#66d9ef">true</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">---</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这个可以修改为json或ymal格式,理应可以添加更多项目,这个之后再研究。</p> +<h2>主题/皮肤<span class="hx:absolute hx:-mt-20" id="主题皮肤"></span> + <a href="#%e4%b8%bb%e9%a2%98%e7%9a%ae%e8%82%a4" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>主题放在themes下,我在知乎上找到了一个高赞且顺眼的,<a href="https://www.zhihu.com/question/266175192/answer/460456938"target="_blank" rel="noopener">有哪些好看的Hugo主题? - 飞雪无情的回答 - 知乎</a>,文章中附了github连接,那么我们直接git过来就完事了。</p> +<h2>生成网站<span class="hx:absolute hx:-mt-20" id="生成网站"></span> + <a href="#%e7%94%9f%e6%88%90%e7%bd%91%e7%ab%99" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>文章+主题都有了,现在可以生成第一个网站了(虽然写这些字的时候,网站还没生成)。<code>hugo</code>就能直接产生网页,保存至public文件夹。<code>hugo server</code>可以启动一个服务器预览静态网页。</p> + + + + + Linux串口和udev理解 + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + + + + <p>配置串口权限,顺便看一下linux的udev规则。</p> +<h2>串口的问题<span class="hx:absolute hx:-mt-20" id="串口的问题"></span> + <a href="#%e4%b8%b2%e5%8f%a3%e7%9a%84%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在现场开发的同事反映UOS工控机没有配置串口,每次都需要sudo才能使用。(明明在家里你们都会自己配置好环境与权限,为什么到了现场反而不会了需要我提供协助呢?)</p> +<p>我之前没考虑过这个问题,认为串口就是得root权限操作才安全。但是既然提出了需求,就赶紧百度并测试一下吧。</p> +<h2>赋予普通用户串口权限的两种方法<span class="hx:absolute hx:-mt-20" id="赋予普通用户串口权限的两种方法"></span> + <a href="#%e8%b5%8b%e4%ba%88%e6%99%ae%e9%80%9a%e7%94%a8%e6%88%b7%e4%b8%b2%e5%8f%a3%e6%9d%83%e9%99%90%e7%9a%84%e4%b8%a4%e7%a7%8d%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在百度上,给普通用户串口权限有两种主流方法(可能2会了,Google上不去),一种是将用户放入特殊的用户组,从而获得权限。另一种是加入udev规则,在串口插入时自动更改权限。</p> +<p>我个人更倾向于使用第二种方案,因为公司中不同的人装系统就可能起不同的名字,之后进入生产环境可能又会起新的名字,所以采用第二种设置规则的办法,便于之后制作恢复镜像和一键部署。</p> +<h2>xxx.rules<span class="hx:absolute hx:-mt-20" id="xxxrules"></span> + <a href="#xxxrules" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo vi /etc/udev/rules.d/70-tty-usb-rules</code>,写入以下内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>KERNEL<span style="color:#f92672">==</span><span style="color:#e6db74">&#34;ttyS*&#34;</span>,MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0666&#34;</span> +</span></span><span style="display:flex;"><span>KERNEL<span style="color:#f92672">==</span><span style="color:#e6db74">&#34;ttyUSB*&#34;</span>,MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0666&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这一段的意思是匹配所有ttyS和ttyUSB,插入时都赋予0666的权限,即所有用户都可读可写。重新加载一下设备,就可以按规则配置权限进行操作了。</p> +<h2>udev规则<span class="hx:absolute hx:-mt-20" id="udev规则"></span> + <a href="#udev%e8%a7%84%e5%88%99" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>udev是什么?udev是Linux下的设备文件管理方式,运行在用户侧(devfs在内核侧管理设备文件)。有了udev,<code>/dev</code>目录下的文件就都是真实存在的已连接的设备了。</p> +<p>udev的规则都保存在<code>/etc/udev/rules.d</code>目录下,文件名的格式为<code>数字-名称.rules</code>,数字表示加载顺序,一般是从小往大加载;名称按功能自定义,最后以.rules结尾。这些规则会在设备接入时生效。</p> +<p>规则文件按行执行,每行分为两个部分:匹配和赋值,每个参数使用逗号,分割。</p> +<blockquote> + <p>(这段复制自引用2,仅供参考)。 +<strong>1、udev 规则的所有操作符</strong> +<strong>==</strong>  比较键、值,若等于,则该条件满足; +<strong>!=</strong>  比较键、值,若不等于,则该条件满足; +<strong>=</strong>   对一个键赋值; +<strong>+=</strong>  为一个表示多个条目的键赋值。 +<strong>:=</strong>  对一个键赋值,并拒绝之后所有对该键的改动,防止后面的规则文件对该键重赋值。</p> +<p><strong>2、udev 规则的匹配键</strong> +<strong>ACTION</strong>        事件 (uevent) 的行为,例如:add、remove。 +<strong>KERNEL</strong>         内核设备名称,例如:sda, cdrom。 +<strong>DEVPATH</strong>        设备的 devpath 路径。 +<strong>SUBSYSTEM</strong>       设备的子系统名称,例如:sda 的子系统为 block。 +<strong>BUS</strong>           设备在 devpath 里的总线名称,例如:usb。 +<strong>DRIVER</strong>         设备在 devpath 里的设备驱动名称,例如:ide-cdrom。 +<strong>ID</strong>            设备在 devpath 里的识别号。 +<strong>SYSFS{filename}</strong>    设备的 devpath 路径下,设备的属性文件“filename”里的内容。 +<strong>ENV{key}</strong>       环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。 +<strong>PROGRAM</strong>        调用外部命令。 +<strong>RESULT</strong>         外部命令 PROGRAM 的返回结果。</p> +<p><strong>3、udev 的重要赋值键</strong> +<strong>NAME</strong>           /dev目录下显示的文件名重命名 +<strong>SYMLINK</strong>          为 /dev下文件进行符号链接,防止覆盖系统默认产生的文件 +<strong>OWNER, GROUP, MODE</strong>  为设备设定权限。 +<strong>ENV{key}</strong>         导入一个环境变量。</p> +<p><strong>4、udev 的值和可调用的替换操作符</strong> +Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符: +<strong>$kernel, %k</strong>        设备的内核设备名称,例如:sda、cdrom。 +<strong>$number, %n</strong>        设备的内核号码,例如:sda3 的内核号码是 3。 +<strong>$devpath, %p</strong>       设备的 devpath路径。 +<strong>$id, %b</strong>          设备在 devpath里的 ID 号。 +<strong>$sysfs{file}, %s{file}</strong>   设备的 sysfs里 file 的内容。其实就是设备的属性值。 +<strong>$env{key}, %E{key}</strong>   一个环境变量的值。 +<strong>$major, %M</strong>        设备的 major 号。 +<strong>$minor %m</strong>        设备的 minor 号。 +<strong>$result, %c</strong>        PROGRAM 返回的结果。 +<strong>$parent, %P</strong>       父设备的设备文件名。 +<strong>$root, %r</strong>         udev_root的值,默认是 /dev/。 +<strong>$tempnode, %N</strong>      临时设备名。 +<strong>%%</strong>            符号 % 本身。 +<strong>$$</strong>             符号 $ 本身。</p> + +</blockquote> +<h2>参考<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://www.cnblogs.com/qixianyu/p/6869744.html +[2]https://www.cnblogs.com/cslunatic/p/3171837.html</p> + + + + + Linux多显示器触屏调试与暂行解决方案 + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + + + + <p>由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。</p> +<p><em>2021-03-22更新部分内容</em> +<em>2021-04-28再次更新内容</em></p> +<p><strong>2021-09-14 文章已有新版本,请访问《<a href="https://www.small09.top/posts/210824-linuxtouchscreenfin/"target="_blank" rel="noopener">Linux处理多触屏的终极解决方案</a>》</strong></p> +<h2>问题来源<span class="hx:absolute hx:-mt-20" id="问题来源"></span> + <a href="#%e9%97%ae%e9%a2%98%e6%9d%a5%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为</p> +<p><code>xinput map-to-output $触屏设备id $显示屏名称</code></p> +<blockquote> + <p>2021-04-28更新</p> +<p><code>xinput map-to-output</code>指令支持触摸屏名称作为参数,比如我通过<code>xinput</code>指令看到有一个触摸屏的设备名称为<code>ILITEK XXXX</code>,那么我映射的时候直接输入</p> +<blockquote> + <p><code>xinput map-to-output &quot;ILITEK XXX&quot; HDMI-1</code></p> + +</blockquote> + +</blockquote> +<h2>尝试解决<span class="hx:absolute hx:-mt-20" id="尝试解决"></span> + <a href="#%e5%b0%9d%e8%af%95%e8%a7%a3%e5%86%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ul> +<li>首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。</li> +<li><del>然后<code>xinput list-props $id</code>查看设备的VID,这个在后续自动化处理有用。</del></li> +<li><code>xrandr</code>查看<code>$显示器名称</code></li> +</ul> +<h2>自动化处理<span class="hx:absolute hx:-mt-20" id="自动化处理"></span> + <a href="#%e8%87%aa%e5%8a%a8%e5%8c%96%e5%a4%84%e7%90%86" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 2021-04-28更新:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 本部分后面的都可以不看了!</span> +</span></span><span style="display:flex;"><span>xinput map-to-output <span style="color:#e6db74">&#34;DEVICE_NAME&#34;</span> DISPLAY +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><strong>仅作归档!下面这些方法过于繁琐!</strong></p> +<p>利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。<code>bash this.sh VID1 VID2 ScreenID</code></p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。</span> +</span></span><span style="display:flex;"><span>MYID_FIRST<span style="color:#f92672">=</span>$1 +</span></span><span style="display:flex;"><span>MYID_SECOND<span style="color:#f92672">=</span>$2 +</span></span><span style="display:flex;"><span>OUT_PUT<span style="color:#f92672">=</span>$3 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#[1]</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> MY_INDEX<span style="color:#f92672">=</span>$I +</span></span><span style="display:flex;"><span> break; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>xinput map-to-output $MY_INDEX $OUT_PUT +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI2</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI1</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput | grep -iw DEVICE_NAME<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID#*id=<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID%% *<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>xinput map-to-output $TOUCH_ID SCREEN_NAME</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),<del>使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id</del>。</p> +<p>经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在<code>~/.bashrc</code>中,这样只需要打开一次终端,就可以实现屏幕重新映射。</p> +<h2>后续问题<span class="hx:absolute hx:-mt-20" id="后续问题"></span> + <a href="#%e5%90%8e%e7%bb%ad%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li>当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。</li> +<li>当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数</li> +<li>如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。</li> +</ol> +<h2>触屏校准(如果需要)<span class="hx:absolute hx:-mt-20" id="触屏校准如果需要"></span> + <a href="#%e8%a7%a6%e5%b1%8f%e6%a0%a1%e5%87%86%e5%a6%82%e6%9e%9c%e9%9c%80%e8%a6%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>网上说的利用校准来区分多触摸屏的方法是<strong>完全错误</strong>的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)</p> +<ul> +<li><code>sudo apt install xinput-calibrator</code>,需要更新源</li> +<li><code>sudo xinput_calibrator</code></li> +<li>将校准信息保存到<code>/usr/share/X11/xorg.conf.d/10-evdev.conf</code>的&quot;Driver evdev”下面一行,重启生效。</li> +</ul> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077</p> + + + + + 使用Docker安装TeamSpeak + https://www.small09.top/posts/210218-teamspeakwithdocker/ + Thu, 18 Feb 2021 00:00:00 +0800 + + https://www.small09.top/posts/210218-teamspeakwithdocker/ + + + + <p>TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。</p> +<h2>Teamspeak<span class="hx:absolute hx:-mt-20" id="teamspeak"></span> + <a href="#teamspeak" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。</p> +<p>所以,选择docker安装ts就是一个很不错的选择。</p> +<h2>Docker及其安装<span class="hx:absolute hx:-mt-20" id="docker及其安装"></span> + <a href="#docker%e5%8f%8a%e5%85%b6%e5%ae%89%e8%a3%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。</p> +<p>我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo yum install docker <span style="color:#75715e">#安装docker程序,会自动选择合适的</span> +</span></span><span style="display:flex;"><span>sudo systemctl start docker <span style="color:#75715e">#启动docker服务</span> +</span></span><span style="display:flex;"><span>sudo systemctl enable docker <span style="color:#75715e">#允许docker开机自启</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>Docker安装TS<span class="hx:absolute hx:-mt-20" id="docker安装ts"></span> + <a href="#docker%e5%ae%89%e8%a3%85ts" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak</code></p> +<p>直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。</p> +<h2>端口<span class="hx:absolute hx:-mt-20" id="端口"></span> + <a href="#%e7%ab%af%e5%8f%a3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>从指令可以看出,docker需要使用到<strong>9987,10011,30033</strong>三个端口,所以需要到服务器后台开启防火墙上对应端口。</p> +<h2>管理员密钥<span class="hx:absolute hx:-mt-20" id="管理员密钥"></span> + <a href="#%e7%ae%a1%e7%90%86%e5%91%98%e5%af%86%e9%92%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。</p> +<p><code>docker ps -a</code>查找所有容器。</p> +<p><code>docker logs -f [CONTATINER ID]</code>查看log,找到token即可。客户端设置之后再说。</p> +<p>密钥只会在第一次运行时打印,注意保存。</p> +<p><em>参考:<a href="https://woolio.cn/683/"target="_blank" rel="noopener">https://woolio.cn/683/</a></em></p> + + + + + diff --git a/public/js/flexsearch.433e941a8a573ebb9931fc16fc75266ab6b93f569ac2fb4f3dc66882e0416f4c.js b/public/js/flexsearch.433e941a8a573ebb9931fc16fc75266ab6b93f569ac2fb4f3dc66882e0416f4c.js new file mode 100644 index 0000000..970b757 --- /dev/null +++ b/public/js/flexsearch.433e941a8a573ebb9931fc16fc75266ab6b93f569ac2fb4f3dc66882e0416f4c.js @@ -0,0 +1,96 @@ +/**! + * FlexSearch.js v0.8.143 (Bundle) + * Author and Copyright: Thomas Wilkerling + * Licence: Apache-2.0 + * Hosted by Nextapps GmbH + * https://github.com/nextapps-de/flexsearch + */ +(function _f(self){'use strict';if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f;var t;function z(a,c,b){const e=typeof b,d=typeof a;if("undefined"!==e){if("undefined"!==d){if(b){if("function"===d&&e===d)return function(h){return a(b(h))};c=a.constructor;if(c===b.constructor){if(c===Array)return b.concat(a);if(c===Map){var f=new Map(b);for(var g of a)f.set(g[0],g[1]);return f}if(c===Set){g=new Set(b);for(f of a.values())g.add(f);return g}}}return a}return b}return"undefined"===d?c:a}function B(){return Object.create(null)}function E(a){return"string"===typeof a} +function I(a){return"object"===typeof a}function aa(a){const c=[];for(const b of a.keys())c.push(b);return c}function ba(a,c){if(E(c))a=a[c];else for(let b=0;a&&b"a1a".split(b).length; +this.numeric=z(a.numeric,e)}else{try{this.split=z(this.split,da)}catch(d){this.split=/\s+/}this.numeric=z(a.numeric,z(this.numeric,!0))}this.prepare=z(a.prepare,null,this.prepare);this.finalize=z(a.finalize,null,this.finalize);b=a.filter;this.filter="function"===typeof b?b:z(b&&new Set(b),null,this.filter);this.dedupe=z(a.dedupe,!1,this.dedupe);this.matcher=z((b=a.matcher)&&new Map(b),null,this.matcher);this.mapper=z((b=a.mapper)&&new Map(b),null,this.mapper);this.stemmer=z((b=a.stemmer)&&new Map(b), +null,this.stemmer);this.replacer=z(a.replacer,null,this.replacer);this.minlength=z(a.minlength,1,this.minlength);this.maxlength=z(a.maxlength,0,this.maxlength);this.rtl=z(a.rtl,!1,this.rtl);if(this.cache=b=z(a.cache,!0,this.cache))this.H=null,this.S="number"===typeof b?b:2E5,this.B=new Map,this.G=new Map,this.L=this.K=128;this.h="";this.M=null;this.A="";this.N=null;if(this.matcher)for(const d of this.matcher.keys())this.h+=(this.h?"|":"")+d;if(this.stemmer)for(const d of this.stemmer.keys())this.A+= +(this.A?"|":"")+d;return this};t.addStemmer=function(a,c){this.stemmer||(this.stemmer=new Map);this.stemmer.set(a,c);this.A+=(this.A?"|":"")+a;this.N=null;this.cache&&K(this);return this};t.addFilter=function(a){"function"===typeof a?this.filter=a:(this.filter||(this.filter=new Set),this.filter.add(a));this.cache&&K(this);return this}; +t.addMapper=function(a,c){if("object"===typeof a)return this.addReplacer(a,c);if(1a.length&&(this.dedupe||this.mapper))return this.addMapper(a,c);this.matcher||(this.matcher=new Map);this.matcher.set(a,c);this.h+=(this.h?"|":"")+a;this.M=null;this.cache&&K(this);return this}; +t.addReplacer=function(a,c){if("string"===typeof a)return this.addMatcher(a,c);this.replacer||(this.replacer=[]);this.replacer.push(a,c);this.cache&&K(this);return this}; +t.encode=function(a){if(this.cache&&a.length<=this.K)if(this.H){if(this.B.has(a))return this.B.get(a)}else this.H=setTimeout(K,50,this);this.normalize&&("function"===typeof this.normalize?a=this.normalize(a):a=ia?a.normalize("NFKD").replace(ia,"").toLowerCase():a.toLowerCase());this.prepare&&(a=this.prepare(a));this.numeric&&3this.stemmer.get(k)),d!==g&&this.filter&& +g.length>=this.minlength&&("function"===typeof this.filter?!this.filter(g):this.filter.has(g))&&(g=""));if(g&&(this.mapper||this.dedupe&&1this.matcher.get(k)));if(g&&this.replacer)for(d=0;g&&dthis.S&&(this.G.clear(),this.L=this.L/1.1|0));g&&b.push(g)}this.finalize&&(b=this.finalize(b)||b);this.cache&&a.length<=this.K&&(this.B.set(a,b),this.B.size>this.S&&(this.B.clear(),this.K=this.K/1.1|0));return b};function K(a){a.H=null;a.B.clear();a.G.clear()};let M,ja;async function ka(a){a=a.data;var c=a.task;const b=a.id;let e=a.args;switch(c){case "init":ja=a.options||{};(c=a.factory)?(Function("return "+c)()(self),M=new self.FlexSearch.Index(ja),delete self.FlexSearch):M=new N(ja);postMessage({id:b});break;default:let d;"export"===c&&(e[1]?(e[0]=ja.export,e[2]=0,e[3]=1):e=null);"import"===c?e[0]&&(a=await ja.import.call(M,e[0]),M.import(e[0],a)):(d=e&&M[c].apply(M,e))&&d.then&&(d=await d);postMessage("search"===c?{id:b,msg:d}:{id:b})}};function la(a){ma.call(a,"add");ma.call(a,"append");ma.call(a,"search");ma.call(a,"update");ma.call(a,"remove")}let na,oa,pa;function qa(){na=pa=0} +function ma(a){this[a+"Async"]=function(){const c=arguments;var b=c[c.length-1];let e;"function"===typeof b&&(e=b,delete c[c.length-1]);na?pa||(pa=Date.now()-oa>=this.priority*this.priority*3):(na=setTimeout(qa,0),oa=Date.now());if(pa){const f=this;return new Promise(g=>{setTimeout(function(){g(f[a+"Async"].apply(f,c))},0)})}const d=this[a].apply(this,c);b=d.then?d:new Promise(f=>f(d));e&&b.then(e);return b}};let O=0; +function P(a={}){function c(g){function h(k){k=k.data||k;const l=k.id,n=l&&d.h[l];n&&(n(k.msg),delete d.h[l])}this.worker=g;this.h=B();if(this.worker){e?this.worker.on("message",h):this.worker.onmessage=h;if(a.config)return new Promise(function(k){d.h[++O]=function(){k(d);1E9=g.length)c-=g.length;else{c=g[e?"splice":"slice"](c,b);const h=c.length;if(h&&(d=d.length?d.concat(c):c,b-=h,e&&(a.length-=h),!b))break;c=0}return d} +function R(a){if(!this||this.constructor!==R)return new R(a);this.index=a?[a]:[];this.length=a?a.length:0;const c=this;return new Proxy([],{get(b,e){if("length"===e)return c.length;if("push"===e)return function(d){c.index[c.index.length-1].push(d);c.length++};if("pop"===e)return function(){if(c.length)return c.length--,c.index[c.index.length-1].pop()};if("indexOf"===e)return function(d){let f=0;for(let g=0,h,k;gb||e?k.slice(e,b+e):k;else{if(ab||e)k=k.slice(e,b+e)}else{d=[];for(let m= +0,q;me)e-=q.length;else{if(q.length>b||e)q=q.slice(e,b+e),b-=q.length,e&&(e-=q.length);d.push(q);if(!b)break}k=1c?c?a.slice(b,b+c):a.slice(b):a,e?V.call(this,a):a;let d=[];for(let f=0,g,h;f=h){b-=h;continue}bc&&(g=g.slice(0,c),h=c);if(!d.length&&h>=c)return e?V.call(this,g):g;d.push(g);c-=h;if(!c)break}d=1a.length?this.result=a[0]:(this.result=Ga(a,b,e,!1,this.h),e=0));return f?this.resolve(b,e,d):this};W.prototype.and=function(){let a=this.result.length,c,b,e,d;if(!a){const f=arguments[0];f&&(a=!!f.suggest,d=f.resolve,c=f.limit,b=f.offset,e=f.enrich&&d)}if(a){const {O:f,P:g,limit:h,offset:k,enrich:l,resolve:n,suggest:m}=Ja(this,"and",arguments);return La.call(this,f,g,h,k,l,n,m)}return d?this.resolve(c,b,e):this}; +function La(a,c,b,e,d,f,g){if(c.length){const h=this;return Promise.all(c).then(function(k){a=[];for(let l=0,n;la.length)this.result=a[0];else{if(c=ca(a))return this.result=Fa(a,c,b,e,g,this.h,f),f?d?V.call(this.index,this.result):this.result:this;this.result=[]}else g||(this.result=a);return f?this.resolve(b,e,d):this};W.prototype.xor=function(){const {O:a,P:c,limit:b,offset:e,enrich:d,resolve:f,suggest:g}=Ja(this,"xor",arguments);return Ma.call(this,a,c,b,e,d,f,g)}; +function Ma(a,c,b,e,d,f,g){if(c.length){const h=this;return Promise.all(c).then(function(k){a=[];for(let l=0,n;la.length)this.result=a[0];else return this.result=Na.call(this,a,b,e,f,this.h),f?d?V.call(this.index,this.result):this.result:this;else g||(this.result=a);return f?this.resolve(b,e,d):this} +function Na(a,c,b,e,d){const f=[],g=B();let h=0;for(let k=0,l;kb||e)a=a.slice(e,e+b);d&&(a=V.call(this,a));return a}}function V(a){if(!this||!this.store)return a;const c=Array(a.length);for(let b=0,e;bthis.limit&&this.cache.delete(this.cache.keys().next().value)}; +X.prototype.get=function(a){const c=this.cache.get(a);c&&this.h!==a&&(this.cache.delete(a),this.cache.set(this.h=a,c));return c};X.prototype.remove=function(a){for(const c of this.cache){const b=c[0];c[1].includes(a)&&this.cache.delete(b)}};X.prototype.clear=function(){this.cache.clear();this.h=""};const Wa={normalize:!1,numeric:!1,split:/\s+/};const Xa={normalize:!0};const Ya={normalize:!0,dedupe:!0};const Za=new Map([["b","p"],["v","f"],["w","f"],["z","s"],["x","s"],["d","t"],["n","m"],["c","k"],["g","k"],["j","k"],["q","k"],["i","e"],["y","e"],["u","o"]]);const $a=new Map([["ae","a"],["oe","o"],["sh","s"],["kh","k"],["th","t"],["ph","f"],["pf","f"]]),ab=[/([^aeo])h(.)/g,"$1$2",/([aeo])h([^aeo]|$)/g,"$1$2",/(.)\1+/g,"$1"];const bb={a:"",e:"",i:"",o:"",u:"",y:"",b:1,f:1,p:1,v:1,c:2,g:2,j:2,k:2,q:2,s:2,x:2,z:2,"\u00df":2,d:3,t:3,l:4,m:5,n:5,r:6};var cb={X:Wa,W:Xa,Y:Ya,LatinBalance:{normalize:!0,dedupe:!0,mapper:Za},LatinAdvanced:{normalize:!0,dedupe:!0,mapper:Za,matcher:$a,replacer:ab},LatinExtra:{normalize:!0,dedupe:!0,mapper:Za,replacer:ab.concat([/(?!^)[aeo]/g,""]),matcher:$a},LatinSoundex:{normalize:!0,dedupe:!1,include:{letter:!0},finalize:function(a){for(let b=0;bu;f--){g=r.substring(u,f);v=this.rtl?d-1-u:u;var h=this.score?this.score(c,r,p,g,v):eb(q,e,p,d,v); +fb(this,n,g,h,a,b)}break}case "bidirectional":case "reverse":if(1g?0:1), +e,p,h-1,k-1),v=this.bidirectional&&r>f;fb(this,l,v?f:r,u,a,b,v?r:f)}}}}this.fastupdate||this.reg.add(a)}else c=""}this.db&&(c||this.commit_task.push({del:a}),this.T&&gb(this));return this}; +function fb(a,c,b,e,d,f,g){let h=g?a.ctx:a.map,k;if(!c[b]||g&&!(k=c[b])[g])if(g?(c=k||(c[b]=B()),c[g]=1,(k=h.get(g))?h=k:h.set(g,h=new Map)):c[b]=1,(k=h.get(b))?h=k:h.set(b,h=k=[]),h=h[e]||(h[e]=[]),!f||!h.includes(d)){if(h.length===2**31-1){c=new R(h);if(a.fastupdate)for(let l of a.reg.values())l.includes(h)&&(l[l.indexOf(h)]=c);k[e]=h=c}h.push(d);a.fastupdate&&((e=a.reg.get(d))?e.push(h):a.reg.set(d,[h]))}} +function eb(a,c,b,e,d){return b&&1b)&&(k=b,b=c,c=k);if(a.db)return a.db.get(c,b,e,d,f,g,h);a=b?(a=a.ctx.get(b))&&a.get(c):a.map.get(c);return a};N.prototype.remove=function(a,c){const b=this.reg.size&&(this.fastupdate?this.reg.get(a):this.reg.has(a));if(b){if(this.fastupdate)for(let e=0,d;ed.length)d.pop();else{const f=d.indexOf(a);f===b.length-1?d.pop():d.splice(f,1)}}else lb(this.map,a),this.depth&&lb(this.ctx,a);c||this.reg.delete(a)}this.db&&(this.commit_task.push({del:a}),this.T&&gb(this));this.cache&&this.cache.remove(a);return this}; +function lb(a,c){let b=0;if(a.constructor===Array)for(let e=0,d,f;eb.add(a,c)):this.add(a,c)}; +function mb(a){let c=0;if(a.constructor===Array)for(let b=0,e;b=n.length){e-=n.length;continue}const m=b?e+Math.min(n.length-e,b):n.length;for(let q=e;q=f.length)return[];if(!c&&!b)return f;f=f.slice(b,b+c);return e?d.enrich(f):f})}; +t.enrich=function(a){"object"!==typeof a&&(a=[a]);const c=this.db.transaction("reg","readonly").objectStore("reg"),b=[];for(let e=0;e{a.onsuccess=a.oncomplete=function(){c&&c(this.result);c=null;b(this.result)};a.onerror=a.onblocked=e;a=null})};const rb={Index:N,Charset:cb,Encoder:J,Document:U,Worker:P,Resolver:W,IndexedDB:pb,Language:{}},tb="undefined"!==typeof self?self:"undefined"!==typeof global?global:self;let ub;(ub=tb.define)&&ub.amd?ub([],function(){return rb}):"object"===typeof tb.exports?tb.exports=rb:tb.FlexSearch=rb;}(this||self)); diff --git a/public/js/flexsearch.9f5b5908f93ae86f1ecd4b043b799f580c2d1654e703dd9357d568ac41b2547a.js b/public/js/flexsearch.9f5b5908f93ae86f1ecd4b043b799f580c2d1654e703dd9357d568ac41b2547a.js new file mode 100644 index 0000000..82ae0dd --- /dev/null +++ b/public/js/flexsearch.9f5b5908f93ae86f1ecd4b043b799f580c2d1654e703dd9357d568ac41b2547a.js @@ -0,0 +1,2525 @@ +/**! + * FlexSearch.js v0.8.143 (Bundle/Debug) + * Author and Copyright: Thomas Wilkerling + * Licence: Apache-2.0 + * Hosted by Nextapps GmbH + * https://github.com/nextapps-de/flexsearch + */ +(function _f(self){'use strict';if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f; +var t; +function z(a, c, b) { + const e = typeof b, d = typeof a; + if ("undefined" !== e) { + if ("undefined" !== d) { + if (b) { + if ("function" === d && e === d) { + return function(k) { + return a(b(k)); + }; + } + c = a.constructor; + if (c === b.constructor) { + if (c === Array) { + return b.concat(a); + } + if (c === Map) { + var f = new Map(b); + for (var g of a) { + f.set(g[0], g[1]); + } + return f; + } + if (c === Set) { + g = new Set(b); + for (f of a.values()) { + g.add(f); + } + return g; + } + } + } + return a; + } + return b; + } + return "undefined" === d ? c : a; +} +function B() { + return Object.create(null); +} +function E(a) { + return "string" === typeof a; +} +function I(a) { + return "object" === typeof a; +} +function aa(a) { + const c = []; + for (const b of a.keys()) { + c.push(b); + } + return c; +} +function ba(a, c) { + if (E(c)) { + a = a[c]; + } else { + for (let b = 0; a && b < c.length; b++) { + a = a[c[b]]; + } + } + return a; +} +function ca(a) { + let c = 0; + for (let b = 0, e; b < a.length; b++) { + (e = a[b]) && c < e.length && (c = e.length); + } + return c; +} +;const da = /[^\p{L}\p{N}]+/u, ea = /(\d{3})/g, fa = /(\D)(\d{3})/g, ha = /(\d{3})(\D)/g, ia = /[\u0300-\u036f]/g; +function ja(a = {}) { + if (!this || this.constructor !== ja) { + return new ja(...arguments); + } + if (arguments.length) { + for (a = 0; a < arguments.length; a++) { + this.assign(arguments[a]); + } + } else { + this.assign(a); + } +} +t = ja.prototype; +t.assign = function(a) { + this.normalize = z(a.normalize, !0, this.normalize); + let c = a.include, b = c || a.exclude || a.split, e; + if (b || "" === b) { + if ("object" === typeof b && b.constructor !== RegExp) { + let d = ""; + e = !c; + c || (d += "\\p{Z}"); + b.letter && (d += "\\p{L}"); + b.number && (d += "\\p{N}", e = !!c); + b.symbol && (d += "\\p{S}"); + b.punctuation && (d += "\\p{P}"); + b.control && (d += "\\p{C}"); + if (b = b.char) { + d += "object" === typeof b ? b.join("") : b; + } + try { + this.split = new RegExp("[" + (c ? "^" : "") + d + "]+", "u"); + } catch (f) { + console.error("Your split configuration:", b, "is not supported on this platform. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; + } + } else { + this.split = b, e = !1 === b || 2 > "a1a".split(b).length; + } + this.numeric = z(a.numeric, e); + } else { + try { + this.split = z(this.split, da); + } catch (d) { + console.warn("This platform does not support unicode regex. It falls back to using simple whitespace splitter instead: /s+/."), this.split = /\s+/; + } + this.numeric = z(a.numeric, z(this.numeric, !0)); + } + this.prepare = z(a.prepare, null, this.prepare); + this.finalize = z(a.finalize, null, this.finalize); + b = a.filter; + this.filter = "function" === typeof b ? b : z(b && new Set(b), null, this.filter); + this.dedupe = z(a.dedupe, !1, this.dedupe); + this.matcher = z((b = a.matcher) && new Map(b), null, this.matcher); + this.mapper = z((b = a.mapper) && new Map(b), null, this.mapper); + this.stemmer = z((b = a.stemmer) && new Map(b), null, this.stemmer); + this.replacer = z(a.replacer, null, this.replacer); + this.minlength = z(a.minlength, 1, this.minlength); + this.maxlength = z(a.maxlength, 0, this.maxlength); + this.rtl = z(a.rtl, !1, this.rtl); + if (this.cache = b = z(a.cache, !0, this.cache)) { + this.H = null, this.S = "number" === typeof b ? b : 2e5, this.B = new Map(), this.G = new Map(), this.L = this.K = 128; + } + this.h = ""; + this.M = null; + this.A = ""; + this.N = null; + if (this.matcher) { + for (const d of this.matcher.keys()) { + this.h += (this.h ? "|" : "") + d; + } + } + if (this.stemmer) { + for (const d of this.stemmer.keys()) { + this.A += (this.A ? "|" : "") + d; + } + } + return this; +}; +t.addStemmer = function(a, c) { + this.stemmer || (this.stemmer = new Map()); + this.stemmer.set(a, c); + this.A += (this.A ? "|" : "") + a; + this.N = null; + this.cache && J(this); + return this; +}; +t.addFilter = function(a) { + "function" === typeof a ? this.filter = a : (this.filter || (this.filter = new Set()), this.filter.add(a)); + this.cache && J(this); + return this; +}; +t.addMapper = function(a, c) { + if ("object" === typeof a) { + return this.addReplacer(a, c); + } + if (1 < a.length) { + return this.addMatcher(a, c); + } + this.mapper || (this.mapper = new Map()); + this.mapper.set(a, c); + this.cache && J(this); + return this; +}; +t.addMatcher = function(a, c) { + if ("object" === typeof a) { + return this.addReplacer(a, c); + } + if (2 > a.length && (this.dedupe || this.mapper)) { + return this.addMapper(a, c); + } + this.matcher || (this.matcher = new Map()); + this.matcher.set(a, c); + this.h += (this.h ? "|" : "") + a; + this.M = null; + this.cache && J(this); + return this; +}; +t.addReplacer = function(a, c) { + if ("string" === typeof a) { + return this.addMatcher(a, c); + } + this.replacer || (this.replacer = []); + this.replacer.push(a, c); + this.cache && J(this); + return this; +}; +t.encode = function(a) { + if (this.cache && a.length <= this.K) { + if (this.H) { + if (this.B.has(a)) { + return this.B.get(a); + } + } else { + this.H = setTimeout(J, 50, this); + } + } + this.normalize && ("function" === typeof this.normalize ? a = this.normalize(a) : a = ia ? a.normalize("NFKD").replace(ia, "").toLowerCase() : a.toLowerCase()); + this.prepare && (a = this.prepare(a)); + this.numeric && 3 < a.length && (a = a.replace(fa, "$1 $2").replace(ha, "$1 $2").replace(ea, "$1 ")); + const c = !(this.dedupe || this.mapper || this.filter || this.matcher || this.stemmer || this.replacer); + let b = [], e = this.split || "" === this.split ? a.split(this.split) : a; + for (let f = 0, g, k; f < e.length; f++) { + if ((g = k = e[f]) && !(g.length < this.minlength)) { + if (c) { + b.push(g); + } else { + if (!this.filter || ("function" === typeof this.filter ? this.filter(g) : !this.filter.has(g))) { + if (this.cache && g.length <= this.L) { + if (this.H) { + var d = this.G.get(g); + if (d || "" === d) { + d && b.push(d); + continue; + } + } else { + this.H = setTimeout(J, 50, this); + } + } + this.stemmer && 2 < g.length && (this.N || (this.N = new RegExp("(?!^)(" + this.A + ")$")), d = g, g = g.replace(this.N, h => this.stemmer.get(h)), d !== g && this.filter && g.length >= this.minlength && ("function" === typeof this.filter ? !this.filter(g) : this.filter.has(g)) && (g = "")); + if (g && (this.mapper || this.dedupe && 1 < g.length)) { + d = ""; + for (let h = 0, l = "", n, m; h < g.length; h++) { + n = g.charAt(h), n === l && this.dedupe || ((m = this.mapper && this.mapper.get(n)) || "" === m ? m === l && this.dedupe || !(l = m) || (d += m) : d += l = n); + } + g = d; + } + this.matcher && 1 < g.length && (this.M || (this.M = new RegExp("(" + this.h + ")", "g")), g = g.replace(this.M, h => this.matcher.get(h))); + if (g && this.replacer) { + for (d = 0; g && d < this.replacer.length; d += 2) { + g = g.replace(this.replacer[d], this.replacer[d + 1]); + } + } + this.cache && k.length <= this.L && (this.G.set(k, g), this.G.size > this.S && (this.G.clear(), this.L = this.L / 1.1 | 0)); + g && b.push(g); + } + } + } + } + this.finalize && (b = this.finalize(b) || b); + this.cache && a.length <= this.K && (this.B.set(a, b), this.B.size > this.S && (this.B.clear(), this.K = this.K / 1.1 | 0)); + return b; +}; +function J(a) { + a.H = null; + a.B.clear(); + a.G.clear(); +} +;let K, M; +async function ka(a) { + a = a.data; + var c = a.task; + const b = a.id; + let e = a.args; + switch(c) { + case "init": + M = a.options || {}; + (c = a.factory) ? (Function("return " + c)()(self), K = new self.FlexSearch.Index(M), delete self.FlexSearch) : K = new N(M); + postMessage({id:b}); + break; + default: + let d; + if ("export" === c) { + if (!M.export || "function" !== typeof M.export) { + throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "export".'); + } + e[1] ? (e[0] = M.export, e[2] = 0, e[3] = 1) : e = null; + } + if ("import" === c) { + if (!M.import || "function" !== typeof M.import) { + throw Error('Either no extern configuration provided for the Worker-Index or no method was defined on the config property "import".'); + } + e[0] && (a = await M.import.call(K, e[0]), K.import(e[0], a)); + } else { + (d = e && K[c].apply(K, e)) && d.then && (d = await d); + } + postMessage("search" === c ? {id:b, msg:d} : {id:b}); + } +} +;function la(a) { + ma.call(a, "add"); + ma.call(a, "append"); + ma.call(a, "search"); + ma.call(a, "update"); + ma.call(a, "remove"); +} +let na, oa, pa; +function qa() { + na = pa = 0; +} +function ma(a) { + this[a + "Async"] = function() { + const c = arguments; + var b = c[c.length - 1]; + let e; + "function" === typeof b && (e = b, delete c[c.length - 1]); + na ? pa || (pa = Date.now() - oa >= this.priority * this.priority * 3) : (na = setTimeout(qa, 0), oa = Date.now()); + if (pa) { + const f = this; + return new Promise(g => { + setTimeout(function() { + g(f[a + "Async"].apply(f, c)); + }, 0); + }); + } + const d = this[a].apply(this, c); + b = d.then ? d : new Promise(f => f(d)); + e && b.then(e); + return b; + }; +} +;let O = 0; +function P(a = {}) { + function c(g) { + function k(h) { + h = h.data || h; + const l = h.id, n = l && d.h[l]; + n && (n(h.msg), delete d.h[l]); + } + this.worker = g; + this.h = B(); + if (this.worker) { + e ? this.worker.on("message", k) : this.worker.onmessage = k; + if (a.config) { + return new Promise(function(h) { + d.h[++O] = function() { + h(d); + 1e9 < O && (O = 0); + }; + d.worker.postMessage({id:O, task:"init", factory:b, options:a}); + }); + } + this.worker.postMessage({task:"init", factory:b, options:a}); + this.priority = a.priority || 4; + return this; + } + } + if (!this || this.constructor !== P) { + return new P(a); + } + let b = "undefined" !== typeof self ? self._factory : "undefined" !== typeof window ? window._factory : null; + b && (b = b.toString()); + const e = "undefined" === typeof window, d = this, f = ra(b, e, a.worker); + return f.then ? f.then(function(g) { + return c.call(d, g); + }) : c.call(this, f); +} +Q("add"); +Q("append"); +Q("search"); +Q("update"); +Q("remove"); +Q("clear"); +Q("export"); +Q("import"); +la(P.prototype); +function Q(a) { + P.prototype[a] = function() { + const c = this, b = [].slice.call(arguments); + var e = b[b.length - 1]; + let d; + "function" === typeof e && (d = e, b.pop()); + e = new Promise(function(f) { + "export" === a && "function" === typeof b[0] && (b[0] = null); + c.h[++O] = f; + c.worker.postMessage({task:a, id:O, args:b}); + }); + return d ? (e.then(d), this) : e; + }; +} +function ra(a, c, b) { + return c ? "undefined" !== typeof module ? new(require("worker_threads")["Worker"])(__dirname+"/node/node.js") : import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)("import.meta.dirname")+"/node/node.mjs")}) : a ? new window.Worker(URL.createObjectURL(new Blob(["onmessage=" + ka.toString()], {type:"text/javascript"}))) : new window.Worker("string" === typeof b ? b : (0,eval)("import.meta.url").replace("/worker.js", "/worker/worker.js").replace("flexsearch.bundle.module.min.js", + "module/worker/worker.js"), {type:"module"}); +} +;function sa(a, c = 0) { + let b = [], e = []; + c && (c = 250000 / c * 5000 | 0); + for (const d of a.entries()) { + e.push(d), e.length === c && (b.push(e), e = []); + } + e.length && b.push(e); + return b; +} +function ta(a, c) { + c || (c = new Map()); + for (let b = 0, e; b < a.length; b++) { + e = a[b], c.set(e[0], e[1]); + } + return c; +} +function ua(a, c = 0) { + let b = [], e = []; + c && (c = 250000 / c * 1000 | 0); + for (const d of a.entries()) { + e.push([d[0], sa(d[1])[0]]), e.length === c && (b.push(e), e = []); + } + e.length && b.push(e); + return b; +} +function va(a, c) { + c || (c = new Map()); + for (let b = 0, e, d; b < a.length; b++) { + e = a[b], d = c.get(e[0]), c.set(e[0], ta(e[1], d)); + } + return c; +} +function wa(a) { + let c = [], b = []; + for (const e of a.keys()) { + b.push(e), 250000 === b.length && (c.push(b), b = []); + } + b.length && c.push(b); + return c; +} +function xa(a, c) { + c || (c = new Set()); + for (let b = 0; b < a.length; b++) { + c.add(a[b]); + } + return c; +} +function ya(a, c, b, e, d, f, g = 0) { + const k = e && e.constructor === Array; + var h = k ? e.shift() : e; + if (!h) { + return this.export(a, c, d, f + 1); + } + if ((h = a((c ? c + "." : "") + (g + 1) + "." + b, JSON.stringify(h))) && h.then) { + const l = this; + return h.then(function() { + return ya.call(l, a, c, b, k ? e : null, d, f, g + 1); + }); + } + return ya.call(this, a, c, b, k ? e : null, d, f, g + 1); +} +function za(a, c) { + let b = ""; + for (const e of a.entries()) { + a = e[0]; + const d = e[1]; + let f = ""; + for (let g = 0, k; g < d.length; g++) { + k = d[g] || [""]; + let h = ""; + for (let l = 0; l < k.length; l++) { + h += (h ? "," : "") + ("string" === c ? '"' + k[l] + '"' : k[l]); + } + h = "[" + h + "]"; + f += (f ? "," : "") + h; + } + f = '["' + a + '",[' + f + "]]"; + b += (b ? "," : "") + f; + } + return b; +} +;function Aa(a, c, b, e) { + let d = []; + for (let f = 0, g; f < a.index.length; f++) { + if (g = a.index[f], c >= g.length) { + c -= g.length; + } else { + c = g[e ? "splice" : "slice"](c, b); + const k = c.length; + if (k && (d = d.length ? d.concat(c) : c, b -= k, e && (a.length -= k), !b)) { + break; + } + c = 0; + } + } + return d; +} +function R(a) { + if (!this || this.constructor !== R) { + return new R(a); + } + this.index = a ? [a] : []; + this.length = a ? a.length : 0; + const c = this; + return new Proxy([], {get(b, e) { + if ("length" === e) { + return c.length; + } + if ("push" === e) { + return function(d) { + c.index[c.index.length - 1].push(d); + c.length++; + }; + } + if ("pop" === e) { + return function() { + if (c.length) { + return c.length--, c.index[c.index.length - 1].pop(); + } + }; + } + if ("indexOf" === e) { + return function(d) { + let f = 0; + for (let g = 0, k, h; g < c.index.length; g++) { + k = c.index[g]; + h = k.indexOf(d); + if (0 <= h) { + return f + h; + } + f += k.length; + } + return -1; + }; + } + if ("includes" === e) { + return function(d) { + for (let f = 0; f < c.index.length; f++) { + if (c.index[f].includes(d)) { + return !0; + } + } + return !1; + }; + } + if ("slice" === e) { + return function(d, f) { + return Aa(c, d || 0, f || c.length, !1); + }; + } + if ("splice" === e) { + return function(d, f) { + return Aa(c, d || 0, f || c.length, !0); + }; + } + if ("constructor" === e) { + return Array; + } + if ("symbol" !== typeof e) { + return (b = c.index[e / 2 ** 31 | 0]) && b[e]; + } + }, set(b, e, d) { + b = e / 2 ** 31 | 0; + (c.index[b] || (c.index[b] = []))[e] = d; + c.length++; + return !0; + }}); +} +R.prototype.clear = function() { + this.index.length = 0; +}; +R.prototype.destroy = function() { + this.proxy = this.index = null; +}; +R.prototype.push = function() { +}; +function S(a = 8) { + if (!this || this.constructor !== S) { + return new S(a); + } + this.index = B(); + this.h = []; + this.size = 0; + 32 < a ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); +} +S.prototype.get = function(a) { + const c = this.index[this.B(a)]; + return c && c.get(a); +}; +S.prototype.set = function(a, c) { + var b = this.B(a); + let e = this.index[b]; + e ? (b = e.size, e.set(a, c), (b -= e.size) && this.size++) : (this.index[b] = e = new Map([[a, c]]), this.h.push(e), this.size++); +}; +function T(a = 8) { + if (!this || this.constructor !== T) { + return new T(a); + } + this.index = B(); + this.h = []; + this.size = 0; + 32 < a ? (this.B = Ba, this.A = BigInt(a)) : (this.B = Ca, this.A = a); +} +T.prototype.add = function(a) { + var c = this.B(a); + let b = this.index[c]; + b ? (c = b.size, b.add(a), (c -= b.size) && this.size++) : (this.index[c] = b = new Set([a]), this.h.push(b), this.size++); +}; +t = S.prototype; +t.has = T.prototype.has = function(a) { + const c = this.index[this.B(a)]; + return c && c.has(a); +}; +t.delete = T.prototype.delete = function(a) { + const c = this.index[this.B(a)]; + c && c.delete(a) && this.size--; +}; +t.clear = T.prototype.clear = function() { + this.index = B(); + this.h = []; + this.size = 0; +}; +t.values = T.prototype.values = function*() { + for (let a = 0; a < this.h.length; a++) { + for (let c of this.h[a].values()) { + yield c; + } + } +}; +t.keys = T.prototype.keys = function*() { + for (let a = 0; a < this.h.length; a++) { + for (let c of this.h[a].keys()) { + yield c; + } + } +}; +t.entries = T.prototype.entries = function*() { + for (let a = 0; a < this.h.length; a++) { + for (let c of this.h[a].entries()) { + yield c; + } + } +}; +function Ca(a) { + let c = 2 ** this.A - 1; + if ("number" == typeof a) { + return a & c; + } + let b = 0, e = this.A + 1; + for (let d = 0; d < a.length; d++) { + b = (b * e ^ a.charCodeAt(d)) & c; + } + return 32 === this.A ? b + 2 ** 31 : b; +} +function Ba(a) { + let c = BigInt(2) ** this.A - BigInt(1); + var b = typeof a; + if ("bigint" === b) { + return a & c; + } + if ("number" === b) { + return BigInt(a) & c; + } + b = BigInt(0); + let e = this.A + BigInt(1); + for (let d = 0; d < a.length; d++) { + b = (b * e ^ BigInt(a.charCodeAt(d))) & c; + } + return b; +} +;U.prototype.add = function(a, c, b) { + I(a) && (c = a, a = ba(c, this.key)); + if (c && (a || 0 === a)) { + if (!b && this.reg.has(a)) { + return this.update(a, c); + } + for (let k = 0, h; k < this.field.length; k++) { + h = this.D[k]; + var e = this.index.get(this.field[k]); + if ("function" === typeof h) { + var d = h(c); + d && e.add(a, d, !1, !0); + } else { + if (d = h.I, !d || d(c)) { + h.constructor === String ? h = ["" + h] : E(h) && (h = [h]), Da(c, h, this.J, 0, e, a, h[0], b); + } + } + } + if (this.tag) { + for (e = 0; e < this.F.length; e++) { + var f = this.F[e], g = this.R[e]; + d = this.tag.get(g); + let k = B(); + if ("function" === typeof f) { + if (f = f(c), !f) { + continue; + } + } else { + const h = f.I; + if (h && !h(c)) { + continue; + } + f.constructor === String && (f = "" + f); + f = ba(c, f); + } + if (d && f) { + E(f) && (f = [f]); + for (let h = 0, l, n; h < f.length; h++) { + if (l = f[h], !k[l] && (k[l] = 1, (g = d.get(l)) ? n = g : d.set(l, n = []), !b || !n.includes(a))) { + if (n.length === 2 ** 31 - 1) { + g = new R(n); + if (this.fastupdate) { + for (let m of this.reg.values()) { + m.includes(n) && (m[m.indexOf(n)] = g); + } + } + d.set(l, n = g); + } + n.push(a); + this.fastupdate && ((g = this.reg.get(a)) ? g.push(n) : this.reg.set(a, [n])); + } + } + } else { + d || console.warn("Tag '" + g + "' was not found"); + } + } + } + if (this.store && (!b || !this.store.has(a))) { + let k; + if (this.C) { + k = B(); + for (let h = 0, l; h < this.C.length; h++) { + l = this.C[h]; + if ((b = l.I) && !b(c)) { + continue; + } + let n; + if ("function" === typeof l) { + n = l(c); + if (!n) { + continue; + } + l = [l.V]; + } else if (E(l) || l.constructor === String) { + k[l] = c[l]; + continue; + } + Ea(c, k, l, 0, l[0], n); + } + } + this.store.set(a, k || c); + } + this.worker && (this.fastupdate || this.reg.add(a)); + } + return this; +}; +function Ea(a, c, b, e, d, f) { + a = a[d]; + if (e === b.length - 1) { + c[d] = f || a; + } else if (a) { + if (a.constructor === Array) { + for (c = c[d] = Array(a.length), d = 0; d < a.length; d++) { + Ea(a, c, b, e, d); + } + } else { + c = c[d] || (c[d] = B()), d = b[++e], Ea(a, c, b, e, d); + } + } +} +function Da(a, c, b, e, d, f, g, k) { + if (a = a[g]) { + if (e === c.length - 1) { + if (a.constructor === Array) { + if (b[e]) { + for (c = 0; c < a.length; c++) { + d.add(f, a[c], !0, !0); + } + return; + } + a = a.join(" "); + } + d.add(f, a, k, !0); + } else { + if (a.constructor === Array) { + for (g = 0; g < a.length; g++) { + Da(a, c, b, e, d, f, g, k); + } + } else { + g = c[++e], Da(a, c, b, e, d, f, g, k); + } + } + } else { + d.db && d.remove(f); + } +} +;function Fa(a, c, b, e, d, f, g) { + const k = a.length; + let h = [], l, n; + l = B(); + for (let m = 0, q, p, r, u; m < c; m++) { + for (let v = 0; v < k; v++) { + if (r = a[v], m < r.length && (q = r[m])) { + for (let w = 0; w < q.length; w++) { + p = q[w]; + (n = l[p]) ? l[p]++ : (n = 0, l[p] = 1); + u = h[n] || (h[n] = []); + if (!g) { + let y = m + (v || !d ? 0 : f || 0); + u = u[y] || (u[y] = []); + } + u.push(p); + if (g && b && n === k - 1 && u.length - e === b) { + return u; + } + } + } + } + } + if (a = h.length) { + if (d) { + h = 1 < h.length ? Ga(h, b, e, g, f) : (h = h[0]).length > b || e ? h.slice(e, b + e) : h; + } else { + if (a < k) { + return []; + } + h = h[a - 1]; + if (b || e) { + if (g) { + if (h.length > b || e) { + h = h.slice(e, b + e); + } + } else { + d = []; + for (let m = 0, q; m < h.length; m++) { + if (q = h[m], q.length > e) { + e -= q.length; + } else { + if (q.length > b || e) { + q = q.slice(e, b + e), b -= q.length, e && (e -= q.length); + } + d.push(q); + if (!b) { + break; + } + } + } + h = 1 < d.length ? [].concat.apply([], d) : d[0]; + } + } + } + } + return h; +} +function Ga(a, c, b, e, d) { + const f = [], g = B(); + let k; + var h = a.length; + let l; + if (e) { + for (d = h - 1; 0 <= d; d--) { + if (l = (e = a[d]) && e.length) { + for (h = 0; h < l; h++) { + if (k = e[h], !g[k]) { + if (g[k] = 1, b) { + b--; + } else { + if (f.push(k), f.length === c) { + return f; + } + } + } + } + } + } + } else { + for (let n = h - 1, m, q = 0; 0 <= n; n--) { + m = a[n]; + for (let p = 0; p < m.length; p++) { + if (l = (e = m[p]) && e.length) { + for (let r = 0; r < l; r++) { + if (k = e[r], !g[k]) { + if (g[k] = 1, b) { + b--; + } else { + let u = (p + (n < h - 1 ? d || 0 : 0)) / (n + 1) | 0; + (f[u] || (f[u] = [])).push(k); + if (++q === c) { + return f; + } + } + } + } + } + } + } + } + return f; +} +function Ha(a, c, b) { + const e = B(), d = []; + for (let f = 0, g; f < c.length; f++) { + g = c[f]; + for (let k = 0; k < g.length; k++) { + e[g[k]] = 1; + } + } + if (b) { + for (let f = 0, g; f < a.length; f++) { + g = a[f], e[g] && (d.push(g), e[g] = 0); + } + } else { + for (let f = 0, g, k; f < a.result.length; f++) { + for (g = a.result[f], c = 0; c < g.length; c++) { + k = g[c], e[k] && ((d[f] || (d[f] = [])).push(k), e[k] = 0); + } + } + } + return d; +} +;function Ia(a, c, b, e) { + if (!a.length) { + return a; + } + if (1 === a.length) { + return a = a[0], a = b || a.length > c ? c ? a.slice(b, b + c) : a.slice(b) : a, e ? V.call(this, a) : a; + } + let d = []; + for (let f = 0, g, k; f < a.length; f++) { + if ((g = a[f]) && (k = g.length)) { + if (b) { + if (b >= k) { + b -= k; + continue; + } + b < k && (g = c ? g.slice(b, b + c) : g.slice(b), k = g.length, b = 0); + } + k > c && (g = g.slice(0, c), k = c); + if (!d.length && k >= c) { + return e ? V.call(this, g) : g; + } + d.push(g); + c -= k; + if (!c) { + break; + } + } + } + d = 1 < d.length ? [].concat.apply([], d) : d[0]; + return e ? V.call(this, d) : d; +} +;function Ja(a, c, b) { + var e = b[0]; + if (e.then) { + return Promise.all(b).then(function(n) { + return a[c].apply(a, n); + }); + } + if (e[0] && e[0].index) { + return a[c].apply(a, e); + } + e = []; + let d = [], f = 0, g = 0, k, h, l; + for (let n = 0, m; n < b.length; n++) { + if (m = b[n]) { + let q; + if (m.constructor === W) { + q = m.result; + } else if (m.constructor === Array) { + q = m; + } else { + if (f = m.limit || 0, g = m.offset || 0, l = m.suggest, h = m.resolve, k = m.enrich && h, m.index) { + m.resolve = !1, m.enrich = !1, q = m.index.search(m).result, m.resolve = h, m.enrich = k; + } else if (m.and) { + q = a.and(m.and); + } else if (m.or) { + q = a.or(m.or); + } else if (m.xor) { + q = a.xor(m.xor); + } else if (m.not) { + q = a.not(m.not); + } else { + continue; + } + } + if (q.then) { + d.push(q); + } else if (q.length) { + e[n] = q; + } else if (!l && ("and" === c || "xor" === c)) { + e = []; + break; + } + } + } + return {O:e, P:d, limit:f, offset:g, enrich:k, resolve:h, suggest:l}; +} +;W.prototype.or = function() { + const {O:a, P:c, limit:b, offset:e, enrich:d, resolve:f} = Ja(this, "or", arguments); + return Ka.call(this, a, c, b, e, d, f); +}; +function Ka(a, c, b, e, d, f) { + if (c.length) { + const g = this; + return Promise.all(c).then(function(k) { + a = []; + for (let h = 0, l; h < k.length; h++) { + (l = k[h]).length && (a[h] = l); + } + return Ka.call(g, a, [], b, e, d, f); + }); + } + a.length && (this.result.length && a.push(this.result), 2 > a.length ? this.result = a[0] : (this.result = Ga(a, b, e, !1, this.h), e = 0)); + return f ? this.resolve(b, e, d) : this; +} +;W.prototype.and = function() { + let a = this.result.length, c, b, e, d; + if (!a) { + const f = arguments[0]; + f && (a = !!f.suggest, d = f.resolve, c = f.limit, b = f.offset, e = f.enrich && d); + } + if (a) { + const {O:f, P:g, limit:k, offset:h, enrich:l, resolve:n, suggest:m} = Ja(this, "and", arguments); + return La.call(this, f, g, k, h, l, n, m); + } + return d ? this.resolve(c, b, e) : this; +}; +function La(a, c, b, e, d, f, g) { + if (c.length) { + const k = this; + return Promise.all(c).then(function(h) { + a = []; + for (let l = 0, n; l < h.length; l++) { + (n = h[l]).length && (a[l] = n); + } + return La.call(k, a, [], b, e, d, f, g); + }); + } + if (a.length) { + if (this.result.length && a.unshift(this.result), 2 > a.length) { + this.result = a[0]; + } else { + if (c = ca(a)) { + return this.result = Fa(a, c, b, e, g, this.h, f), f ? d ? V.call(this.index, this.result) : this.result : this; + } + this.result = []; + } + } else { + g || (this.result = a); + } + return f ? this.resolve(b, e, d) : this; +} +;W.prototype.xor = function() { + const {O:a, P:c, limit:b, offset:e, enrich:d, resolve:f, suggest:g} = Ja(this, "xor", arguments); + return Ma.call(this, a, c, b, e, d, f, g); +}; +function Ma(a, c, b, e, d, f, g) { + if (c.length) { + const k = this; + return Promise.all(c).then(function(h) { + a = []; + for (let l = 0, n; l < h.length; l++) { + (n = h[l]).length && (a[l] = n); + } + return Ma.call(k, a, [], b, e, d, f, g); + }); + } + if (a.length) { + if (this.result.length && a.unshift(this.result), 2 > a.length) { + this.result = a[0]; + } else { + return this.result = Na.call(this, a, b, e, f, this.h), f ? d ? V.call(this.index, this.result) : this.result : this; + } + } else { + g || (this.result = a); + } + return f ? this.resolve(b, e, d) : this; +} +function Na(a, c, b, e, d) { + const f = [], g = B(); + let k = 0; + for (let h = 0, l; h < a.length; h++) { + if (l = a[h]) { + k < l.length && (k = l.length); + for (let n = 0, m; n < l.length; n++) { + if (m = l[n]) { + for (let q = 0, p; q < m.length; q++) { + p = m[q], g[p] = g[p] ? 2 : 1; + } + } + } + } + } + for (let h = 0, l, n = 0; h < k; h++) { + for (let m = 0, q; m < a.length; m++) { + if (q = a[m]) { + if (l = q[h]) { + for (let p = 0, r; p < l.length; p++) { + if (r = l[p], 1 === g[r]) { + if (b) { + b--; + } else { + if (e) { + if (f.push(r), f.length === c) { + return f; + } + } else { + const u = h + (m ? d : 0); + f[u] || (f[u] = []); + f[u].push(r); + if (++n === c) { + return f; + } + } + } + } + } + } + } + } + } + return f; +} +;W.prototype.not = function() { + const {O:a, P:c, limit:b, offset:e, enrich:d, resolve:f, suggest:g} = Ja(this, "not", arguments); + return Oa.call(this, a, c, b, e, d, f, g); +}; +function Oa(a, c, b, e, d, f, g) { + if (c.length) { + const k = this; + return Promise.all(c).then(function(h) { + a = []; + for (let l = 0, n; l < h.length; l++) { + (n = h[l]).length && (a[l] = n); + } + return Oa.call(k, a, [], b, e, d, f, g); + }); + } + if (a.length && this.result.length) { + this.result = Pa.call(this, a, b, e, f); + } else if (f) { + return this.resolve(b, e, d); + } + return f ? d ? V.call(this.index, this.result) : this.result : this; +} +function Pa(a, c, b, e) { + const d = []; + a = new Set(a.flat().flat()); + for (let f = 0, g, k = 0; f < this.result.length; f++) { + if (g = this.result[f]) { + for (let h = 0, l; h < g.length; h++) { + if (l = g[h], !a.has(l)) { + if (b) { + b--; + } else { + if (e) { + if (d.push(l), d.length === c) { + return d; + } + } else { + if (d[f] || (d[f] = []), d[f].push(l), ++k === c) { + return d; + } + } + } + } + } + } + } + return d; +} +;function W(a) { + if (!this || this.constructor !== W) { + return new W(a); + } + if (a && a.index) { + return a.resolve = !1, this.index = a.index, this.h = a.boost || 0, this.result = a.index.search(a).result, this; + } + this.index = null; + this.result = a || []; + this.h = 0; +} +W.prototype.limit = function(a) { + if (this.result.length) { + const c = []; + for (let b = 0, e; b < this.result.length; b++) { + if (e = this.result[b]) { + if (e.length <= a) { + if (c[b] = e, a -= e.length, !a) { + break; + } + } else { + c[b] = e.slice(0, a); + break; + } + } + } + this.result = c; + } + return this; +}; +W.prototype.offset = function(a) { + if (this.result.length) { + const c = []; + for (let b = 0, e; b < this.result.length; b++) { + if (e = this.result[b]) { + e.length <= a ? a -= e.length : (c[b] = e.slice(a), a = 0); + } + } + this.result = c; + } + return this; +}; +W.prototype.boost = function(a) { + this.h += a; + return this; +}; +W.prototype.resolve = function(a, c, b) { + const e = this.result, d = this.index; + this.result = this.index = null; + return e.length ? ("object" === typeof a && (b = a.enrich, c = a.offset, a = a.limit), Ia.call(d, e, a || 100, c, b)) : e; +}; +B(); +U.prototype.search = function(a, c, b, e) { + b || (!c && I(a) ? (b = a, a = "") : I(c) && (b = c, c = 0)); + let d = []; + var f = [], g; + let k; + let h, l; + let n = 0; + var m = !0; + let q; + if (b) { + b.constructor === Array && (b = {index:b}); + a = b.query || a; + var p = b.pluck; + k = b.merge; + h = p || b.field || (h = b.index) && (h.index ? null : h); + l = this.tag && b.tag; + var r = b.suggest; + m = !1 !== b.resolve; + if (!m && !p) { + if (h = h || this.field) { + E(h) ? p = h : (h.constructor === Array && 1 === h.length && (h = h[0]), p = h.field || h.index); + } + if (!p) { + throw Error("Apply resolver on document search requires either the option 'pluck' to be set or just select a single field name in your query."); + } + } + this.store && b.enrich && !m && console.warn("Enrich results can only be done on a final resolver task or when calling .resolve({ enrich: true })"); + q = (g = this.store && b.enrich && m) && b.highlight; + c = b.limit || c; + var u = b.offset || 0; + c || (c = 100); + if (l && (!this.db || !e)) { + l.constructor !== Array && (l = [l]); + var v = []; + for (let A = 0, x; A < l.length; A++) { + x = l[A]; + if (E(x)) { + throw Error("A tag option can't be a string, instead it needs a { field: tag } format."); + } + if (x.field && x.tag) { + var w = x.tag; + if (w.constructor === Array) { + for (var y = 0; y < w.length; y++) { + v.push(x.field, w[y]); + } + } else { + v.push(x.field, w); + } + } else { + w = Object.keys(x); + for (let D = 0, H, C; D < w.length; D++) { + if (H = w[D], C = x[H], C.constructor === Array) { + for (y = 0; y < C.length; y++) { + v.push(H, C[y]); + } + } else { + v.push(H, C); + } + } + } + } + if (!v.length) { + throw Error("Your tag definition within the search options is probably wrong. No valid tags found."); + } + l = v; + if (!a) { + m = []; + if (v.length) { + for (f = 0; f < v.length; f += 2) { + if (this.db) { + p = this.index.get(v[f]); + if (!p) { + console.warn("Tag '" + v[f] + ":" + v[f + 1] + "' will be skipped because there is no field '" + v[f] + "'."); + continue; + } + m.push(p = p.db.tag(v[f + 1], c, u, g)); + } else { + p = Qa.call(this, v[f], v[f + 1], c, u, g); + } + d.push({field:v[f], tag:v[f + 1], result:p}); + } + } + return m.length ? Promise.all(m).then(function(A) { + for (let x = 0; x < A.length; x++) { + d[x].result = A[x]; + } + return d; + }) : d; + } + } + h && h.constructor !== Array && (h = [h]); + } + h || (h = this.field); + v = !e && (this.worker || this.db) && []; + let F; + for (let A = 0, x, D, H; A < h.length; A++) { + D = h[A]; + if (this.db && this.tag && !this.D[A]) { + continue; + } + let C; + E(D) || (C = D, D = C.field, a = C.query || a, c = C.limit || c, u = C.offset || u, r = C.suggest || r, g = this.store && (C.enrich || g)); + if (e) { + x = e[A]; + } else { + if (w = C || b, y = this.index.get(D), l && (this.db && (w.tag = l, F = y.db.support_tag_search, w.field = h), F || (w.enrich = !1)), v) { + v[A] = y.search(a, c, w); + w && g && (w.enrich = g); + continue; + } else { + x = y.search(a, c, w), w && g && (w.enrich = g); + } + } + H = x && (m ? x.length : x.result.length); + if (l && H) { + w = []; + y = 0; + if (this.db && e) { + if (!F) { + for (let G = h.length; G < e.length; G++) { + let L = e[G]; + if (L && L.length) { + y++, w.push(L); + } else if (!r) { + return m ? d : new W(d); + } + } + } + } else { + for (let G = 0, L, tb; G < l.length; G += 2) { + L = this.tag.get(l[G]); + if (!L) { + if (console.warn("Tag '" + l[G] + ":" + l[G + 1] + "' will be skipped because there is no field '" + l[G] + "'."), r) { + continue; + } else { + return m ? d : new W(d); + } + } + if (tb = (L = L && L.get(l[G + 1])) && L.length) { + y++, w.push(L); + } else if (!r) { + return m ? d : new W(d); + } + } + } + if (y) { + x = Ha(x, w, m); + H = x.length; + if (!H && !r) { + return m ? x : new W(x); + } + y--; + } + } + if (H) { + f[n] = D, d.push(x), n++; + } else if (1 === h.length) { + return m ? d : new W(d); + } + } + if (v) { + if (this.db && l && l.length && !F) { + for (g = 0; g < l.length; g += 2) { + f = this.index.get(l[g]); + if (!f) { + if (console.warn("Tag '" + l[g] + ":" + l[g + 1] + "' was not found because there is no field '" + l[g] + "'."), r) { + continue; + } else { + return m ? d : new W(d); + } + } + v.push(f.db.tag(l[g + 1], c, u, !1)); + } + } + const A = this; + return Promise.all(v).then(function(x) { + return x.length ? A.search(a, c, b, x) : x; + }); + } + if (!n) { + return m ? d : new W(d); + } + if (p && (!g || !this.store)) { + return d[0]; + } + v = []; + for (u = 0; u < f.length; u++) { + r = d[u]; + g && r.length && "undefined" === typeof r[0].doc && (this.db ? v.push(r = this.index.get(this.field[0]).db.enrich(r)) : r = V.call(this, r)); + if (p) { + return m ? r : new W(r); + } + d[u] = {field:f[u], result:r}; + } + if (g && this.db && v.length) { + const A = this; + return Promise.all(v).then(function(x) { + for (let D = 0; D < x.length; D++) { + d[D].result = x[D]; + } + return k ? Ra(d, c) : q ? Sa(d, a, A.index, A.field, A.D, q) : d; + }); + } + return k ? Ra(d, c) : q ? Sa(d, a, this.index, this.field, this.D, q) : d; +}; +function Sa(a, c, b, e, d, f) { + let g, k, h; + for (let n = 0, m, q, p, r; n < a.length; n++) { + let u = a[n].result; + m = a[n].field; + p = b.get(m); + q = p.encoder; + h = p.tokenize; + r = d[e.indexOf(m)]; + q !== g && (g = q, k = g.encode(c)); + for (let v = 0; v < u.length; v++) { + let w = ""; + var l = ba(u[v].doc, r); + let y = g.encode(l); + l = l.split(g.split); + for (let F = 0, A, x; F < y.length; F++) { + A = y[F]; + x = l[F]; + if (!A || !x) { + continue; + } + let D; + for (let H = 0, C; H < k.length; H++) { + if (C = k[H], "strict" === h) { + if (A === C) { + w += (w ? " " : "") + f.replace("$1", x); + D = !0; + break; + } + } else { + const G = A.indexOf(C); + if (-1 < G) { + w += (w ? " " : "") + x.substring(0, G) + f.replace("$1", x.substring(G, C.length)) + x.substring(G + C.length); + D = !0; + break; + } + } + } + D || (w += (w ? " " : "") + l[F]); + } + u[v].highlight = w; + } + } + return a; +} +function Ra(a, c) { + const b = [], e = B(); + for (let d = 0, f, g; d < a.length; d++) { + f = a[d]; + g = f.result; + for (let k = 0, h, l, n; k < g.length; k++) { + if (l = g[k], "object" !== typeof l && (l = {id:l}), h = l.id, n = e[h]) { + n.push(f.field); + } else { + if (b.length === c) { + return b; + } + l.field = e[h] = [f.field]; + b.push(l); + } + } + } + return b; +} +function Qa(a, c, b, e, d) { + let f = this.tag.get(a); + if (!f) { + return console.warn("Tag '" + a + "' was not found"), []; + } + if ((a = (f = f && f.get(c)) && f.length - e) && 0 < a) { + if (a > b || e) { + f = f.slice(e, e + b); + } + d && (f = V.call(this, f)); + return f; + } +} +function V(a) { + if (!this || !this.store) { + return a; + } + const c = Array(a.length); + for (let b = 0, e; b < a.length; b++) { + e = a[b], c[b] = {id:e, doc:this.store.get(e)}; + } + return c; +} +;function U(a) { + if (!this || this.constructor !== U) { + return new U(a); + } + const c = a.document || a.doc || a; + let b, e; + this.D = []; + this.field = []; + this.J = []; + this.key = (b = c.key || c.id) && Ta(b, this.J) || "id"; + (e = a.keystore || 0) && (this.keystore = e); + this.fastupdate = !!a.fastupdate; + this.reg = !this.fastupdate || a.worker || a.db ? e ? new T(e) : new Set() : e ? new S(e) : new Map(); + this.C = (b = c.store || null) && b && !0 !== b && []; + this.store = b && (e ? new S(e) : new Map()); + this.cache = (b = a.cache || null) && new X(b); + a.cache = !1; + this.worker = a.worker; + this.priority = a.priority || 4; + this.index = Ua.call(this, a, c); + this.tag = null; + if (b = c.tag) { + if ("string" === typeof b && (b = [b]), b.length) { + this.tag = new Map(); + this.F = []; + this.R = []; + for (let d = 0, f, g; d < b.length; d++) { + f = b[d]; + g = f.field || f; + if (!g) { + throw Error("The tag field from the document descriptor is undefined."); + } + f.custom ? this.F[d] = f.custom : (this.F[d] = Ta(g, this.J), f.filter && ("string" === typeof this.F[d] && (this.F[d] = new String(this.F[d])), this.F[d].I = f.filter)); + this.R[d] = g; + this.tag.set(g, new Map()); + } + } + } + if (this.worker) { + this.fastupdate = !1; + a = []; + for (const d of this.index.values()) { + d.then && a.push(d); + } + if (a.length) { + const d = this; + return Promise.all(a).then(function(f) { + let g = 0; + for (const k of d.index.entries()) { + const h = k[0]; + k[1].then && d.index.set(h, f[g++]); + } + return d; + }); + } + } else { + a.db && (this.fastupdate = !1, this.mount(a.db)); + } +} +t = U.prototype; +t.mount = function(a) { + if (this.worker) { + throw Error("You can't use Worker-Indexes on a persistent model. That would be useless, since each of the persistent model acts like Worker-Index by default (Master/Slave)."); + } + let c = this.field; + if (this.tag) { + for (let d = 0, f; d < this.R.length; d++) { + f = this.R[d]; + var b = void 0; + this.index.set(f, b = new N({}, this.reg)); + c === this.field && (c = c.slice(0)); + c.push(f); + b.tag = this.tag.get(f); + } + } + b = []; + const e = {db:a.db, type:a.type, fastupdate:a.fastupdate}; + for (let d = 0, f, g; d < c.length; d++) { + e.field = g = c[d]; + f = this.index.get(g); + const k = new a.constructor(a.id, e); + k.id = a.id; + b[d] = k.mount(f); + f.document = !0; + d ? f.bypass = !0 : f.store = this.store; + } + this.db = !0; + return Promise.all(b); +}; +t.commit = async function(a, c) { + const b = []; + for (const e of this.index.values()) { + b.push(e.commit(a, c)); + } + await Promise.all(b); + this.reg.clear(); +}; +t.destroy = function() { + const a = []; + for (const c of this.index.values()) { + a.push(c.destroy()); + } + return Promise.all(a); +}; +function Ua(a, c) { + const b = new Map(); + let e = c.index || c.field || c; + E(e) && (e = [e]); + for (let d = 0, f, g; d < e.length; d++) { + f = e[d]; + E(f) || (g = f, f = f.field); + g = I(g) ? Object.assign({}, a, g) : a; + if (this.worker) { + const k = new P(g); + b.set(f, k); + } + this.worker || b.set(f, new N(g, this.reg)); + g.custom ? this.D[d] = g.custom : (this.D[d] = Ta(f, this.J), g.filter && ("string" === typeof this.D[d] && (this.D[d] = new String(this.D[d])), this.D[d].I = g.filter)); + this.field[d] = f; + } + if (this.C) { + a = c.store; + E(a) && (a = [a]); + for (let d = 0, f, g; d < a.length; d++) { + f = a[d], g = f.field || f, f.custom ? (this.C[d] = f.custom, f.custom.V = g) : (this.C[d] = Ta(g, this.J), f.filter && ("string" === typeof this.C[d] && (this.C[d] = new String(this.C[d])), this.C[d].I = f.filter)); + } + } + return b; +} +function Ta(a, c) { + const b = a.split(":"); + let e = 0; + for (let d = 0; d < b.length; d++) { + a = b[d], "]" === a[a.length - 1] && (a = a.substring(0, a.length - 2)) && (c[e] = !0), a && (b[e++] = a); + } + e < b.length && (b.length = e); + return 1 < e ? b : b[0]; +} +t.append = function(a, c) { + return this.add(a, c, !0); +}; +t.update = function(a, c) { + return this.remove(a).add(a, c); +}; +t.remove = function(a) { + I(a) && (a = ba(a, this.key)); + for (var c of this.index.values()) { + c.remove(a, !0); + } + if (this.reg.has(a)) { + if (this.tag && !this.fastupdate) { + for (let b of this.tag.values()) { + for (let e of b) { + c = e[0]; + const d = e[1], f = d.indexOf(a); + -1 < f && (1 < d.length ? d.splice(f, 1) : b.delete(c)); + } + } + } + this.store && this.store.delete(a); + this.reg.delete(a); + } + this.cache && this.cache.remove(a); + return this; +}; +t.clear = function() { + const a = []; + for (const c of this.index.values()) { + const b = c.clear(); + b.then && a.push(b); + } + if (this.tag) { + for (const c of this.tag.values()) { + c.clear(); + } + } + this.store && this.store.clear(); + this.cache && this.cache.clear(); + return a.length ? Promise.all(a) : this; +}; +t.contain = function(a) { + return this.db ? this.index.get(this.field[0]).db.has(a) : this.reg.has(a); +}; +t.cleanup = function() { + for (const a of this.index.values()) { + a.cleanup(); + } + return this; +}; +t.get = function(a) { + return this.db ? this.index.get(this.field[0]).db.enrich(a).then(function(c) { + return c[0] && c[0].doc; + }) : this.store.get(a); +}; +t.set = function(a, c) { + this.store.set(a, c); + return this; +}; +t.searchCache = Va; +t.export = function(a, c, b = 0, e = 0) { + if (b < this.field.length) { + const g = this.field[b]; + if ((c = this.index.get(g).export(a, g, b, e = 1)) && c.then) { + const k = this; + return c.then(function() { + return k.export(a, g, b + 1); + }); + } + return this.export(a, g, b + 1); + } + let d, f; + switch(e) { + case 0: + d = "reg"; + f = wa(this.reg); + c = null; + break; + case 1: + d = "tag"; + f = this.tag && ua(this.tag, this.reg.size); + c = null; + break; + case 2: + d = "doc"; + f = this.store && sa(this.store); + c = null; + break; + default: + return; + } + return ya.call(this, a, c, d, f, b, e); +}; +t.import = function(a, c) { + var b = a.split("."); + "json" === b[b.length - 1] && b.pop(); + const e = 2 < b.length ? b[0] : ""; + b = 2 < b.length ? b[2] : b[1]; + if (this.worker && e) { + return this.index.get(e).import(a); + } + if (c) { + "string" === typeof c && (c = JSON.parse(c)); + if (e) { + return this.index.get(e).import(b, c); + } + switch(b) { + case "reg": + this.fastupdate = !1; + this.reg = xa(c, this.reg); + for (let d = 0, f; d < this.field.length; d++) { + f = this.index.get(this.field[d]), f.fastupdate = !1, f.reg = this.reg; + } + if (this.worker) { + c = []; + for (const d of this.index.values()) { + c.push(d.import(a)); + } + return Promise.all(c); + } + break; + case "tag": + this.tag = va(c, this.tag); + break; + case "doc": + this.store = ta(c, this.store); + } + } +}; +la(U.prototype); +function Va(a, c, b) { + a = ("object" === typeof a ? "" + a.query : a).toLowerCase(); + this.cache || (this.cache = new X()); + let e = this.cache.get(a); + if (!e) { + e = this.search(a, c, b); + if (e.then) { + const d = this; + e.then(function(f) { + d.cache.set(a, f); + return f; + }); + } + this.cache.set(a, e); + } + return e; +} +function X(a) { + this.limit = a && !0 !== a ? a : 1000; + this.cache = new Map(); + this.h = ""; +} +X.prototype.set = function(a, c) { + this.cache.set(this.h = a, c); + this.cache.size > this.limit && this.cache.delete(this.cache.keys().next().value); +}; +X.prototype.get = function(a) { + const c = this.cache.get(a); + c && this.h !== a && (this.cache.delete(a), this.cache.set(this.h = a, c)); + return c; +}; +X.prototype.remove = function(a) { + for (const c of this.cache) { + const b = c[0]; + c[1].includes(a) && this.cache.delete(b); + } +}; +X.prototype.clear = function() { + this.cache.clear(); + this.h = ""; +}; +const Wa = {normalize:!1, numeric:!1, split:/\s+/}; +const Xa = {normalize:!0}; +const Ya = {normalize:!0, dedupe:!0}; +const Za = new Map([["b", "p"], ["v", "f"], ["w", "f"], ["z", "s"], ["x", "s"], ["d", "t"], ["n", "m"], ["c", "k"], ["g", "k"], ["j", "k"], ["q", "k"], ["i", "e"], ["y", "e"], ["u", "o"]]); +const $a = new Map([["ae", "a"], ["oe", "o"], ["sh", "s"], ["kh", "k"], ["th", "t"], ["ph", "f"], ["pf", "f"]]), ab = [/([^aeo])h(.)/g, "$1$2", /([aeo])h([^aeo]|$)/g, "$1$2", /(.)\1+/g, "$1"]; +const bb = {a:"", e:"", i:"", o:"", u:"", y:"", b:1, f:1, p:1, v:1, c:2, g:2, j:2, k:2, q:2, s:2, x:2, z:2, "\u00df":2, d:3, t:3, l:4, m:5, n:5, r:6}; +var cb = {X:Wa, W:Xa, Y:Ya, LatinBalance:{normalize:!0, dedupe:!0, mapper:Za}, LatinAdvanced:{normalize:!0, dedupe:!0, mapper:Za, matcher:$a, replacer:ab}, LatinExtra:{normalize:!0, dedupe:!0, mapper:Za, replacer:ab.concat([/(?!^)[aeo]/g, ""]), matcher:$a}, LatinSoundex:{normalize:!0, dedupe:!1, include:{letter:!0}, finalize:function(a) { + for (let b = 0; b < a.length; b++) { + var c = a[b]; + let e = c.charAt(0), d = bb[e]; + for (let f = 1, g; f < c.length && (g = c.charAt(f), "h" === g || "w" === g || !(g = bb[g]) || g === d || (e += g, d = g, 4 !== e.length)); f++) { + } + a[b] = e; + } +}}, LatinExact:Wa, LatinDefault:Xa, LatinSimple:Ya}; +const db = {memory:{resolution:1}, performance:{resolution:3, fastupdate:!0, context:{depth:1, resolution:1}}, match:{tokenize:"forward"}, score:{resolution:9, context:{depth:2, resolution:3}}}; +N.prototype.add = function(a, c, b, e) { + if (c && (a || 0 === a)) { + if (!e && !b && this.reg.has(a)) { + return this.update(a, c); + } + c = this.encoder.encode(c); + if (e = c.length) { + const l = B(), n = B(), m = this.depth, q = this.resolution; + for (let p = 0; p < e; p++) { + let r = c[this.rtl ? e - 1 - p : p]; + var d = r.length; + if (d && (m || !n[r])) { + var f = this.score ? this.score(c, r, p, null, 0) : eb(q, e, p), g = ""; + switch(this.tokenize) { + case "full": + if (2 < d) { + for (let u = 0, v; u < d; u++) { + for (f = d; f > u; f--) { + g = r.substring(u, f); + v = this.rtl ? d - 1 - u : u; + var k = this.score ? this.score(c, r, p, g, v) : eb(q, e, p, d, v); + fb(this, n, g, k, a, b); + } + } + break; + } + case "bidirectional": + case "reverse": + if (1 < d) { + for (k = d - 1; 0 < k; k--) { + g = r[this.rtl ? d - 1 - k : k] + g; + var h = this.score ? this.score(c, r, p, g, k) : eb(q, e, p, d, k); + fb(this, n, g, h, a, b); + } + g = ""; + } + case "forward": + if (1 < d) { + for (k = 0; k < d; k++) { + g += r[this.rtl ? d - 1 - k : k], fb(this, n, g, f, a, b); + } + break; + } + default: + if (fb(this, n, r, f, a, b), m && 1 < e && p < e - 1) { + for (d = B(), g = this.U, f = r, k = Math.min(m + 1, this.rtl ? p + 1 : e - p), d[f] = 1, h = 1; h < k; h++) { + if ((r = c[this.rtl ? e - 1 - p - h : p + h]) && !d[r]) { + d[r] = 1; + const u = this.score ? this.score(c, f, p, r, h - 1) : eb(g + (e / 2 > g ? 0 : 1), e, p, k - 1, h - 1), v = this.bidirectional && r > f; + fb(this, l, v ? f : r, u, a, b, v ? r : f); + } + } + } + } + } + } + this.fastupdate || this.reg.add(a); + } else { + c = ""; + } + } + this.db && (c || this.commit_task.push({del:a}), this.T && gb(this)); + return this; +}; +function fb(a, c, b, e, d, f, g) { + let k = g ? a.ctx : a.map, h; + if (!c[b] || g && !(h = c[b])[g]) { + if (g ? (c = h || (c[b] = B()), c[g] = 1, (h = k.get(g)) ? k = h : k.set(g, k = new Map())) : c[b] = 1, (h = k.get(b)) ? k = h : k.set(b, k = h = []), k = k[e] || (k[e] = []), !f || !k.includes(d)) { + if (k.length === 2 ** 31 - 1) { + c = new R(k); + if (a.fastupdate) { + for (let l of a.reg.values()) { + l.includes(k) && (l[l.indexOf(k)] = c); + } + } + h[e] = k = c; + } + k.push(d); + a.fastupdate && ((e = a.reg.get(d)) ? e.push(k) : a.reg.set(d, [k])); + } + } +} +function eb(a, c, b, e, d) { + return b && 1 < a ? c + (e || 0) <= a ? b + (d || 0) : (a - 1) / (c + (e || 0)) * (b + (d || 0)) + 1 | 0 : 0; +} +;N.prototype.search = function(a, c, b) { + b || (!c && I(a) ? (b = a, a = "") : I(c) && (b = c, c = 0)); + let e = [], d, f, g, k = 0, h, l, n, m, q; + b ? (a = b.query || a, c = b.limit || c, k = b.offset || 0, f = b.context, g = b.suggest, q = (h = !1 !== b.resolve) && b.enrich, n = b.boost, m = b.resolution, l = this.db && b.tag) : h = this.resolve; + let p = this.encoder.encode(a); + d = p.length; + c = c || (h ? 100 : 0); + if (1 === d) { + return hb.call(this, p[0], "", c, k, h, q, l); + } + f = this.depth && !1 !== f; + if (2 === d && f && !g) { + return hb.call(this, p[0], p[1], c, k, h, q, l); + } + let r = B(), u = 0, v; + 1 < d && f && (v = p[0], u = 1); + m || 0 === m || (m = v ? this.U : this.resolution); + if (this.db) { + if (this.db.search && (a = this.db.search(this, p, c, k, g, h, q, l), !1 !== a)) { + return a; + } + const w = this; + return async function() { + for (let y, F; u < d; u++) { + if ((F = p[u]) && !r[F]) { + r[F] = 1; + y = await ib(w, F, v, 0, 0, !1, !1); + if (y = jb(y, e, g, m)) { + e = y; + break; + } + v && (g && y && e.length || (v = F)); + } + g && v && u === d - 1 && !e.length && (m = w.resolution, v = "", u = -1, r = B()); + } + return kb(e, m, c, k, g, n, h); + }(); + } + for (let w, y; u < d; u++) { + if ((y = p[u]) && !r[y]) { + r[y] = 1; + w = ib(this, y, v, 0, 0, !1, !1); + if (w = jb(w, e, g, m)) { + e = w; + break; + } + v && (g && w && e.length || (v = y)); + } + g && v && u === d - 1 && !e.length && (m = this.resolution, v = "", u = -1, r = B()); + } + return kb(e, m, c, k, g, n, h); +}; +function kb(a, c, b, e, d, f, g) { + let k = a.length, h = a; + if (1 < k) { + h = Fa(a, c, b, e, d, f, g); + } else if (1 === k) { + return g ? Ia.call(null, a[0], b, e) : new W(a[0]); + } + return g ? h : new W(h); +} +function hb(a, c, b, e, d, f, g) { + a = ib(this, a, c, b, e, d, f, g); + return this.db ? a.then(function(k) { + return d ? k || [] : new W(k); + }) : a && a.length ? d ? Ia.call(this, a, b, e) : new W(a) : d ? [] : new W(); +} +function jb(a, c, b, e) { + let d = []; + if (a && a.length) { + if (a.length <= e) { + c.push(a); + return; + } + for (let f = 0, g; f < e; f++) { + if (g = a[f]) { + d[f] = g; + } + } + if (d.length) { + c.push(d); + return; + } + } + if (!b) { + return d; + } +} +function ib(a, c, b, e, d, f, g, k) { + let h; + b && (h = a.bidirectional && c > b) && (h = b, b = c, c = h); + if (a.db) { + return a.db.get(c, b, e, d, f, g, k); + } + a = b ? (a = a.ctx.get(b)) && a.get(c) : a.map.get(c); + return a; +} +;N.prototype.remove = function(a, c) { + const b = this.reg.size && (this.fastupdate ? this.reg.get(a) : this.reg.has(a)); + if (b) { + if (this.fastupdate) { + for (let e = 0, d; e < b.length; e++) { + if (d = b[e]) { + if (2 > d.length) { + d.pop(); + } else { + const f = d.indexOf(a); + f === b.length - 1 ? d.pop() : d.splice(f, 1); + } + } + } + } else { + lb(this.map, a), this.depth && lb(this.ctx, a); + } + c || this.reg.delete(a); + } + this.db && (this.commit_task.push({del:a}), this.T && gb(this)); + this.cache && this.cache.remove(a); + return this; +}; +function lb(a, c) { + let b = 0; + if (a.constructor === Array) { + for (let e = 0, d, f; e < a.length; e++) { + if ((d = a[e]) && d.length) { + if (f = d.indexOf(c), 0 <= f) { + 1 < d.length ? (d.splice(f, 1), b++) : delete a[e]; + break; + } else { + b++; + } + } + } + } else { + for (let e of a.entries()) { + const d = e[0], f = lb(e[1], c); + f ? b += f : a.delete(d); + } + } + return b; +} +;function N(a, c) { + if (!this || this.constructor !== N) { + return new N(a); + } + if (a) { + var b = E(a) ? a : a.preset; + b && (db[b] || console.warn("Preset not found: " + b), a = Object.assign({}, db[b], a)); + } else { + a = {}; + } + b = a.context; + const e = !0 === b ? {depth:1} : b || {}, d = E(a.encoder) ? cb[a.encoder] : a.encode || a.encoder || Xa; + this.encoder = d.encode ? d : "object" === typeof d ? new ja(d) : {encode:d}; + this.resolution = a.resolution || 9; + this.tokenize = b = (b = a.tokenize) && "default" !== b && "exact" !== b && b || "strict"; + this.depth = "strict" === b && e.depth || 0; + this.bidirectional = !1 !== e.bidirectional; + this.fastupdate = !!a.fastupdate; + this.score = a.score || null; + e && "strict" !== this.tokenize && console.warn('Context-Search could not applied, because it is just supported when using the tokenizer "strict".'); + (b = a.keystore || 0) && (this.keystore = b); + this.map = b ? new S(b) : new Map(); + this.ctx = b ? new S(b) : new Map(); + this.reg = c || (this.fastupdate ? b ? new S(b) : new Map() : b ? new T(b) : new Set()); + this.U = e.resolution || 3; + this.rtl = d.rtl || a.rtl || !1; + this.cache = (b = a.cache || null) && new X(b); + this.resolve = !1 !== a.resolve; + if (b = a.db) { + this.db = this.mount(b); + } + this.T = !1 !== a.commit; + this.commit_task = []; + this.commit_timer = null; + this.priority = a.priority || 4; +} +t = N.prototype; +t.mount = function(a) { + this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); + return a.mount(this); +}; +t.commit = function(a, c) { + this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); + return this.db.commit(this, a, c); +}; +t.destroy = function() { + this.commit_timer && (clearTimeout(this.commit_timer), this.commit_timer = null); + return this.db.destroy(); +}; +function gb(a) { + a.commit_timer || (a.commit_timer = setTimeout(function() { + a.commit_timer = null; + a.db.commit(a, void 0, void 0); + }, 1)); +} +t.clear = function() { + this.map.clear(); + this.ctx.clear(); + this.reg.clear(); + this.cache && this.cache.clear(); + this.db && (this.commit_timer && clearTimeout(this.commit_timer), this.commit_timer = null, this.commit_task = [{clear:!0}]); + return this; +}; +t.append = function(a, c) { + return this.add(a, c, !0); +}; +t.contain = function(a) { + return this.db ? this.db.has(a) : this.reg.has(a); +}; +t.update = function(a, c) { + const b = this, e = this.remove(a); + return e && e.then ? e.then(() => b.add(a, c)) : this.add(a, c); +}; +function mb(a) { + let c = 0; + if (a.constructor === Array) { + for (let b = 0, e; b < a.length; b++) { + (e = a[b]) && (c += e.length); + } + } else { + for (const b of a.entries()) { + const e = b[0], d = mb(b[1]); + d ? c += d : a.delete(e); + } + } + return c; +} +t.cleanup = function() { + if (!this.fastupdate) { + return console.info('Cleanup the index isn\'t required when not using "fastupdate".'), this; + } + mb(this.map); + this.depth && mb(this.ctx); + return this; +}; +t.searchCache = Va; +t.export = function(a, c, b = 0, e = 0) { + let d, f; + switch(e) { + case 0: + d = "reg"; + f = wa(this.reg); + break; + case 1: + d = "cfg"; + f = null; + break; + case 2: + d = "map"; + f = sa(this.map, this.reg.size); + break; + case 3: + d = "ctx"; + f = ua(this.ctx, this.reg.size); + break; + default: + return; + } + return ya.call(this, a, c, d, f, b, e); +}; +t.import = function(a, c) { + if (c) { + switch("string" === typeof c && (c = JSON.parse(c)), a = a.split("."), "json" === a[a.length - 1] && a.pop(), 3 === a.length && a.shift(), a = 1 < a.length ? a[1] : a[0], a) { + case "reg": + this.fastupdate = !1; + this.reg = xa(c, this.reg); + break; + case "map": + this.map = ta(c, this.map); + break; + case "ctx": + this.ctx = va(c, this.ctx); + } + } +}; +t.serialize = function(a = !0) { + let c = "", b = "", e = ""; + if (this.reg.size) { + let f; + for (var d of this.reg.keys()) { + f || (f = typeof d), c += (c ? "," : "") + ("string" === f ? '"' + d + '"' : d); + } + c = "index.reg=new Set([" + c + "]);"; + b = za(this.map, f); + b = "index.map=new Map([" + b + "]);"; + for (const g of this.ctx.entries()) { + d = g[0]; + let k = za(g[1], f); + k = "new Map([" + k + "])"; + k = '["' + d + '",' + k + "]"; + e += (e ? "," : "") + k; + } + e = "index.ctx=new Map([" + e + "]);"; + } + return a ? "function inject(index){" + c + b + e + "}" : c + b + e; +}; +la(N.prototype); +const nb = "undefined" !== typeof window && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB), ob = ["map", "ctx", "tag", "reg", "cfg"], Y = B(); +function pb(a, c = {}) { + if (!this) { + return new pb(a, c); + } + "object" === typeof a && (c = a, a = a.name); + a || console.info("Default storage space was used, because a name was not passed."); + this.id = "flexsearch" + (a ? ":" + a.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""); + this.field = c.field ? c.field.toLowerCase().replace(/[^a-z0-9_\-]/g, "") : ""; + this.type = c.type; + this.fastupdate = this.support_tag_search = !1; + this.db = null; + this.h = {}; +} +t = pb.prototype; +t.mount = function(a) { + if (!a.encoder) { + return a.mount(this); + } + a.db = this; + return this.open(); +}; +t.open = function() { + if (this.db) { + return this.db; + } + let a = this; + navigator.storage && navigator.storage.persist(); + Y[a.id] || (Y[a.id] = []); + Y[a.id].push(a.field); + const c = nb.open(a.id, 1); + c.onupgradeneeded = function() { + const b = a.db = this.result; + for (let e = 0, d; e < ob.length; e++) { + d = ob[e]; + for (let f = 0, g; f < Y[a.id].length; f++) { + g = Y[a.id][f], b.objectStoreNames.contains(d + ("reg" !== d ? g ? ":" + g : "" : "")) || b.createObjectStore(d + ("reg" !== d ? g ? ":" + g : "" : "")); + } + } + }; + return a.db = Z(c, function(b) { + a.db = b; + a.db.onversionchange = function() { + a.close(); + }; + }); +}; +t.close = function() { + this.db && this.db.close(); + this.db = null; +}; +t.destroy = function() { + const a = nb.deleteDatabase(this.id); + return Z(a); +}; +t.clear = function() { + const a = []; + for (let b = 0, e; b < ob.length; b++) { + e = ob[b]; + for (let d = 0, f; d < Y[this.id].length; d++) { + f = Y[this.id][d], a.push(e + ("reg" !== e ? f ? ":" + f : "" : "")); + } + } + const c = this.db.transaction(a, "readwrite"); + for (let b = 0; b < a.length; b++) { + c.objectStore(a[b]).clear(); + } + return Z(c); +}; +t.get = function(a, c, b = 0, e = 0, d = !0, f = !1) { + a = this.db.transaction((c ? "ctx" : "map") + (this.field ? ":" + this.field : ""), "readonly").objectStore((c ? "ctx" : "map") + (this.field ? ":" + this.field : "")).get(c ? c + ":" + a : a); + const g = this; + return Z(a).then(function(k) { + let h = []; + if (!k || !k.length) { + return h; + } + if (d) { + if (!b && !e && 1 === k.length) { + return k[0]; + } + for (let l = 0, n; l < k.length; l++) { + if ((n = k[l]) && n.length) { + if (e >= n.length) { + e -= n.length; + continue; + } + const m = b ? e + Math.min(n.length - e, b) : n.length; + for (let q = e; q < m; q++) { + h.push(n[q]); + } + e = 0; + if (h.length === b) { + break; + } + } + } + return f ? g.enrich(h) : h; + } + return k; + }); +}; +t.tag = function(a, c = 0, b = 0, e = !1) { + a = this.db.transaction("tag" + (this.field ? ":" + this.field : ""), "readonly").objectStore("tag" + (this.field ? ":" + this.field : "")).get(a); + const d = this; + return Z(a).then(function(f) { + if (!f || !f.length || b >= f.length) { + return []; + } + if (!c && !b) { + return f; + } + f = f.slice(b, b + c); + return e ? d.enrich(f) : f; + }); +}; +t.enrich = function(a) { + "object" !== typeof a && (a = [a]); + const c = this.db.transaction("reg", "readonly").objectStore("reg"), b = []; + for (let e = 0; e < a.length; e++) { + b[e] = Z(c.get(a[e])); + } + return Promise.all(b).then(function(e) { + for (let d = 0; d < e.length; d++) { + e[d] = {id:a[d], doc:e[d] ? JSON.parse(e[d]) : null}; + } + return e; + }); +}; +t.has = function(a) { + a = this.db.transaction("reg", "readonly").objectStore("reg").getKey(a); + return Z(a).then(function(c) { + return !!c; + }); +}; +t.search = null; +t.info = function() { +}; +t.transaction = function(a, c, b) { + a += "reg" !== a ? this.field ? ":" + this.field : "" : ""; + let e = this.h[a + ":" + c]; + if (e) { + return b.call(this, e); + } + let d = this.db.transaction(a, c); + this.h[a + ":" + c] = e = d.objectStore(a); + const f = b.call(this, e); + this.h[a + ":" + c] = null; + return Z(d).finally(function() { + d = e = null; + return f; + }); +}; +t.commit = async function(a, c, b) { + if (c) { + await this.clear(), a.commit_task = []; + } else { + let e = a.commit_task; + a.commit_task = []; + for (let d = 0, f; d < e.length; d++) { + if (f = e[d], f.clear) { + await this.clear(); + c = !0; + break; + } else { + e[d] = f.del; + } + } + c || (b || (e = e.concat(aa(a.reg))), e.length && await this.remove(e)); + } + a.reg.size && (await this.transaction("map", "readwrite", function(e) { + for (const d of a.map) { + const f = d[0], g = d[1]; + g.length && (c ? e.put(g, f) : e.get(f).onsuccess = function() { + let k = this.result; + var h; + if (k && k.length) { + const l = Math.max(k.length, g.length); + for (let n = 0, m, q; n < l; n++) { + if ((q = g[n]) && q.length) { + if ((m = k[n]) && m.length) { + for (h = 0; h < q.length; h++) { + m.push(q[h]); + } + } else { + k[n] = q; + } + h = 1; + } + } + } else { + k = g, h = 1; + } + h && e.put(k, f); + }); + } + }), await this.transaction("ctx", "readwrite", function(e) { + for (const d of a.ctx) { + const f = d[0], g = d[1]; + for (const k of g) { + const h = k[0], l = k[1]; + l.length && (c ? e.put(l, f + ":" + h) : e.get(f + ":" + h).onsuccess = function() { + let n = this.result; + var m; + if (n && n.length) { + const q = Math.max(n.length, l.length); + for (let p = 0, r, u; p < q; p++) { + if ((u = l[p]) && u.length) { + if ((r = n[p]) && r.length) { + for (m = 0; m < u.length; m++) { + r.push(u[m]); + } + } else { + n[p] = u; + } + m = 1; + } + } + } else { + n = l, m = 1; + } + m && e.put(n, f + ":" + h); + }); + } + } + }), a.store ? await this.transaction("reg", "readwrite", function(e) { + for (const d of a.store) { + const f = d[0], g = d[1]; + e.put("object" === typeof g ? JSON.stringify(g) : 1, f); + } + }) : a.bypass || await this.transaction("reg", "readwrite", function(e) { + for (const d of a.reg.keys()) { + e.put(1, d); + } + }), a.tag && await this.transaction("tag", "readwrite", function(e) { + for (const d of a.tag) { + const f = d[0], g = d[1]; + g.length && (e.get(f).onsuccess = function() { + let k = this.result; + k = k && k.length ? k.concat(g) : g; + e.put(k, f); + }); + } + }), a.map.clear(), a.ctx.clear(), a.tag && a.tag.clear(), a.store && a.store.clear(), a.document || a.reg.clear()); +}; +function qb(a, c, b) { + const e = a.value; + let d, f = 0; + for (let g = 0, k; g < e.length; g++) { + if (k = b ? e : e[g]) { + for (let h = 0, l, n; h < c.length; h++) { + if (n = c[h], l = k.indexOf(n), 0 <= l) { + if (d = 1, 1 < k.length) { + k.splice(l, 1); + } else { + e[g] = []; + break; + } + } + } + f += k.length; + } + if (b) { + break; + } + } + f ? d && a.update(e) : a.delete(); + a.continue(); +} +t.remove = function(a) { + "object" !== typeof a && (a = [a]); + return Promise.all([this.transaction("map", "readwrite", function(c) { + c.openCursor().onsuccess = function() { + const b = this.result; + b && qb(b, a); + }; + }), this.transaction("ctx", "readwrite", function(c) { + c.openCursor().onsuccess = function() { + const b = this.result; + b && qb(b, a); + }; + }), this.transaction("tag", "readwrite", function(c) { + c.openCursor().onsuccess = function() { + const b = this.result; + b && qb(b, a, !0); + }; + }), this.transaction("reg", "readwrite", function(c) { + for (let b = 0; b < a.length; b++) { + c.delete(a[b]); + } + })]); +}; +function Z(a, c) { + return new Promise((b, e) => { + a.onsuccess = a.oncomplete = function() { + c && c(this.result); + c = null; + b(this.result); + }; + a.onerror = a.onblocked = e; + a = null; + }); +} +;const rb = {Index:N, Charset:cb, Encoder:ja, Document:U, Worker:P, Resolver:W, IndexedDB:pb, Language:{}}, sb = "undefined" !== typeof self ? self : "undefined" !== typeof global ? global : self; +let ub; +(ub = sb.define) && ub.amd ? ub([], function() { + return rb; +}) : "object" === typeof sb.exports ? sb.exports = rb : sb.FlexSearch = rb; +}(this||self)); diff --git a/public/js/main-head.js b/public/js/main-head.js new file mode 100644 index 0000000..8fb4191 --- /dev/null +++ b/public/js/main-head.js @@ -0,0 +1,22 @@ +// The section must not be in the banner.js (body) file because it can create a quick flash. + +if (localStorage.getItem('banner-closed')) { + document.documentElement.style.setProperty("--hextra-banner-height", "0px"); + document.documentElement.classList.add("hextra-banner-hidden"); +} + +; +// The section must not be in the theme.js (body) file because it can create a quick flash (switch between light and dark). + +function setTheme(theme) { + document.documentElement.classList.remove("light", "dark"); + + if (theme !== "light" && theme !== "dark") { + theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; + } + + document.documentElement.classList.add(theme); + document.documentElement.style.colorScheme = theme; +} + +setTheme("color-theme" in localStorage ? localStorage.getItem("color-theme") : 'system') diff --git a/public/js/main-head.min.94da6f95b126542222dab3a6e07ffefe1aaf63ec3ca555e827b58b80e7d3f0b7.js b/public/js/main-head.min.94da6f95b126542222dab3a6e07ffefe1aaf63ec3ca555e827b58b80e7d3f0b7.js new file mode 100644 index 0000000..ae77987 --- /dev/null +++ b/public/js/main-head.min.94da6f95b126542222dab3a6e07ffefe1aaf63ec3ca555e827b58b80e7d3f0b7.js @@ -0,0 +1 @@ +localStorage.getItem("banner-closed")&&(document.documentElement.style.setProperty("--hextra-banner-height","0px"),document.documentElement.classList.add("hextra-banner-hidden"));function setTheme(e){document.documentElement.classList.remove("light","dark"),e!=="light"&&e!=="dark"&&(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),document.documentElement.classList.add(e),document.documentElement.style.colorScheme=e}setTheme("color-theme"in localStorage?localStorage.getItem("color-theme"):"system") \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..4e51018 --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,1082 @@ +// Back to top button + +document.addEventListener("DOMContentLoaded", function () { + const backToTop = document.querySelector("#backToTop"); + if (backToTop) { + backToTop.addEventListener("click", scrollUp); + document.addEventListener("scroll", (e) => { + if (window.scrollY > 300) { + backToTop.classList.remove("hx:opacity-0"); + backToTop.removeAttribute("tabindex"); + } else { + backToTop.classList.add("hx:opacity-0"); + backToTop.setAttribute("tabindex", "-1"); + } + }); + } +}); + +function scrollUp() { + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + window.scroll({ + top: 0, + left: 0, + behavior: prefersReducedMotion ? 'auto' : 'smooth', + }); +} + +; +// +; +// Copy button for code blocks + +document.addEventListener('DOMContentLoaded', function () { + const getCopyIcon = () => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.innerHTML = ` + + `; + svg.setAttribute('fill', 'none'); + svg.setAttribute('viewBox', '0 0 24 24'); + svg.setAttribute('stroke', 'currentColor'); + svg.setAttribute('stroke-width', '2'); + return svg; + } + + const getSuccessIcon = () => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svg.innerHTML = ` + + `; + svg.setAttribute('fill', 'none'); + svg.setAttribute('viewBox', '0 0 24 24'); + svg.setAttribute('stroke', 'currentColor'); + svg.setAttribute('stroke-width', '2'); + return svg; + } + + // Make scrollable code blocks focusable for keyboard users. + const updateScrollableCodeBlocks = () => { + document.querySelectorAll('.hextra-code-block pre, .highlight pre').forEach(function (pre) { + if (pre.scrollWidth > pre.clientWidth) { + pre.setAttribute('tabindex', '0'); + } else { + pre.removeAttribute('tabindex'); + } + }); + }; + + updateScrollableCodeBlocks(); + + let resizeRaf; + window.addEventListener('resize', () => { + if (resizeRaf) { + cancelAnimationFrame(resizeRaf); + } + resizeRaf = requestAnimationFrame(updateScrollableCodeBlocks); + }); + + document.querySelectorAll('.hextra-code-copy-btn').forEach(function (button) { + // Add copy and success icons + button.querySelector('.hextra-copy-icon')?.appendChild(getCopyIcon()); + button.querySelector('.hextra-success-icon')?.appendChild(getSuccessIcon()); + + // Add click event listener for copy button + button.addEventListener('click', function (e) { + e.preventDefault(); + // Get the code target + const target = button.parentElement.previousElementSibling; + let codeElement; + if (target.tagName === 'CODE') { + codeElement = target; + } else { + // Select the last code element in case line numbers are present + const codeElements = target.querySelectorAll('code'); + codeElement = codeElements[codeElements.length - 1]; + } + if (codeElement) { + let code = codeElement.innerText; + // Replace double newlines with single newlines in the innerText + // as each line inside has trailing newline '\n' + if ("lang" in codeElement.dataset) { + code = code.replace(/\n\n/g, '\n'); + } + navigator.clipboard.writeText(code).then(function () { + button.classList.add('copied'); + var originalLabel = button.getAttribute('aria-label'); + var copiedLabel = button.dataset.copiedLabel || 'Copied!'; + button.setAttribute('aria-label', copiedLabel); + setTimeout(function () { + button.classList.remove('copied'); + button.setAttribute('aria-label', originalLabel); + }, 1000); + }).catch(function (err) { + console.error('Failed to copy text: ', err); + }); + } else { + console.error('Target element not found'); + } + }); + }); +}); + +; +// +(function () { + const faviconEl = document.getElementById("favicon-svg"); + const faviconDarkExists = "false" === "true"; + + if (faviconEl && faviconDarkExists) { + const lightFavicon = '/favicon.svg'; + const darkFavicon = '/favicon-dark.svg'; + + const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); + + function updateFavicon(e) { + faviconEl.href = e.matches ? darkFavicon : lightFavicon; + } + + // Set favicon on load + updateFavicon(darkModeQuery); + + // Listen for system preference changes + darkModeQuery.addEventListener("change", updateFavicon); + } +})(); + +; +// Script for filetree shortcode collapsing/expanding folders used in the theme +// ====================================================================== +document.addEventListener("DOMContentLoaded", function () { + const folders = document.querySelectorAll(".hextra-filetree-folder"); + folders.forEach(function (folder) { + folder.addEventListener("click", function () { + Array.from(folder.children).forEach(function (el) { + el.dataset.state = el.dataset.state === "open" ? "closed" : "open"; + }); + var newState = folder.nextElementSibling.dataset.state === "open" ? "closed" : "open"; + folder.nextElementSibling.dataset.state = newState; + folder.setAttribute('aria-expanded', newState === 'open' ? 'true' : 'false'); + }); + }); +}); + +; +(function () { + const languageSwitchers = document.querySelectorAll('.hextra-language-switcher'); + const closeSwitcher = (switcher, focusSwitcher = false) => { + switcher.dataset.state = 'closed'; + switcher.setAttribute('aria-expanded', 'false'); + const optionsElement = switcher.nextElementSibling; + optionsElement.classList.add('hx:hidden'); + if (focusSwitcher) { + switcher.focus(); + } + }; + + const openSwitcher = (switcher, focusTarget = "none") => { + switcher.dataset.state = 'open'; + switcher.setAttribute('aria-expanded', 'true'); + const optionsElement = switcher.nextElementSibling; + if (optionsElement.classList.contains('hx:hidden')) { + toggleMenu(switcher); + } else { + resizeMenu(switcher); + } + + if (focusTarget !== "none") { + const items = Array.from(optionsElement.querySelectorAll('[role="menuitem"]')); + if (items.length > 0) { + const target = focusTarget === "last" ? items[items.length - 1] : items[0]; + target.focus(); + } + } + }; + + languageSwitchers.forEach((switcher) => { + switcher.addEventListener('click', (e) => { + e.preventDefault(); + + if (switcher.dataset.state === 'open') { + closeSwitcher(switcher); + } else { + openSwitcher(switcher); + } + }); + + switcher.addEventListener('keydown', (e) => { + if (e.key === 'ArrowDown') { + e.preventDefault(); + openSwitcher(switcher, 'first'); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + openSwitcher(switcher, 'last'); + } + }); + }); + + document.querySelectorAll('.hextra-language-options[role=menu]').forEach((menu) => { + menu.addEventListener('keydown', (e) => { + const items = Array.from(menu.querySelectorAll('[role="menuitem"]')); + if (items.length === 0) return; + + const currentIndex = items.indexOf(document.activeElement); + let newIndex; + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + newIndex = (currentIndex + 1) % items.length; + items[newIndex].focus(); + break; + case 'ArrowUp': + e.preventDefault(); + newIndex = (currentIndex - 1 + items.length) % items.length; + items[newIndex].focus(); + break; + case 'Home': + e.preventDefault(); + items[0].focus(); + break; + case 'End': + e.preventDefault(); + items[items.length - 1].focus(); + break; + case 'Escape': { + e.preventDefault(); + const switcher = menu.previousElementSibling; + if (switcher) { + closeSwitcher(switcher, true); + } + break; + } + } + }); + }); + + window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu)); + + // Dismiss language switcher when clicking outside. + document.addEventListener('click', (e) => { + if (!e.target.closest('.hextra-language-switcher') && !e.target.closest('.hextra-language-options')) { + languageSwitchers.forEach((switcher) => { + closeSwitcher(switcher); + }); + } + }); +})(); + +; +// Hamburger menu for mobile navigation + +document.addEventListener('DOMContentLoaded', function () { + const menu = document.querySelector('.hextra-hamburger-menu'); + const sidebarContainer = document.querySelector('.hextra-sidebar-container'); + const mobileQuery = window.matchMedia('(max-width: 767px)'); + + function isMenuOpen() { + return menu.querySelector('svg').classList.contains('open'); + } + + // On mobile, the sidebar is off-screen so hide it from assistive tech + function syncAriaHidden() { + if (mobileQuery.matches) { + sidebarContainer.setAttribute('aria-hidden', isMenuOpen() ? 'false' : 'true'); + } else { + sidebarContainer.removeAttribute('aria-hidden'); + } + } + + // Set initial state + syncAriaHidden(); + mobileQuery.addEventListener('change', syncAriaHidden); + + function toggleMenu(options = {}) { + const { focusOnOpen = true } = options; + + // Toggle the hamburger menu + menu.querySelector('svg').classList.toggle('open'); + + // When the menu is open, we want to show the navigation sidebar + sidebarContainer.classList.toggle('hx:max-md:[transform:translate3d(0,-100%,0)]'); + sidebarContainer.classList.toggle('hx:max-md:[transform:translate3d(0,0,0)]'); + + // When the menu is open, we want to prevent the body from scrolling + document.body.classList.toggle('hx:overflow-hidden'); + document.body.classList.toggle('hx:md:overflow-auto'); + + // Sync aria-expanded and aria-hidden + const isOpen = isMenuOpen(); + menu.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + syncAriaHidden(); + + // Move focus into sidebar when opening, restore when closing + if (isOpen) { + if (focusOnOpen) { + const firstFocusable = sidebarContainer.querySelector('a, button, input, [tabindex="0"]'); + if (firstFocusable) firstFocusable.focus(); + } + } else { + menu.focus(); + } + } + + menu.addEventListener('click', (e) => { + e.preventDefault(); + // Pointer-initiated clicks on mobile should not force focus into the search input, + // which opens the software keyboard immediately. + toggleMenu({ focusOnOpen: e.detail === 0 }); + }); + + // Close menu on Escape key (mobile only) + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && mobileQuery.matches && isMenuOpen()) { + toggleMenu(); + } + }); + + // Select all anchor tags in the sidebar container + const sidebarLinks = sidebarContainer.querySelectorAll('a'); + + // Add click event listener to each anchor tag + sidebarLinks.forEach(link => { + link.addEventListener('click', (e) => { + // Check if the href attribute contains a hash symbol (links to a heading) + if (link.getAttribute('href') && link.getAttribute('href').startsWith('#')) { + // Only dismiss overlay on mobile view + if (window.innerWidth < 768) { + toggleMenu(); + } + } + }); + }); +}); + +; +(function () { + const hiddenClass = "hx:hidden"; + const dropdownToggles = document.querySelectorAll(".hextra-nav-menu-toggle"); + const closeDropdown = (toggle, focusToggle = false) => { + toggle.dataset.state = "closed"; + toggle.setAttribute("aria-expanded", "false"); + const menuItemsElement = toggle.nextElementSibling; + menuItemsElement.classList.add(hiddenClass); + if (focusToggle) { + toggle.focus(); + } + }; + + const openDropdown = (toggle, focusTarget = "none") => { + // Close all other dropdowns first. + dropdownToggles.forEach((otherToggle) => { + if (otherToggle !== toggle) { + closeDropdown(otherToggle); + } + }); + + toggle.dataset.state = "open"; + toggle.setAttribute("aria-expanded", "true"); + const menuItemsElement = toggle.nextElementSibling; + + // Position dropdown centered with toggle. + menuItemsElement.style.position = "absolute"; + menuItemsElement.style.top = "100%"; + menuItemsElement.style.left = "50%"; + menuItemsElement.style.transform = "translateX(-50%)"; + menuItemsElement.style.zIndex = "1000"; + menuItemsElement.classList.remove(hiddenClass); + + if (focusTarget !== "none") { + const items = Array.from(menuItemsElement.querySelectorAll('[role="menuitem"]')); + if (items.length > 0) { + const target = focusTarget === "last" ? items[items.length - 1] : items[0]; + target.focus(); + } + } + }; + + dropdownToggles.forEach((toggle) => { + toggle.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + + // Toggle current dropdown. + const isOpen = toggle.dataset.state === "open"; + if (isOpen) { + closeDropdown(toggle); + } else { + openDropdown(toggle); + } + }); + + toggle.addEventListener("keydown", (e) => { + if (e.key === "ArrowDown") { + e.preventDefault(); + openDropdown(toggle, "first"); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + openDropdown(toggle, "last"); + } + }); + }); + + document.querySelectorAll(".hextra-nav-menu-items[role=menu]").forEach((menu) => { + menu.addEventListener("keydown", (e) => { + const items = Array.from(menu.querySelectorAll('[role="menuitem"]')); + if (items.length === 0) return; + + const currentIndex = items.indexOf(document.activeElement); + let newIndex; + + switch (e.key) { + case "ArrowDown": + e.preventDefault(); + newIndex = (currentIndex + 1) % items.length; + items[newIndex].focus(); + break; + case "ArrowUp": + e.preventDefault(); + newIndex = (currentIndex - 1 + items.length) % items.length; + items[newIndex].focus(); + break; + case "Home": + e.preventDefault(); + items[0].focus(); + break; + case "End": + e.preventDefault(); + items[items.length - 1].focus(); + break; + case "Escape": { + e.preventDefault(); + const toggle = menu.previousElementSibling; + if (toggle) { + closeDropdown(toggle, true); + } + break; + } + } + }); + }); + + // Dismiss dropdown when clicking outside. + document.addEventListener("click", (e) => { + if (!e.target.closest(".hextra-nav-menu-toggle") && !e.target.closest(".hextra-nav-menu-items")) { + dropdownToggles.forEach((toggle) => { + closeDropdown(toggle); + }); + } + }); + + // Close dropdowns on escape key. + document.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + dropdownToggles.forEach((toggle) => { + if (toggle.dataset.state === "open") { + closeDropdown(toggle, true); + } + }); + } + }); +})(); + +; +document.addEventListener('DOMContentLoaded', () => { + // Pre-fetch markdown content for all copy buttons to avoid Safari NotAllowedError + // Safari requires clipboard writes to happen synchronously within user gesture + const copyButtons = document.querySelectorAll('.hextra-page-context-menu-copy'); + const contentCache = new Map(); + + // Pre-fetch content for each button on page load + copyButtons.forEach(button => { + const url = button.dataset.url; + if (url) { + fetch(url) + .then(response => { + if (response.ok) return response.text(); + throw new Error('Failed to fetch'); + }) + .then(markdown => contentCache.set(url, markdown)) + .catch(error => console.error('Failed to pre-fetch markdown:', error)); + } + }); + + // Initialize copy buttons with synchronous clipboard access + copyButtons.forEach(button => { + button.addEventListener('click', () => { + const url = button.dataset.url; + const markdown = contentCache.get(url); + + if (markdown) { + // Synchronous clipboard write initiation - works in Safari + navigator.clipboard.writeText(markdown) + .then(() => { + button.classList.add('copied'); + setTimeout(() => button.classList.remove('copied'), 1000); + }) + .catch(error => console.error('Failed to copy markdown:', error)); + } else { + // Fallback: fetch and copy (may fail in Safari if content not pre-fetched) + fetch(url) + .then(response => { + if (!response.ok) throw new Error('Failed to fetch'); + return response.text(); + }) + .then(text => { + contentCache.set(url, text); + return navigator.clipboard.writeText(text); + }) + .then(() => { + button.classList.add('copied'); + setTimeout(() => button.classList.remove('copied'), 1000); + }) + .catch(error => console.error('Failed to copy markdown:', error)); + } + }); + }); + + // Initialize dropdown toggles + const dropdownToggles = document.querySelectorAll('.hextra-page-context-menu-toggle'); + dropdownToggles.forEach(toggle => { + const container = toggle.closest('.hextra-page-context-menu'); + const menu = container.querySelector('.hextra-page-context-menu-dropdown'); + const chevron = toggle.querySelector('[data-chevron]'); + + toggle.addEventListener('click', (e) => { + e.stopPropagation(); + const isOpen = toggle.dataset.state === 'open'; + + // Close all other dropdowns first + dropdownToggles.forEach(t => { + if (t !== toggle) { + t.dataset.state = 'closed'; + t.setAttribute('aria-expanded', 'false'); + const otherContainer = t.closest('.hextra-page-context-menu'); + const otherMenu = otherContainer.querySelector('.hextra-page-context-menu-dropdown'); + const otherChevron = t.querySelector('[data-chevron]'); + otherMenu.classList.add('hx:hidden'); + if (otherChevron) { + otherChevron.style.transform = ''; + } + } + }); + + // Toggle current + toggle.dataset.state = isOpen ? 'closed' : 'open'; + toggle.setAttribute('aria-expanded', isOpen ? 'false' : 'true'); + menu.classList.toggle('hx:hidden', isOpen); + + // Rotate chevron icon + if (chevron) { + chevron.style.transform = isOpen ? '' : 'rotate(180deg)'; + } + }); + }); + + // Close dropdown when clicking outside + document.addEventListener('click', (e) => { + // Check if click is outside any dropdown container + const isOutside = !e.target.closest('.hextra-page-context-menu'); + if (isOutside) { + dropdownToggles.forEach(toggle => { + toggle.dataset.state = 'closed'; + toggle.setAttribute('aria-expanded', 'false'); + const container = toggle.closest('.hextra-page-context-menu'); + const menu = container.querySelector('.hextra-page-context-menu-dropdown'); + const chevron = toggle.querySelector('[data-chevron]'); + menu.classList.add('hx:hidden'); + if (chevron) { + chevron.style.transform = ''; + } + }); + } + }); + + // Close dropdown on Escape key and return focus to toggle + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') { + dropdownToggles.forEach(toggle => { + if (toggle.dataset.state === 'open') { + const container = toggle.closest('.hextra-page-context-menu'); + closeDropdown(container); + toggle.focus(); + } + }); + } + }); + + // Helper to close dropdown + const closeDropdown = (container) => { + if (!container) return; + + const toggle = container.querySelector('.hextra-page-context-menu-toggle'); + const menu = container.querySelector('.hextra-page-context-menu-dropdown'); + + if (!toggle || !menu) return; + + const chevron = toggle.querySelector('[data-chevron]'); + toggle.dataset.state = 'closed'; + toggle.setAttribute('aria-expanded', 'false'); + menu.classList.add('hx:hidden'); + if (chevron) { + chevron.style.transform = ''; + } + }; + + // Handle dropdown menu copy action + document.querySelectorAll('.hextra-page-context-menu-dropdown button[data-action="copy"]').forEach(btn => { + btn.addEventListener('click', async (e) => { + e.stopPropagation(); + const container = btn.closest('.hextra-page-context-menu'); + if (!container) return; + + const copyBtn = container.querySelector('.hextra-page-context-menu-copy'); + if (!copyBtn) return; + + closeDropdown(container); + copyBtn.click(); + }); + }); + + // Handle dropdown menu view action + document.querySelectorAll('.hextra-page-context-menu-dropdown button[data-action="view"]').forEach(btn => { + btn.addEventListener('click', (e) => { + e.stopPropagation(); + const container = btn.closest('.hextra-page-context-menu'); + if (!container) return; + + const url = btn.dataset.url; + if (!url) return; + + closeDropdown(container); + window.open(url, '_blank', 'noopener,noreferrer'); + }); + }); +}); + +; +document.addEventListener("DOMContentLoaded", function () { + scrollToActiveItem(); + enableCollapsibles(); +}); + +function enableCollapsibles() { + const buttons = document.querySelectorAll(".hextra-sidebar-collapsible-button"); + buttons.forEach(function (button) { + button.addEventListener("click", function (e) { + e.preventDefault(); + const list = button.closest('li'); + if (list) { + list.classList.toggle("open"); + button.setAttribute('aria-expanded', list.classList.contains('open') ? 'true' : 'false'); + } + }); + }); +} + +function scrollToActiveItem() { + const sidebarScrollbar = document.querySelector("aside.hextra-sidebar-container > .hextra-scrollbar"); + const activeItems = document.querySelectorAll(".hextra-sidebar-active-item"); + const visibleActiveItem = Array.from(activeItems).find(function (activeItem) { + return activeItem.getBoundingClientRect().height > 0; + }); + + if (!visibleActiveItem) { + return; + } + + const yOffset = visibleActiveItem.clientHeight; + const yDistance = visibleActiveItem.getBoundingClientRect().top - sidebarScrollbar.getBoundingClientRect().top; + sidebarScrollbar.scrollTo({ + behavior: "instant", + top: yDistance - yOffset + }); +} + +; +function computeMenuTranslation(switcher, optionsElement) { + // Calculate the position of a language options element. + const switcherRect = switcher.getBoundingClientRect(); + + // Must be called before optionsElement.clientWidth. + optionsElement.style.minWidth = `${Math.max(switcherRect.width, 50)}px`; + + const isOnTop = switcher.dataset.location === 'top'; + const isOnBottom = switcher.dataset.location === 'bottom'; + const isOnBottomRight = switcher.dataset.location === 'bottom-right'; + const isRTL = document.documentElement.dir === 'rtl' + + // Stuck on the left side of the switcher. + let x = switcherRect.left; + + if (isOnTop && !isRTL || isOnBottom && isRTL || isOnBottomRight && !isRTL) { + // Stuck on the right side of the switcher. + x = switcherRect.right - optionsElement.clientWidth; + } + + // Stuck on the top of the switcher. + let y = switcherRect.top - window.innerHeight - 10; + + if (isOnTop) { + // Stuck on the bottom of the switcher. + y = switcherRect.top - window.innerHeight + optionsElement.clientHeight + switcher.clientHeight + 4; + } + + return { x: x, y: y }; +} + +function toggleMenu(switcher) { + const optionsElement = switcher.nextElementSibling; + + optionsElement.classList.toggle('hx:hidden'); + + // Calculate the position of a language options element. + const translate = computeMenuTranslation(switcher, optionsElement); + + optionsElement.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`; +} + +function resizeMenu(switcher) { + const optionsElement = switcher.nextElementSibling; + + if (optionsElement.classList.contains('hx:hidden')) return; + + // Calculate the position of a language options element. + const translate = computeMenuTranslation(switcher, optionsElement); + + optionsElement.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`; +} + +; +(function () { + function updateGroup(container, index) { + const tabs = Array.from(container.querySelectorAll('.hextra-tabs-toggle')); + tabs.forEach((tab, i) => { + tab.dataset.state = i === index ? 'selected' : ''; + if (i === index) { + tab.setAttribute('aria-selected', 'true'); + tab.tabIndex = 0; + } else { + tab.setAttribute('aria-selected', 'false'); + tab.tabIndex = -1; + } + }); + const panelsContainer = container.parentElement.nextElementSibling; + if (!panelsContainer) return; + Array.from(panelsContainer.children).forEach((panel, i) => { + panel.dataset.state = i === index ? 'selected' : ''; + panel.setAttribute('aria-hidden', i === index ? 'false' : 'true'); + if (i === index) { + panel.tabIndex = 0; + } else { + panel.removeAttribute('tabindex'); + } + }); + } + + const syncGroups = document.querySelectorAll('[data-tab-group]'); + + syncGroups.forEach((group) => { + const key = encodeURIComponent(group.dataset.tabGroup); + const saved = localStorage.getItem('hextra-tab-' + key); + if (saved !== null) { + updateGroup(group, parseInt(saved, 10)); + } + }); + + document.querySelectorAll('.hextra-tabs-toggle').forEach((button) => { + button.addEventListener('click', function (e) { + const targetButton = e.currentTarget; + const container = targetButton.parentElement; + const index = Array.from(container.querySelectorAll('.hextra-tabs-toggle')).indexOf( + targetButton + ); + + if (container.dataset.tabGroup) { + // Sync behavior: update all tab groups with the same name + const tabGroupValue = container.dataset.tabGroup; + const key = encodeURIComponent(tabGroupValue); + document + .querySelectorAll('[data-tab-group="' + tabGroupValue + '"]') + .forEach((grp) => updateGroup(grp, index)); + localStorage.setItem('hextra-tab-' + key, index.toString()); + } else { + // Non-sync behavior: update only this specific tab group + updateGroup(container, index); + } + }); + + // Keyboard navigation for tabs + button.addEventListener('keydown', function (e) { + const container = button.parentElement; + const tabs = Array.from(container.querySelectorAll('.hextra-tabs-toggle')); + const currentIndex = tabs.indexOf(button); + let newIndex; + + switch (e.key) { + case 'ArrowRight': + case 'ArrowDown': + e.preventDefault(); + newIndex = (currentIndex + 1) % tabs.length; + break; + case 'ArrowLeft': + case 'ArrowUp': + e.preventDefault(); + newIndex = (currentIndex - 1 + tabs.length) % tabs.length; + break; + case 'Home': + e.preventDefault(); + newIndex = 0; + break; + case 'End': + e.preventDefault(); + newIndex = tabs.length - 1; + break; + default: + return; + } + + if (container.dataset.tabGroup) { + const tabGroupValue = container.dataset.tabGroup; + const key = encodeURIComponent(tabGroupValue); + document + .querySelectorAll('[data-tab-group="' + tabGroupValue + '"]') + .forEach((grp) => updateGroup(grp, newIndex)); + localStorage.setItem('hextra-tab-' + key, newIndex.toString()); + } else { + updateGroup(container, newIndex); + } + tabs[newIndex].focus(); + }); + }); +})(); + +; +document.addEventListener("DOMContentLoaded", function () { + // Hugo task lists render bare checkboxes; provide an accessible name. + document.querySelectorAll("main#content li > input[type='checkbox']").forEach(function (checkbox) { + if (checkbox.hasAttribute("aria-label") || checkbox.hasAttribute("aria-labelledby")) { + return; + } + var listItem = checkbox.closest("li"); + if (!listItem) return; + + var labelText = listItem.textContent.replace(/\s+/g, " ").trim(); + if (labelText) { + checkbox.setAttribute("aria-label", labelText); + } + }); +}); + +; +// Light / Dark theme toggle +(function () { + const defaultTheme = 'system' + const themes = ["light", "dark"]; + + const themeToggleButtons = document.querySelectorAll(".hextra-theme-toggle"); + const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options button[role=menuitemradio]"); + + function applyTheme(theme) { + theme = themes.includes(theme) ? theme : "system"; + + themeToggleButtons.forEach((btn) => btn.parentElement.dataset.theme = theme ); + themeToggleOptions.forEach((option) => { + option.setAttribute('aria-checked', option.dataset.item === theme ? 'true' : 'false'); + }); + + localStorage.setItem("color-theme", theme); + } + + function switchTheme(theme) { + setTheme(theme); + applyTheme(theme); + } + + const colorTheme = "color-theme" in localStorage ? localStorage.getItem("color-theme") : defaultTheme; + switchTheme(colorTheme); + + // Add click event handler to the menu items. + themeToggleOptions.forEach((option) => { + option.addEventListener("click", function (e) { + e.preventDefault(); + + switchTheme(option.dataset.item); + }) + }) + + // Add click event handler to the buttons + themeToggleButtons.forEach((toggler) => { + toggler.addEventListener("click", function (e) { + e.preventDefault(); + + toggler.dataset.state = toggler.dataset.state === 'open' ? 'closed' : 'open'; + toggleMenu(toggler); + const isOpen = toggler.dataset.state === 'open'; + toggler.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); + + // Focus first menuitem when opening + if (isOpen) { + const firstItem = toggler.nextElementSibling.querySelector('button[role=menuitemradio]'); + if (firstItem) firstItem.focus(); + } + }); + }); + + window.addEventListener("resize", () => themeToggleButtons.forEach(resizeMenu)) + + // Dismiss the menu when clicking outside + document.addEventListener('click', (e) => { + if (e.target.closest('.hextra-theme-toggle') === null) { + themeToggleButtons.forEach((toggler) => { + toggler.dataset.state = 'closed'; + toggler.setAttribute('aria-expanded', 'false'); + toggler.nextElementSibling.classList.add('hx:hidden'); + }); + } + }); + + // Keyboard navigation for the theme menu + document.querySelectorAll('.hextra-theme-toggle-options[role=menu]').forEach(function (menu) { + menu.addEventListener('keydown', function (e) { + const items = Array.from(menu.querySelectorAll('button[role=menuitemradio]')); + const currentIndex = items.indexOf(document.activeElement); + let newIndex; + + switch (e.key) { + case 'ArrowDown': + e.preventDefault(); + newIndex = (currentIndex + 1) % items.length; + items[newIndex].focus(); + break; + case 'ArrowUp': + e.preventDefault(); + newIndex = (currentIndex - 1 + items.length) % items.length; + items[newIndex].focus(); + break; + case 'Home': + e.preventDefault(); + items[0].focus(); + break; + case 'End': + e.preventDefault(); + items[items.length - 1].focus(); + break; + case 'Escape': + e.preventDefault(); + var toggler = menu.previousElementSibling; + toggler.dataset.state = 'closed'; + toggler.setAttribute('aria-expanded', 'false'); + menu.classList.add('hx:hidden'); + toggler.focus(); + break; + } + }); + }); + + // Listen for system theme changes + window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => { + if (localStorage.getItem("color-theme") === "system") { + setTheme("system"); + } + }); +})(); + +; +/** + * TOC Scroll - Highlights active TOC links based on visible headings + * + * Uses Intersection Observer to track heading visibility and applies + * 'hextra-toc-active' class to corresponding TOC links. Selects the + * topmost heading when multiple are visible. + * + * Requires: .hextra-toc element, matching heading IDs, toc.css styles + */ +document.addEventListener("DOMContentLoaded", function () { + const toc = document.querySelector(".hextra-toc"); + if (!toc) return; + + const tocLinks = toc.querySelectorAll('a[href^="#"]'); + if (tocLinks.length === 0) return; + + const headingIds = Array.from(tocLinks).map((link) => link.getAttribute("href").substring(1)); + + const headings = headingIds.map((id) => document.getElementById(decodeURIComponent(id))).filter(Boolean); + if (headings.length === 0) return; + + let currentActiveLink = null; + let isHashNavigation = false; + + // Create intersection observer + const observer = new IntersectionObserver( + (entries) => { + // Skip observer updates during hash navigation + if (isHashNavigation) return; + + const visibleHeadings = entries.filter((entry) => entry.isIntersecting).map((entry) => entry.target); + + if (visibleHeadings.length === 0) return; + + // Find the heading closest to the top of the viewport + const topMostHeading = visibleHeadings.reduce((closest, heading) => { + const headingTop = heading.getBoundingClientRect().top; + const closestTop = closest.getBoundingClientRect().top; + return Math.abs(headingTop) < Math.abs(closestTop) ? heading : closest; + }); + + // Encode the id and make it lowercase to match the TOC link + const targetId = encodeURIComponent(topMostHeading.id).toLowerCase(); + const targetLink = toc.querySelector(`a[href="#${targetId}"]`); + + if (targetLink && targetLink !== currentActiveLink) { + // Remove active class from previous link + if (currentActiveLink) { + currentActiveLink.classList.remove("hextra-toc-active"); + currentActiveLink.removeAttribute("aria-current"); + } + + // Add active class to current link + targetLink.classList.add("hextra-toc-active"); + targetLink.setAttribute("aria-current", "location"); + currentActiveLink = targetLink; + } + }, + { + rootMargin: "-20px 0px -60% 0px", // Adjust sensitivity + threshold: [0, 0.1, 0.5, 1], + } + ); + + // Observe all headings + headings.forEach((heading) => observer.observe(heading)); + + // Handle direct navigation to page with hash + function handleHashNavigation() { + const hash = window.location.hash; // already url encoded + if (hash) { + const targetLink = toc.querySelector(`a[href="${hash}"]`); + if (targetLink) { + // Disable observer temporarily during hash navigation + isHashNavigation = true; + + if (currentActiveLink) { + currentActiveLink.classList.remove("hextra-toc-active"); + currentActiveLink.removeAttribute("aria-current"); + } + targetLink.classList.add("hextra-toc-active"); + targetLink.setAttribute("aria-current", "location"); + currentActiveLink = targetLink; + + // Re-enable observer after scroll settles + setTimeout(() => { isHashNavigation = false; }, 500); + return; + } + } + } + + // Handle hash changes navigation + window.addEventListener("hashchange", handleHashNavigation); + + // Handle initial load + setTimeout(handleHashNavigation, 100); +}); diff --git a/public/js/main.min.d9fbefbbac8caeb66025dbb14d569c30defac0effcba45486a78209fba9360af.js b/public/js/main.min.d9fbefbbac8caeb66025dbb14d569c30defac0effcba45486a78209fba9360af.js new file mode 100644 index 0000000..5232ade --- /dev/null +++ b/public/js/main.min.d9fbefbbac8caeb66025dbb14d569c30defac0effcba45486a78209fba9360af.js @@ -0,0 +1,6 @@ +document.addEventListener("DOMContentLoaded",function(){const e=document.querySelector("#backToTop");e&&(e.addEventListener("click",scrollUp),document.addEventListener("scroll",t=>{window.scrollY>300?(e.classList.remove("hx:opacity-0"),e.removeAttribute("tabindex")):(e.classList.add("hx:opacity-0"),e.setAttribute("tabindex","-1"))}))});function scrollUp(){const e=window.matchMedia("(prefers-reduced-motion: reduce)").matches;window.scroll({top:0,left:0,behavior:e?"auto":"smooth"})}document.addEventListener("DOMContentLoaded",function(){const n=()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","svg");return e.innerHTML=` + + `,e.setAttribute("fill","none"),e.setAttribute("viewBox","0 0 24 24"),e.setAttribute("stroke","currentColor"),e.setAttribute("stroke-width","2"),e},s=()=>{const e=document.createElementNS("http://www.w3.org/2000/svg","svg");return e.innerHTML=` + + `,e.setAttribute("fill","none"),e.setAttribute("viewBox","0 0 24 24"),e.setAttribute("stroke","currentColor"),e.setAttribute("stroke-width","2"),e},t=()=>{document.querySelectorAll(".hextra-code-block pre, .highlight pre").forEach(function(e){e.scrollWidth>e.clientWidth?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")})};t();let e;window.addEventListener("resize",()=>{e&&cancelAnimationFrame(e),e=requestAnimationFrame(t)}),document.querySelectorAll(".hextra-code-copy-btn").forEach(function(e){e.querySelector(".hextra-copy-icon")?.appendChild(n()),e.querySelector(".hextra-success-icon")?.appendChild(s()),e.addEventListener("click",function(t){t.preventDefault();const s=e.parentElement.previousElementSibling;let n;if(s.tagName==="CODE")n=s;else{const e=s.querySelectorAll("code");n=e[e.length-1]}if(n){let t=n.innerText;"lang"in n.dataset&&(t=t.replace(/\n\n/g,` +`)),navigator.clipboard.writeText(t).then(function(){e.classList.add("copied");var t=e.getAttribute("aria-label"),n=e.dataset.copiedLabel||"Copied!";e.setAttribute("aria-label",n),setTimeout(function(){e.classList.remove("copied"),e.setAttribute("aria-label",t)},1e3)}).catch(function(e){console.error("Failed to copy text: ",e)})}else console.error("Target element not found")})})}),function(){const e=document.getElementById("favicon-svg"),n="false"==="true";if(e&&n){const s="/favicon.svg",o="/favicon-dark.svg",n=window.matchMedia("(prefers-color-scheme: dark)");function t(t){e.href=t.matches?o:s}t(n),n.addEventListener("change",t)}}(),document.addEventListener("DOMContentLoaded",function(){const e=document.querySelectorAll(".hextra-filetree-folder");e.forEach(function(e){e.addEventListener("click",function(){Array.from(e.children).forEach(function(e){e.dataset.state=e.dataset.state==="open"?"closed":"open"});var t=e.nextElementSibling.dataset.state==="open"?"closed":"open";e.nextElementSibling.dataset.state=t,e.setAttribute("aria-expanded",t==="open"?"true":"false")})})}),function(){const e=document.querySelectorAll(".hextra-language-switcher"),t=(e,t=!1)=>{e.dataset.state="closed",e.setAttribute("aria-expanded","false");const n=e.nextElementSibling;n.classList.add("hx:hidden"),t&&e.focus()},n=(e,t="none")=>{e.dataset.state="open",e.setAttribute("aria-expanded","true");const n=e.nextElementSibling;if(n.classList.contains("hx:hidden")?toggleMenu(e):resizeMenu(e),t!=="none"){const e=Array.from(n.querySelectorAll('[role="menuitem"]'));if(e.length>0){const n=t==="last"?e[e.length-1]:e[0];n.focus()}}};e.forEach(e=>{e.addEventListener("click",s=>{s.preventDefault(),e.dataset.state==="open"?t(e):n(e)}),e.addEventListener("keydown",t=>{t.key==="ArrowDown"?(t.preventDefault(),n(e,"first")):t.key==="ArrowUp"&&(t.preventDefault(),n(e,"last"))})}),document.querySelectorAll(".hextra-language-options[role=menu]").forEach(e=>{e.addEventListener("keydown",n=>{const s=Array.from(e.querySelectorAll('[role="menuitem"]'));if(s.length===0)return;const i=s.indexOf(document.activeElement);let o;switch(n.key){case"ArrowDown":n.preventDefault(),o=(i+1)%s.length,s[o].focus();break;case"ArrowUp":n.preventDefault(),o=(i-1+s.length)%s.length,s[o].focus();break;case"Home":n.preventDefault(),s[0].focus();break;case"End":n.preventDefault(),s[s.length-1].focus();break;case"Escape":{n.preventDefault();const s=e.previousElementSibling;s&&t(s,!0);break}}})}),window.addEventListener("resize",()=>e.forEach(resizeMenu)),document.addEventListener("click",n=>{!n.target.closest(".hextra-language-switcher")&&!n.target.closest(".hextra-language-options")&&e.forEach(e=>{t(e)})})}(),document.addEventListener("DOMContentLoaded",function(){const t=document.querySelector(".hextra-hamburger-menu"),e=document.querySelector(".hextra-sidebar-container"),n=window.matchMedia("(max-width: 767px)");function s(){return t.querySelector("svg").classList.contains("open")}function o(){n.matches?e.setAttribute("aria-hidden",s()?"false":"true"):e.removeAttribute("aria-hidden")}o(),n.addEventListener("change",o);function i(n={}){const{focusOnOpen:a=!0}=n;t.querySelector("svg").classList.toggle("open"),e.classList.toggle("hx:max-md:[transform:translate3d(0,-100%,0)]"),e.classList.toggle("hx:max-md:[transform:translate3d(0,0,0)]"),document.body.classList.toggle("hx:overflow-hidden"),document.body.classList.toggle("hx:md:overflow-auto");const i=s();if(t.setAttribute("aria-expanded",i?"true":"false"),o(),i){if(a){const t=e.querySelector('a, button, input, [tabindex="0"]');t&&t.focus()}}else t.focus()}t.addEventListener("click",e=>{e.preventDefault(),i({focusOnOpen:e.detail===0})}),document.addEventListener("keydown",e=>{e.key==="Escape"&&n.matches&&s()&&i()});const a=e.querySelectorAll("a");a.forEach(e=>{e.addEventListener("click",t=>{e.getAttribute("href")&&e.getAttribute("href").startsWith("#")&&window.innerWidth<768&&i()})})}),function(){const s="hx:hidden",t=document.querySelectorAll(".hextra-nav-menu-toggle"),e=(e,t=!1)=>{e.dataset.state="closed",e.setAttribute("aria-expanded","false");const n=e.nextElementSibling;n.classList.add(s),t&&e.focus()},n=(n,o="none")=>{t.forEach(t=>{t!==n&&e(t)}),n.dataset.state="open",n.setAttribute("aria-expanded","true");const i=n.nextElementSibling;if(i.style.position="absolute",i.style.top="100%",i.style.left="50%",i.style.transform="translateX(-50%)",i.style.zIndex="1000",i.classList.remove(s),o!=="none"){const e=Array.from(i.querySelectorAll('[role="menuitem"]'));if(e.length>0){const t=o==="last"?e[e.length-1]:e[0];t.focus()}}};t.forEach(t=>{t.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation();const o=t.dataset.state==="open";o?e(t):n(t)}),t.addEventListener("keydown",e=>{e.key==="ArrowDown"?(e.preventDefault(),n(t,"first")):e.key==="ArrowUp"&&(e.preventDefault(),n(t,"last"))})}),document.querySelectorAll(".hextra-nav-menu-items[role=menu]").forEach(t=>{t.addEventListener("keydown",n=>{const s=Array.from(t.querySelectorAll('[role="menuitem"]'));if(s.length===0)return;const i=s.indexOf(document.activeElement);let o;switch(n.key){case"ArrowDown":n.preventDefault(),o=(i+1)%s.length,s[o].focus();break;case"ArrowUp":n.preventDefault(),o=(i-1+s.length)%s.length,s[o].focus();break;case"Home":n.preventDefault(),s[0].focus();break;case"End":n.preventDefault(),s[s.length-1].focus();break;case"Escape":{n.preventDefault();const s=t.previousElementSibling;s&&e(s,!0);break}}})}),document.addEventListener("click",n=>{!n.target.closest(".hextra-nav-menu-toggle")&&!n.target.closest(".hextra-nav-menu-items")&&t.forEach(t=>{e(t)})}),document.addEventListener("keydown",n=>{n.key==="Escape"&&t.forEach(t=>{t.dataset.state==="open"&&e(t,!0)})})}(),document.addEventListener("DOMContentLoaded",()=>{const s=document.querySelectorAll(".hextra-page-context-menu-copy"),t=new Map;s.forEach(e=>{const n=e.dataset.url;n&&fetch(n).then(e=>{if(e.ok)return e.text();throw new Error("Failed to fetch")}).then(e=>t.set(n,e)).catch(e=>console.error("Failed to pre-fetch markdown:",e))}),s.forEach(e=>{e.addEventListener("click",()=>{const n=e.dataset.url,s=t.get(n);s?navigator.clipboard.writeText(s).then(()=>{e.classList.add("copied"),setTimeout(()=>e.classList.remove("copied"),1e3)}).catch(e=>console.error("Failed to copy markdown:",e)):fetch(n).then(e=>{if(!e.ok)throw new Error("Failed to fetch");return e.text()}).then(e=>(t.set(n,e),navigator.clipboard.writeText(e))).then(()=>{e.classList.add("copied"),setTimeout(()=>e.classList.remove("copied"),1e3)}).catch(e=>console.error("Failed to copy markdown:",e))})});const e=document.querySelectorAll(".hextra-page-context-menu-toggle");e.forEach(t=>{const s=t.closest(".hextra-page-context-menu"),o=s.querySelector(".hextra-page-context-menu-dropdown"),n=t.querySelector("[data-chevron]");t.addEventListener("click",s=>{s.stopPropagation();const i=t.dataset.state==="open";e.forEach(e=>{if(e!==t){e.dataset.state="closed",e.setAttribute("aria-expanded","false");const n=e.closest(".hextra-page-context-menu"),s=n.querySelector(".hextra-page-context-menu-dropdown"),t=e.querySelector("[data-chevron]");s.classList.add("hx:hidden"),t&&(t.style.transform="")}}),t.dataset.state=i?"closed":"open",t.setAttribute("aria-expanded",i?"false":"true"),o.classList.toggle("hx:hidden",i),n&&(n.style.transform=i?"":"rotate(180deg)")})}),document.addEventListener("click",t=>{const n=!t.target.closest(".hextra-page-context-menu");n&&e.forEach(e=>{e.dataset.state="closed",e.setAttribute("aria-expanded","false");const n=e.closest(".hextra-page-context-menu"),s=n.querySelector(".hextra-page-context-menu-dropdown"),t=e.querySelector("[data-chevron]");s.classList.add("hx:hidden"),t&&(t.style.transform="")})}),document.addEventListener("keydown",t=>{t.key==="Escape"&&e.forEach(e=>{if(e.dataset.state==="open"){const t=e.closest(".hextra-page-context-menu");n(t),e.focus()}})});const n=e=>{if(!e)return;const t=e.querySelector(".hextra-page-context-menu-toggle"),n=e.querySelector(".hextra-page-context-menu-dropdown");if(!t||!n)return;const s=t.querySelector("[data-chevron]");t.dataset.state="closed",t.setAttribute("aria-expanded","false"),n.classList.add("hx:hidden"),s&&(s.style.transform="")};document.querySelectorAll('.hextra-page-context-menu-dropdown button[data-action="copy"]').forEach(e=>{e.addEventListener("click",async t=>{t.stopPropagation();const s=e.closest(".hextra-page-context-menu");if(!s)return;const o=s.querySelector(".hextra-page-context-menu-copy");if(!o)return;n(s),o.click()})}),document.querySelectorAll('.hextra-page-context-menu-dropdown button[data-action="view"]').forEach(e=>{e.addEventListener("click",t=>{t.stopPropagation();const s=e.closest(".hextra-page-context-menu");if(!s)return;const o=e.dataset.url;if(!o)return;n(s),window.open(o,"_blank","noopener,noreferrer")})})}),document.addEventListener("DOMContentLoaded",function(){scrollToActiveItem(),enableCollapsibles()});function enableCollapsibles(){const e=document.querySelectorAll(".hextra-sidebar-collapsible-button");e.forEach(function(e){e.addEventListener("click",function(t){t.preventDefault();const n=e.closest("li");n&&(n.classList.toggle("open"),e.setAttribute("aria-expanded",n.classList.contains("open")?"true":"false"))})})}function scrollToActiveItem(){const t=document.querySelector("aside.hextra-sidebar-container > .hextra-scrollbar"),n=document.querySelectorAll(".hextra-sidebar-active-item"),e=Array.from(n).find(function(e){return e.getBoundingClientRect().height>0});if(!e)return;const s=e.clientHeight,o=e.getBoundingClientRect().top-t.getBoundingClientRect().top;t.scrollTo({behavior:"instant",top:o-s})}function computeMenuTranslation(e,t){const n=e.getBoundingClientRect();t.style.minWidth=`${Math.max(n.width,50)}px`;const o=e.dataset.location==="top",r=e.dataset.location==="bottom",c=e.dataset.location==="bottom-right",s=document.documentElement.dir==="rtl";let i=n.left;(o&&!s||r&&s||c&&!s)&&(i=n.right-t.clientWidth);let a=n.top-window.innerHeight-10;return o&&(a=n.top-window.innerHeight+t.clientHeight+e.clientHeight+4),{x:i,y:a}}function toggleMenu(e){const t=e.nextElementSibling;t.classList.toggle("hx:hidden");const n=computeMenuTranslation(e,t);t.style.transform=`translate3d(${n.x}px, ${n.y}px, 0)`}function resizeMenu(e){const t=e.nextElementSibling;if(t.classList.contains("hx:hidden"))return;const n=computeMenuTranslation(e,t);t.style.transform=`translate3d(${n.x}px, ${n.y}px, 0)`}(function(){function e(e,t){const s=Array.from(e.querySelectorAll(".hextra-tabs-toggle"));s.forEach((e,n)=>{e.dataset.state=n===t?"selected":"",n===t?(e.setAttribute("aria-selected","true"),e.tabIndex=0):(e.setAttribute("aria-selected","false"),e.tabIndex=-1)});const n=e.parentElement.nextElementSibling;if(!n)return;Array.from(n.children).forEach((e,n)=>{e.dataset.state=n===t?"selected":"",e.setAttribute("aria-hidden",n===t?"false":"true"),n===t?e.tabIndex=0:e.removeAttribute("tabindex")})}const t=document.querySelectorAll("[data-tab-group]");t.forEach(t=>{const s=encodeURIComponent(t.dataset.tabGroup),n=localStorage.getItem("hextra-tab-"+s);n!==null&&e(t,parseInt(n,10))}),document.querySelectorAll(".hextra-tabs-toggle").forEach(t=>{t.addEventListener("click",function(t){const o=t.currentTarget,n=o.parentElement,s=Array.from(n.querySelectorAll(".hextra-tabs-toggle")).indexOf(o);if(n.dataset.tabGroup){const t=n.dataset.tabGroup,o=encodeURIComponent(t);document.querySelectorAll('[data-tab-group="'+t+'"]').forEach(t=>e(t,s)),localStorage.setItem("hextra-tab-"+o,s.toString())}else e(n,s)}),t.addEventListener("keydown",function(n){const i=t.parentElement,o=Array.from(i.querySelectorAll(".hextra-tabs-toggle")),a=o.indexOf(t);let s;switch(n.key){case"ArrowRight":case"ArrowDown":n.preventDefault(),s=(a+1)%o.length;break;case"ArrowLeft":case"ArrowUp":n.preventDefault(),s=(a-1+o.length)%o.length;break;case"Home":n.preventDefault(),s=0;break;case"End":n.preventDefault(),s=o.length-1;break;default:return}if(i.dataset.tabGroup){const t=i.dataset.tabGroup,n=encodeURIComponent(t);document.querySelectorAll('[data-tab-group="'+t+'"]').forEach(t=>e(t,s)),localStorage.setItem("hextra-tab-"+n,s.toString())}else e(i,s);o[s].focus()})})})(),document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("main#content li > input[type='checkbox']").forEach(function(e){if(e.hasAttribute("aria-label")||e.hasAttribute("aria-labelledby"))return;var t,n=e.closest("li");if(!n)return;t=n.textContent.replace(/\s+/g," ").trim(),t&&e.setAttribute("aria-label",t)})}),function(){const s="system",o=["light","dark"],e=document.querySelectorAll(".hextra-theme-toggle"),t=document.querySelectorAll(".hextra-theme-toggle-options button[role=menuitemradio]");function i(n){n=o.includes(n)?n:"system",e.forEach(e=>e.parentElement.dataset.theme=n),t.forEach(e=>{e.setAttribute("aria-checked",e.dataset.item===n?"true":"false")}),localStorage.setItem("color-theme",n)}function n(e){setTheme(e),i(e)}const a="color-theme"in localStorage?localStorage.getItem("color-theme"):s;n(a),t.forEach(e=>{e.addEventListener("click",function(t){t.preventDefault(),n(e.dataset.item)})}),e.forEach(e=>{e.addEventListener("click",function(t){t.preventDefault(),e.dataset.state=e.dataset.state==="open"?"closed":"open",toggleMenu(e);const n=e.dataset.state==="open";if(e.setAttribute("aria-expanded",n?"true":"false"),n){const t=e.nextElementSibling.querySelector("button[role=menuitemradio]");t&&t.focus()}})}),window.addEventListener("resize",()=>e.forEach(resizeMenu)),document.addEventListener("click",t=>{t.target.closest(".hextra-theme-toggle")===null&&e.forEach(e=>{e.dataset.state="closed",e.setAttribute("aria-expanded","false"),e.nextElementSibling.classList.add("hx:hidden")})}),document.querySelectorAll(".hextra-theme-toggle-options[role=menu]").forEach(function(e){e.addEventListener("keydown",function(t){const n=Array.from(e.querySelectorAll("button[role=menuitemradio]")),i=n.indexOf(document.activeElement);let s;switch(t.key){case"ArrowDown":t.preventDefault(),s=(i+1)%n.length,n[s].focus();break;case"ArrowUp":t.preventDefault(),s=(i-1+n.length)%n.length,n[s].focus();break;case"Home":t.preventDefault(),n[0].focus();break;case"End":t.preventDefault(),n[n.length-1].focus();break;case"Escape":t.preventDefault();var o=e.previousElementSibling;o.dataset.state="closed",o.setAttribute("aria-expanded","false"),e.classList.add("hx:hidden"),o.focus();break}})}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{localStorage.getItem("color-theme")==="system"&&setTheme("system")})}(),document.addEventListener("DOMContentLoaded",function(){const t=document.querySelector(".hextra-toc");if(!t)return;const s=t.querySelectorAll('a[href^="#"]');if(s.length===0)return;const a=Array.from(s).map(e=>e.getAttribute("href").substring(1)),o=a.map(e=>document.getElementById(decodeURIComponent(e))).filter(Boolean);if(o.length===0)return;let e=null,n=!1;const r=new IntersectionObserver(s=>{if(n)return;const i=s.filter(e=>e.isIntersecting).map(e=>e.target);if(i.length===0)return;const a=i.reduce((e,t)=>{const n=t.getBoundingClientRect().top,s=e.getBoundingClientRect().top;return(n<0?-n:n)<(s<0?-s:s)?t:e}),r=encodeURIComponent(a.id).toLowerCase(),o=t.querySelector(`a[href="#${r}"]`);o&&o!==e&&(e&&(e.classList.remove("hextra-toc-active"),e.removeAttribute("aria-current")),o.classList.add("hextra-toc-active"),o.setAttribute("aria-current","location"),e=o)},{rootMargin:"-20px 0px -60% 0px",threshold:[0,.1,.5,1]});o.forEach(e=>r.observe(e));function i(){const s=window.location.hash;if(s){const o=t.querySelector(`a[href="${s}"]`);if(o){n=!0,e&&(e.classList.remove("hextra-toc-active"),e.removeAttribute("aria-current")),o.classList.add("hextra-toc-active"),o.setAttribute("aria-current","location"),e=o,setTimeout(()=>{n=!1},500);return}}}window.addEventListener("hashchange",i),setTimeout(i,100)}) \ No newline at end of file diff --git a/public/posts/210218-teamspeakwithdocker/index.html b/public/posts/210218-teamspeakwithdocker/index.html new file mode 100644 index 0000000..82e75a5 --- /dev/null +++ b/public/posts/210218-teamspeakwithdocker/index.html @@ -0,0 +1,685 @@ + + + + + + + +使用Docker安装TeamSpeak – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    使用Docker安装TeamSpeak
    +
    + + +
    +

    使用Docker安装TeamSpeak

    + +
    + +
    2021/02/18·Kushidou
    +
    + +

    TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。

    +

    Teamspeak +

    TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。

    +

    所以,选择docker安装ts就是一个很不错的选择。

    +

    Docker及其安装 +

    docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。

    +

    我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:

    +
    + +
    sudo yum install docker	#安装docker程序,会自动选择合适的
    +sudo systemctl start docker	#启动docker服务
    +sudo systemctl enable docker	#允许docker开机自启
    + +
    +
    +

    Docker安装TS +

    docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak

    +

    直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。

    +

    端口 +

    从指令可以看出,docker需要使用到9987,10011,30033三个端口,所以需要到服务器后台开启防火墙上对应端口。

    +

    管理员密钥 +

    TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。

    +

    docker ps -a查找所有容器。

    +

    docker logs -f [CONTATINER ID]查看log,找到token即可。客户端设置之后再说。

    +

    密钥只会在第一次运行时打印,注意保存。

    +

    参考:https://woolio.cn/683/

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210311-multitouchscreeninlinux/index.html b/public/posts/210311-multitouchscreeninlinux/index.html new file mode 100644 index 0000000..7deca5c --- /dev/null +++ b/public/posts/210311-multitouchscreeninlinux/index.html @@ -0,0 +1,783 @@ + + + + + + + +Linux多显示器触屏调试与暂行解决方案 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux多显示器触屏调试与暂行解决方案
    +
    + + +
    +

    Linux多显示器触屏调试与暂行解决方案

    + +
    + +
    2021/03/11·Kushidou
    +
    + +

    由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。

    +

    2021-03-22更新部分内容 +2021-04-28再次更新内容

    +

    2021-09-14 文章已有新版本,请访问《Linux处理多触屏的终极解决方案

    +

    问题来源 +

    最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为

    +

    xinput map-to-output $触屏设备id $显示屏名称

    +
    +

    2021-04-28更新

    +

    xinput map-to-output指令支持触摸屏名称作为参数,比如我通过xinput指令看到有一个触摸屏的设备名称为ILITEK XXXX,那么我映射的时候直接输入

    +
    +

    xinput map-to-output "ILITEK XXX" HDMI-1

    + +
    + +
    +

    尝试解决 +

      +
    • 首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。
    • +
    • 然后xinput list-props $id查看设备的VID,这个在后续自动化处理有用。
    • +
    • xrandr查看$显示器名称
    • +
    +

    自动化处理 +

    + +
    # 2021-04-28更新:
    +# 本部分后面的都可以不看了!
    +xinput map-to-output "DEVICE_NAME" DISPLAY
    +# 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, 
    +# 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~
    + +
    +
    +

    仅作归档!下面这些方法过于繁琐!

    +

    利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。bash this.sh VID1 VID2 ScreenID

    +
    + +
    #!/bin/bash
    +# 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。
    +# 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。
    +MYID_FIRST=$1
    +MYID_SECOND=$2
    +OUT_PUT=$3
    +
    +for I in $(xinput list --id-only)
    +do
    +#[1]
    +    CUR_FIRST=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}')
    +    CUR_SECOND=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}')
    +    if [ "$MYID_FIRST" == "$CUR_FIRST" ] && [ "$MYID_SECOND" == "$CUR_SECOND" ];
    +    then
    +        MY_INDEX=$I
    +        break;
    +    fi
    +done
    +
    +xinput map-to-output $MY_INDEX $OUT_PUT
    +
    +#./this.sh VID VID HDMI2
    +#./this.sh VID VID HDMI1
    + +
    +
    +
    + +
    #!/bin/bash
    +# 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。
    +TOUCH_ID=$(xinput | grep -iw DEVICE_NAME)
    +TOUCH_ID=$(echo ${TOUCH_ID#*id=})
    +TOUCH_ID=$(echo ${TOUCH_ID%% *})
    +xinput map-to-output $TOUCH_ID SCREEN_NAME
    + +
    +
    +

    此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id

    +

    经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在~/.bashrc中,这样只需要打开一次终端,就可以实现屏幕重新映射。

    +

    后续问题 +

      +
    1. 当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。
    2. +
    3. 当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数
    4. +
    5. 如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。
    6. +
    +

    触屏校准(如果需要) +

    网上说的利用校准来区分多触摸屏的方法是完全错误的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)

    +
      +
    • sudo apt install xinput-calibrator,需要更新源
    • +
    • sudo xinput_calibrator
    • +
    • 将校准信息保存到/usr/share/X11/xorg.conf.d/10-evdev.conf的"Driver evdev”下面一行,重启生效。
    • +
    +

    参考: +

    [1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210311-serialmodeandudevinlinux/index.html b/public/posts/210311-serialmodeandudevinlinux/index.html new file mode 100644 index 0000000..ff5e00a --- /dev/null +++ b/public/posts/210311-serialmodeandudevinlinux/index.html @@ -0,0 +1,732 @@ + + + + + + + +Linux串口和udev理解 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux串口和udev理解
    +
    + + +
    +

    Linux串口和udev理解

    + +
    + +
    2021/03/11·Kushidou
    +
    + +

    配置串口权限,顺便看一下linux的udev规则。

    +

    串口的问题 +

    在现场开发的同事反映UOS工控机没有配置串口,每次都需要sudo才能使用。(明明在家里你们都会自己配置好环境与权限,为什么到了现场反而不会了需要我提供协助呢?)

    +

    我之前没考虑过这个问题,认为串口就是得root权限操作才安全。但是既然提出了需求,就赶紧百度并测试一下吧。

    +

    赋予普通用户串口权限的两种方法 +

    在百度上,给普通用户串口权限有两种主流方法(可能2会了,Google上不去),一种是将用户放入特殊的用户组,从而获得权限。另一种是加入udev规则,在串口插入时自动更改权限。

    +

    我个人更倾向于使用第二种方案,因为公司中不同的人装系统就可能起不同的名字,之后进入生产环境可能又会起新的名字,所以采用第二种设置规则的办法,便于之后制作恢复镜像和一键部署。

    +

    xxx.rules +

    sudo vi /etc/udev/rules.d/70-tty-usb-rules,写入以下内容:

    +
    + +
    KERNEL=="ttyS*",MODE="0666"
    +KERNEL=="ttyUSB*",MODE="0666"
    + +
    +
    +

    这一段的意思是匹配所有ttyS和ttyUSB,插入时都赋予0666的权限,即所有用户都可读可写。重新加载一下设备,就可以按规则配置权限进行操作了。

    +

    udev规则 +

    udev是什么?udev是Linux下的设备文件管理方式,运行在用户侧(devfs在内核侧管理设备文件)。有了udev,/dev目录下的文件就都是真实存在的已连接的设备了。

    +

    udev的规则都保存在/etc/udev/rules.d目录下,文件名的格式为数字-名称.rules,数字表示加载顺序,一般是从小往大加载;名称按功能自定义,最后以.rules结尾。这些规则会在设备接入时生效。

    +

    规则文件按行执行,每行分为两个部分:匹配和赋值,每个参数使用逗号,分割。

    +
    +

    (这段复制自引用2,仅供参考)。 +1、udev 规则的所有操作符 +==  比较键、值,若等于,则该条件满足; +!=  比较键、值,若不等于,则该条件满足; +=   对一个键赋值; ++=  为一个表示多个条目的键赋值。 +:=  对一个键赋值,并拒绝之后所有对该键的改动,防止后面的规则文件对该键重赋值。

    +

    2、udev 规则的匹配键 +ACTION        事件 (uevent) 的行为,例如:add、remove。 +KERNEL         内核设备名称,例如:sda, cdrom。 +DEVPATH        设备的 devpath 路径。 +SUBSYSTEM       设备的子系统名称,例如:sda 的子系统为 block。 +BUS           设备在 devpath 里的总线名称,例如:usb。 +DRIVER         设备在 devpath 里的设备驱动名称,例如:ide-cdrom。 +ID            设备在 devpath 里的识别号。 +SYSFS{filename}    设备的 devpath 路径下,设备的属性文件“filename”里的内容。 +ENV{key}       环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。 +PROGRAM        调用外部命令。 +RESULT         外部命令 PROGRAM 的返回结果。

    +

    3、udev 的重要赋值键 +NAME           /dev目录下显示的文件名重命名 +SYMLINK          为 /dev下文件进行符号链接,防止覆盖系统默认产生的文件 +OWNER, GROUP, MODE  为设备设定权限。 +ENV{key}         导入一个环境变量。

    +

    4、udev 的值和可调用的替换操作符 +Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符: +$kernel, %k        设备的内核设备名称,例如:sda、cdrom。 +$number, %n        设备的内核号码,例如:sda3 的内核号码是 3。 +$devpath, %p       设备的 devpath路径。 +$id, %b          设备在 devpath里的 ID 号。 +$sysfs{file}, %s{file}   设备的 sysfs里 file 的内容。其实就是设备的属性值。 +$env{key}, %E{key}   一个环境变量的值。 +$major, %M        设备的 major 号。 +$minor %m        设备的 minor 号。 +$result, %c        PROGRAM 返回的结果。 +$parent, %P       父设备的设备文件名。 +$root, %r         udev_root的值,默认是 /dev/。 +$tempnode, %N      临时设备名。 +%%            符号 % 本身。 +$$             符号 $ 本身。

    + +
    +

    参考 +

    [1]https://www.cnblogs.com/qixianyu/p/6869744.html +[2]https://www.cnblogs.com/cslunatic/p/3171837.html

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210328-linuxchangemacip/index.html b/public/posts/210328-linuxchangemacip/index.html new file mode 100644 index 0000000..c3142c4 --- /dev/null +++ b/public/posts/210328-linuxchangemacip/index.html @@ -0,0 +1,732 @@ + + + + + + + +Linux修改mac地址 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux修改mac地址
    +
    + + +
    +

    Linux修改mac地址

    + +
    + +
    2021/03/28·Kushidou
    +
    + +

    当Linux的图形界面没有或失效的时候,该如何更改mac地址?

    +

    Linux调整网卡MAC地址(指令)

    有的时候需要使用欺骗的方法为电脑设置新的mac,比如存在上网网卡限制,或者想要隐藏自己的真实mac地址。这里列举三种修改mac的方法,适用于Debian系的Linux,其他的应该类似。

    +

    方法一:ifconfig +

    + +
    # ifconfig    查看网卡名称$NAME
    +# 需要sudo权限
    +ifconfig $NAME down
    +ifconfig $NAME hw ether $NEW_MAC
    +ifconfig $NAME up
    +# 使用ifconfig检查有没有改变
    + +
    +
    +

    方法二:macchanger +

    sudo apt install macchanger,安装过程提示是否自启,随便。

    +
    + +
    ifconfig $NAME down
    +macchanger -m $NEW_MAC $NAME
    +ifconfig $NAME up
    + +
    +
    +

    ps:macchanger可以附加多种参数,如-m指定mac地址,-s查看状态,-r随机地址

    +

    备注: +

    以上两种方法是软件层面的修改,不会对硬件造成损伤,重启之后就能恢复如初。如果是要长期有效,可以把这些命令写入脚本开机自启。除了这两个,还有另一种软修改的方法,因为我总是提示失败就遗忘了。

    +

    有些方法会提到修改/etc目录下的文件实现MAC地址的修改,但是我一个文件都没找到(UOS),这个是因系统而异的,不同的发行版可以选择性的编译这些文件,就会造成有人有有人没有,因为没有实践,所以就不写在这里了。

    +

    方法三:ethtool(慎用) +

    以上方法都失效,可以直接修改网卡的物理MAC地址,修改后无法恢复,慎用

    +
    + +
    # 首先检查mac是否支持修改
    +ethtool -i $NAME
    +# 如果  supports-eeprom-access: no,大概率不支持修改
    +
    +# 查看网卡的mac寄存器
    +ethtool -e $NAME
    +# 如果无法读取,大概率不支持修改
    +
    +# 修改mac,按字节修改
    +ethtool -E $NAME offset 0 value 0xAA
    +ethtool -E $NAME offset 1 value 0xBB
    +ethtool -E $NAME offset 2 value 0xCC
    +...共六行
    +# 如果修改无效,则驱动不支持修改
    + +
    +
    +

    如果以上方法都无法修改,则建议申请新的ip。。。 +

    文章首发于香风家的小别墅,转载请携带出处。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210420-linuxmorethan4serialport/index.html b/public/posts/210420-linuxmorethan4serialport/index.html new file mode 100644 index 0000000..111015a --- /dev/null +++ b/public/posts/210420-linuxmorethan4serialport/index.html @@ -0,0 +1,722 @@ + + + + + + + +Linux启用四个以上的串口 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux启用四个以上的串口
    +
    + + +
    +

    Linux启用四个以上的串口

    + +
    + +
    2021/04/20·Kushidou
    +
    + +

    工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。

    +

    先说结论 +

    + +
    # 1.修改开机grub文件
    +sudo vi /etc/default/grub
    +# 2.在"GRUB_CMDLINE_LINUX_DEFAULT="一行的结尾空格,添加内容
    + 8250.nr_uarts=8
    +# 3.更新开机引导并重启
    +sudo update-grub
    + +
    +
    +

    再说细节 +

    给带有多串口的工控机安装国产Linux操作系统UOS,发现/dev/ttyS只有四个,而且一个都不通,其实这是Linux 的通病,如果发行商没有提前配置,我们只能看到四个不能用的串口。但是工控机都有8-16个串口,不得不想办法让他们能够正常使用,而许多串口芯片只需要基本的驱动就能运行,也不提供专有驱动的源代码编译(一般安装专用驱动后,串口名称会改变,比如xr17v358芯片的驱动安装后名称变成ttyXR0),我们只有从linux本身下手了。

    +

    经过多番查找资料,虽然不知道为什么,但是我们应该修改/etc/default/grub文件,其中有一行是这样的:

    +
    + +
    GRUB_CMDLINE_LINUX_DEFAULT="这里面 的内容 先不管 它"
    + +
    +
    +

    我看了UOS(含Deepin)、Ubuntu和服务器的Centos7都有,在这一行的最后添加8250.nr_uart=32,其中数字<=32都行,效果如下:

    +
    + +
    GRUB_CMDLINE_LINUX_DEFAULT="这里面 的内容 先不管 它 8250.nr_uarts=32"
    + +
    +
    +

    然后更新一下系统的引导

    +
    + +
    sudo update-gurb
    +reboot
    + +
    +
    +

    重启完了你就能看到更多的串口啦!配合修改udev规则,你就可以用普通用户的权限使用串口通信了。但是请注意其他隐患,比如这个芯片配合基本驱动只能完成基础的收发功能,流控的RTS、CTS等还是需要专用驱动实现的!

    +

    最后吐槽 +

    为了串口问题我找了一下午的资料,最后才在外网找到了一个能用的。国内的教程都是说要修改/boot/gurb/gurb2,宝友,这可不兴改啊,这个改坏了系统可就没了啊!这些文件普通用户连浏览权限都没就可见其重要性。

    +

    参考: +

    https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/

    +

    核心步骤就参考了这个,在Linux方面大家还是多用google,多用英文去搜索吧!

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210429-deepinautorun/index.html b/public/posts/210429-deepinautorun/index.html new file mode 100644 index 0000000..ffbc1ec --- /dev/null +++ b/public/posts/210429-deepinautorun/index.html @@ -0,0 +1,677 @@ + + + + + + + +deepin和UOS,快捷方式,开机自启动 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    deepin和UOS,快捷方式,开机自启动
    +
    + + +
    +

    deepin和UOS,快捷方式,开机自启动

    + +
    + +
    2021/04/29·Kushidou
    +
    + +

    修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。

    +

    桌面快捷方式 +

    deepin的桌面快捷方式是以*.desktop文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容

    +
    + +
    [Desktop Entry]
    +Name=ddd
    +Exec="/usr/sbin/xx.sh"
    +Type=Application
    + +
    +
    +

    其中Desktop Entry表示声明该文件为快捷方式文件。Name是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。Exec是程序的启动脚本,或者启动指令。Type是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。

    +

    把这个文件放在~/Desktop,就可以看到桌面上多了一个快捷方式了。

    +

    开始菜单 +

    sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。

    +

    开机自动执行 +

    UOS/Deepin和Ubuntu +

    cp ~/Desktop/刚刚的.desktop ~/.config/autostart

    +

    需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(lightdm.service)执行,导致error从而无法进入桌面,将脚本放进/etc/profile.d之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。

    +

    经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在~/.config/下手动创建一个autostart目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。

    +

    麒麟和其他使用X11桌面的Linux +

    本条目包含UOS和Ubuntu、deepin系统

    +

    自启动目录为/etc/xdg/autostart,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210630-sudowithoutpasswd/index.html b/public/posts/210630-sudowithoutpasswd/index.html new file mode 100644 index 0000000..c1b7025 --- /dev/null +++ b/public/posts/210630-sudowithoutpasswd/index.html @@ -0,0 +1,678 @@ + + + + + + + +免密码使用sudo – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    免密码使用sudo
    +
    + + +
    +

    免密码使用sudo

    + +
    + +
    2021/06/30·Kushidou
    +
    + +

    记录一种可以免密码使用sudo的方法。

    +

    每次使用sudo提权,常常要输密码太烦了,所以想给自己赋予免密码的特权。

    +
      +
    1. +

      首先登录root用户: sudo -i

      +
    2. +
    3. +

      这个操作的核心文件是/etc/sudoers,可以看到这个文件是只读的,要修改他需要先赋予写入权限。

      +
    4. +
    +
    + +
    chmod 666 /etc/sudoers
    + +
    +
    +

    文件是只读权限

    +
      +
    1. 使用vim修改文件,vi /etc/sudoers,把当前用户和用户组加入,如图
    2. +
    +
    + +
    # 表示为该用户免密码
    +yourname	ALL=(ALL:ALL) NOPASSWD:ALL
    +# 表示为该用户组免密码
    +%yourname	ALL=(ALL:ALL) NOPASSWD:ALL
    +# 不用严格按照次形式,与你的这个文件中root的格式对应即可。
    + +
    +
    +
      +
    1. :wq保存,把sodoers改回只读
    2. +
    +
    + +
    chmod 440 /etc/sudoers
    + +
    +
    +

    文件修改的内容

    +
      +
    1. 完成~~~ 把终端关掉再次打开就应该能看到效果了。
    2. +
    +
    +

    PS. 不可使用sudo chmod

    +

    一定要直接sudo -i进入root用户,如果使用sudo chmod以普通用户的身份修改sudoers文件的话,改完会发现无法使用sudo指令把权限改回去了,提示“任何人可读写,无法找到有效的sudoers”。其实还有一种更加安全的操作,但是那个编辑器用起来没有vim顺手,

    + +
    +

    参考:https://blog.csdn.net/wxqee/article/details/72718869

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210719-gpuinfoinlinux/index.html b/public/posts/210719-gpuinfoinlinux/index.html new file mode 100644 index 0000000..45894eb --- /dev/null +++ b/public/posts/210719-gpuinfoinlinux/index.html @@ -0,0 +1,684 @@ + + + + + + + +Linux中获得AMD显卡的状态信息 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux中获得AMD显卡的状态信息
    +
    + + +
    +

    Linux中获得AMD显卡的状态信息

    + +
    + +
    2021/07/19·Kushidou
    +
    + +

    在Linux下获得AMD GPU的状态和温度信息。

    +

    在Linux下如果要查看Nvidia GPU的状态信息,可以使用Nvidia驱动自带的工具。而AMD显卡一般都是使用的开源驱动,这种情况下如何获得显卡的状态和占用呢?

    +

    文章部分图片之后再附

    +

    1、获取GPU信息 +

    如需获得GPU的信息(型号、驱动)等,如果系统提供类似*“设备管理器”*的工具可以打开查看。或者就是用lspci指令查看。

    +
    + +
    # 获得所有PCIe设备,在其中找到AMD GPU
    +[exam@ple]:~$ lspci
    +00:02.0 VGA compatible controller: xxxxxxxx
    +
    +[exam@ple]:~$ lspci -v -s 00:02.0
    +# 这里就会显示显卡的具体信息了,包括型号、驱动等,之后补充
    + +
    +
    +

    2、GPU占用率 +

    显卡占用率是一个能反映负载的直观指标,AMD显卡可以通过安装radeontop软件查看。

    +

    radeontop界面

    +

    最上面是GPU管道使用,大概类似于总体使用率的东西;最下面是内存使用率,使用sudo权限能看到更多信息。其他项目可以到radeontop的Github页面查看。

    +

    3、温度 +

    特指GPU温度。查阅这篇文章所涉及的知识点的最初目的,也是确认机箱过热是否是GPU的问题。

    +

    GPU温度使用第三方软件查看,并使其实时更新。查看温度使用sensors指令,通过安装lm-sensors实现。执行sensors时只会显示一次,我们可以利用watch指令定时刷新温度显示:

    +
    + +
    watch -n 2 sensors
    +# 每隔2秒执行sensors指令显示结果
    + +
    +
    +

    ![watch执行结果](图片占位)

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210824-linuxtouchscreenfin/index.html b/public/posts/210824-linuxtouchscreenfin/index.html new file mode 100644 index 0000000..d987ae9 --- /dev/null +++ b/public/posts/210824-linuxtouchscreenfin/index.html @@ -0,0 +1,774 @@ + + + + + + + +Linux处理多触屏的终极解决方案 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux处理多触屏的终极解决方案
    +
    + + +
    +

    Linux处理多触屏的终极解决方案

    + +
    + +
    2021/08/24·Kushidou
    +
    + +

    一般Linux操作系统接入多个触摸屏的最终解!

    +

    21年3月中旬开始,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。

    +

    重要: 本文全文基于软件xinput

    +

    更重要: 如简介所说,仍有一种方法无法对应,具体为【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。

    +

    0、核心:xinput +

    xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)

    +
    +

    xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级

    +

    xinput list –id-only : 仅仅列出设备ID(序号,每次开机都可能改变)

    +

    xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕

    +

    xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。

    + +
    +

    1、简约:两个完全不同的触摸显示器 +

    xinput map-to-output "$DEVICE NAME A" PORT-A

    +

    这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。

    +

    首先通过xinput查看所有的输入设备:

    +
    + +
    ~$ xinput
    +┌ Virtual core pointer		id=2 [master pointer  (3)]
    +|	ILITEK TLITEK-TP		id=9 [slave  pointer  (2)]
    +|	ILITEK Multi-Touch		id=7 [slave  pointer  (2)]
    +(下略)
    + +
    +
    +

    可以看到ILITEK ILITEK-TPILITEK Multi-Touch显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:

    +

    xinput map-to-output "ILITEK ILITEK-TP" HDMI-0

    +

    xinput map-to-output "ILITEK Multi-Touch" HDMI-1

    +

    这种方式的要点是1.必须带英文"",2.引号内名称须完整。map-to-output所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。

    +

    2、复杂但通用:VID:PID不同的显示器 +

    不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。

    +

    通过xinput list-props [id]可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。list-props的结果示例如下:

    +
    + +
    (后续再补)
    + +
    +
    +

    其中Device Product ID一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:

    +
    + +
    #!/bin/bash
    +# 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:
    +# 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段
    +VIDA=222a
    +PIDA=0001
    +PORTA=VGA-0
    +
    +# 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行
    +# 或者这里直接填写Device Product ID里面的结果,先VID后PID。
    +# 字母O,不是数字0,多屏重复本段
    +VIDA_O=$((16#$VIDA))
    +PIDA_O=$((16#$PIDA))
    +
    +# =========自动处理步骤,请勿随意修改==========
    +for I in $(xinput list --id-only)
    +do
    +# 根据设备ID取其序号
    +    CUR_FIRST=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $1}'|awk '{print $1}')
    +    CUR_SECOND=$(xinput list-props $I|grep "Device Product ID"|awk -F : '{print $2}'|awk -F , '{print $2}'|awk '{print $1}')
    +# 3、屏映射,多屏重复本段
    +    if [ "$VIDA_O" == "$CUR_FIRST" ] && [ "$PIDA_O" == "$CUR_SECOND" ];
    +    then
    +		  xinput map-to-output $I $PORTA
    +    fi
    +done
    +exit 0
    + +
    +
    +

    这个脚本使用xinput list --id-only仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用map-to-output进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。

    +

    3、[2]的进阶版 +

    前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。

    +

    在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是pyudev库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装python3+pyudev,并且使用desktop文件在开机时启动,即可。

    +
    + +
    #!/usr/bin/python3
    +# -- coding: UTF-8 --
    +# -- Author: Blues --
    +
    +import pyudev
    +import os
    +
    +context = pyudev.Context()
    +monitor = pyudev.Monitor.from_netlink(context)
    +monitor.filter_by(subsystem='hid')
    +for device in iter(monitor.poll, None):
    +    if device.driver != "hid-multitouch":
    +        continue
    +    if device.action == "add" or device.action == "change":
    +        os.system("/var/my-touchscreen/remap-screen")
    + +
    +
    +

    上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照设备名、VID、UID、物理接口四大特征,自动映射到对应的显示屏上了。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210914-linuxbackupdd/index.html b/public/posts/210914-linuxbackupdd/index.html new file mode 100644 index 0000000..12a1394 --- /dev/null +++ b/public/posts/210914-linuxbackupdd/index.html @@ -0,0 +1,703 @@ + + + + + + + +使用dd指令备份和恢复Linux系统(硬盘级) – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    使用dd指令备份和恢复Linux系统(硬盘级)
    +
    + + +
    +

    使用dd指令备份和恢复Linux系统(硬盘级)

    + +
    + +
    2021/09/14·Kushidou
    +
    + +

    害,谁不是个DD (誰でも 大好き)了呢!

    +

    但是这玩意儿太慢了。。。

    +

    起因 +

    使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。

    +

    DD说明 +

    dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。

    +

    dd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。

    +

    实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。

    +

    DD备份 +

    + +
    dd if=/dev/sda of=/dev/sdb				=>    备份整个磁盘到另外一个磁盘
    +dd if=/dev/sdb of=sda.img				=>    备份整个磁盘为某个文件
    +dd if=/dev/sda | gzip > sda.img    		=>    备份并且压缩
    +dd if=/dev/sda bs=1M | gzip > sda.img	=>    指定块大小,备份并压缩(据说能提速)
    + +
    +
    +

    DD复原 +

    + +
    dd if=/dev/sdb of=/dev/sda					=>从另一个磁盘恢复回来
    +gzip -dc sda.img | dd of=/dev/sda			=>从压缩的文件恢复出来
    +gzip -dc sda.img | dd of=/dev/sda bs=1M		=>前面指定了块大小,这里也需要
    + +
    +
    +

    参考: +

    本文参考(抄)了以下内容:

    +

    Linux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像

    +

    linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)

    +

    还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ )

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210914-mdadm/index.html b/public/posts/210914-mdadm/index.html new file mode 100644 index 0000000..a41d640 --- /dev/null +++ b/public/posts/210914-mdadm/index.html @@ -0,0 +1,687 @@ + + + + + + + +Linux恢复操作系统后mdadm导致开机变慢的方法 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux恢复操作系统后mdadm导致开机变慢的方法
    +
    + + +
    +

    Linux恢复操作系统后mdadm导致开机变慢的方法

    + +
    + +
    2021/09/14·Kushidou
    +
    + +

    解决Linux系统异地恢复后开机mdadm报错导致开机时间变长。

    +

    现象 +

    使用deepin-clone恢复UOS镜像后,开机会不断跳一条日志:

    +
    + +
    mdadm: no arrays found in config file or automatically
    + +
    +
    +

    大概蹦个15条后,系统才真正开机。mdadm好像是一个磁盘raid管理工具,当磁盘变化后其配置也不适配了,必须等到超时才会加载默认配置。那么我们就为其生成一个新的配置即可解决问题。

    +

    方案 +

    步骤一:删除mdadm.conf

    +
    + +
    sudo rm /etc/mdadm/mdadm.conf
    + +
    +
    +

    步骤二:生成新的mdadm.conf

    +
    + +
    sudo update-initramfs  -u
    + +
    +
    +

    ps:如果是swap分区问题导致等待90s开机慢,不归这儿管。

    +

    从这儿抄的: +

    W: mdadm: no arrays found in config file or automatically导致开机变慢的解决方案(deepin)

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/210928-rpm_package/index.html b/public/posts/210928-rpm_package/index.html new file mode 100644 index 0000000..f9ca54a --- /dev/null +++ b/public/posts/210928-rpm_package/index.html @@ -0,0 +1,601 @@ + + + + + + + +RPM安装包制作方法 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    RPM安装包制作方法
    +
    + + +
    +

    RPM安装包制作方法

    + +
    + +
    2021/09/28·Kushidou
    +
    + +

    Linux如Centos使用的RPM打包方法。本文仅涉及二进制打包,且采用取巧的方法简化操作。(笔记,而非教程)

    +

    正文开始

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/211019-dpkg_package_guide/index.html b/public/posts/211019-dpkg_package_guide/index.html new file mode 100644 index 0000000..35dcfa4 --- /dev/null +++ b/public/posts/211019-dpkg_package_guide/index.html @@ -0,0 +1,759 @@ + + + + + + + +DEB打包方法简介 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    DEB打包方法简介
    +
    + + +
    +

    DEB打包方法简介

    + +
    + +
    2021/10/19·Kushidou
    +
    + +

    简单介绍deb打包的方法,包括目录树格式和打包注意事项。

    +

    文件结构 +

    首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录/。一个工作空间的目录树如下:

    +
    + +
      PackageDIR
    +	├─DEBIAN
    +    ├─opt
    +    │  └─kushidou-touchscreen
    +    │      └─desktops
    +    ├─usr
    +    │  ├─bin
    +    │  └─share
    +    └─var
    + +
    +
    +

    其中,DEBIAN目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。

    +

    DEBIAN +

    DEBIAN目录下有一个control文件和至多四个脚本文件。

    +

    control +

    control文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NAMEDescription
    Package软件名,dpkg -l 列出的名字
    Version版本号-修订号
    Section软件类型,比如mail, test, x11等
    Priority优先级/重要性,比如optional 可选的
    Architecture软件架构,比如x86, arm64
    Depends依赖环境,用 , 隔开,用(>=< )指定版本号
    Suggests推荐安装,指非必要依赖,可以提供更多功能
    Maintainer作者 <邮箱或者其他联系方式>
    Descritprion简介,可以换行书写
    +

    image-20211019094048011

    +

    额外脚本 +

    DEBIAN目录下之多放置四个可执行脚本,包括安装前preinst安装后postinst卸载前prerm卸载前postrm,四个脚本都需要以#!/bin/bash开头,内容与普通脚本无异,文件名不需要后缀。

    +

    这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以root身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。

    +

    此外postinst可以用来启动已安装的程序,prerm常用来停止正在运行的程序。

    +

    md5sums +

    为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。

    +

    打包 +

    所有文件配置完成后,进入工作空间上级目录,执行指令

    +
    + +
    dpkg -b ./PackageDIR packagename.deb
    + +
    +
    +

    deb包命名的一般规则为 软件名_版本-修订号_架构.deb

    +

    其他信息 +

    一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/211115-fixnasdriver/index.html b/public/posts/211115-fixnasdriver/index.html new file mode 100644 index 0000000..3293555 --- /dev/null +++ b/public/posts/211115-fixnasdriver/index.html @@ -0,0 +1,751 @@ + + + + + + + +记一次拯救NAS硬盘 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    记一次拯救NAS硬盘
    +
    + + +
    +

    记一次拯救NAS硬盘

    + +
    + +
    2021/11/15·Kushidou
    +
    + +

    更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据?

    +

    图片稍后补充。

    +

    v 0 起因 +

    双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0.

    +

    考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。

    +

    为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。

    +

    v-1 杯具 +

    当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。

    +

    v-2 尝试 +

    DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内写入任何内容,也没有格式化。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。

    +

    为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。

    +

    经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。

    +

    搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。

    +

    难道就这么放弃了么?

    +

    v 1 曙光 +

    群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。

    +

    我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?!

    +

    开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。

    +

    把小硬盘用易驱线连上树莓派,lsblk看到这个硬盘有三个分区,其中/dev/sda3下面还有一个md127,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。

    +

    v 2 得到数据 +

    安装mdadm软件包,我的树莓派不带这个,然后就能挂载raid盘了。

    +
    + +
    sudo mount /dev/md127 ~/disk
    + +
    +
    +

    disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个NasData目录,这正是我当时保存文件的地方。cd进去,在!都还在!

    +

    v 3 移动数据 +

    我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。

    +

    在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs

    +
    + +
    sudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata
    + +
    +
    +

    主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。

    +
    + +
    rsync -vrultD --exclude '文件名' --exclude '目录名' ~/disk/NasData/* ~/Nasdata/临时目录
    +# -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息
    +# 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败
    + +
    +
    +

    经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。

    +

    v 4 结论 +

    现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。

    +

    这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。

    +

    总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢?

    +

    image-20211115095410121

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/211203-linuxchangeusername/index.html b/public/posts/211203-linuxchangeusername/index.html new file mode 100644 index 0000000..aa65903 --- /dev/null +++ b/public/posts/211203-linuxchangeusername/index.html @@ -0,0 +1,706 @@ + + + + + + + +在Linux环境下修改用户名和主目录 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    在Linux环境下修改用户名和主目录
    +
    + + +
    +

    在Linux环境下修改用户名和主目录

    + +
    + +
    2021/12/03·Kushidou
    +
    + +

    如何在Linux下完整修改当前用户名和主目录,还不影响其他文件?

    +

    Linux的用户名是一个用户的字符串表示,是可以修改的。只要用户的id不改变就不会被系统认作新用户,所有的权限都可以继承。下面介绍如何修改用户的名称、所属组,并更改主目录的名称与对应关系。

    +

    一、进入“单用户模式” +

    为了修改用户名时不影响程序运行,需要把当前用户打开的所有程序都关闭。该方法可以快速注销当前用户。

    +
    + +
    init  1
    +执行后输入当前用户密码
    +进入黑屏终端后需要输入root密码,可以使用sudo passwd 提前设置
    + +
    +
    +

    对于屏蔽init指令的系统,一般情况下注销用户就可以了,但是有些GUI桌面环境就是以当前用户的身份运行的,所以提供如下几个方式关闭当前用户,挨个尝试即可。

    +
    + +
    # 1 桌面环境中点击注销
    +
    +# 2 杀掉当前用户所有进程
    +pkill -kill -u old_name
    +# 3 杀掉进程后可能桌面重启,关闭Lightdm管理器
    +#     Ctrl + Alt + F2 进入第二页面,以root用户登录(可能需要提前设置root密码)
    +systemctl stop lightdm
    +#     然后再次执行第二步
    + +
    +
    +

    二、修改信息 +

    init 0可以直接进入黑屏的终端,或者使用 ctrl + alt + f2进入(远程连接可以),以root用户登录。

    +
    + +
    # 1 修改组的名称
    +groupmod -n new_name old_name
    +# 2 修改用户名    -l 改名字  -d 改目录  -g 改组
    +usermod -l new_name -d /home/new_name -g new_name old_name
    +# 3 修改主目录
    +mv /home/old_name /home/new_name
    + +
    +
    +

    最后回到图形化界面,输入init 5 ,或者重启电脑。

    +

    修改完主目录后记得检查是不是有其他软连接指向旧目录,需要进行更新,否则还会遇到问题。

    +

    参考: +

    # linux用户家目录怎么修改,Linux 更改用户名,用户组和主目录

    +

    # 菜鸟教程-usermod指令

    +

    # linux组的管理:修改组名字,删除组,使用sudo权限

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/220104-diylinuxiso/index.html b/public/posts/220104-diylinuxiso/index.html new file mode 100644 index 0000000..7286c0b --- /dev/null +++ b/public/posts/220104-diylinuxiso/index.html @@ -0,0 +1,696 @@ + + + + + + + +定制自己的Linux ISO镜像 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    定制自己的Linux ISO镜像
    +
    + + +
    +

    定制自己的Linux ISO镜像

    + +
    + +
    2022/01/04·Kushidou
    +
    + +

    想要在原版的Linux镜像上添加自定义的软件,但是不想、不会编译,那么就可以基于Linux的iso文件进行修改,定制自己的安装试用镜像。

    +

    起因是因为对付信创操作系统的备份恢复时,UOS、麒麟自带的工具都超级不好用,两个系统的镜像不统一,导致实际使用的时候各种问题和不方便。所以使用clonezilla再生龙工具自己做一个PE镜像。这个镜像不仅可以备份、安装系统,还能够修改硬盘大小、应急拯救系统等多种功能。并且它是一个完整的系统可以安装使用。

    +

    基本计划 +

    之前尝试过使用deepin魔改lub工具制作系统的完整镜像,但是启动总是失败。LUB全称是Live Ubuntu Backup,是Ubuntu的一个工具,到了同为Debian系的deepin魔改的UOS上就水土不服了。(好绕,套娃呢搁这儿

    +

    所以采取另一种办法,用已有的Linux iso,通过解压其系统文件filesystem.squashfs,然后替换关键文件,最后实现自定义系统。

    +

    解压、修改、打包Linux文件系统 +

    首先把Linux iso挂载到系统中。把里面的filesystem.squashfs文件复制出来(因为iso是只读的,复制出来后也方便管理和备份)。我这里以UOS1040为例,其文件系统在./live目录下

    +
    + +
    sudo mount -o loop /xxx/xxx.iso  /media/usrname/isofolder
    +cd /media/usrname/isofolder/live/
    +cp ./filesystem.squashfs ~
    + +
    +
    +

    安装squashfs-tools工具,用来压缩解压归档文件。

    +
    + +
    sudo apt install squashfs-tools
    +#回到主目录后,解压filesystem.squashfs文件
    +sudo unsquashfs filesystem.squashfs
    + +
    +
    +

    解压后可以看到主目录多一个squashfs-root目录,里面存放的就是一个Linux的文件系统了。这时候把你需要的文件拷贝进去,比如我替换了/usr、/opt、/etc三个目录,这三个目录保存了我安装的clonezilla应用及其配置程序,以及我编写的辅助脚本和自启动脚本。

    +

    修改好了就可以打包了。第一个参数是刚刚解压出来修改好的文件系统,第二个参数是输出的名称。建议把之前拷贝出来的squashfs文件改名进行保存。

    +
    + +
    sudo mksquashfs squashfs-root/ filesystem.squashfs
    + +
    +
    +

    重建Linux iso +

    最后我们需要重建ISO文件。由于我们是基于原来的ISO修改的,所以只需要替换iso里面的filesystem.squashfs文件即可。否则还需要更换系统内核img/lz、重写引导。

    +

    把新生成的filesystem.squashfs文件拷贝到Windows系统里,打开UltraISO软件,直接替换Live目录下的filesystem.squashfs文件。同时我们也可以修改EFI/grub.cfg文件,毕竟这个系统主要是使用而不是安装。我调换了安装和试用的次序,并把等待时间增加到60s。

    +

    最后进行修改ISO名称等小操作,保存为ISO文件就可以了。

    +

    新生成的ISO文件既可以用rufus等工具烧录到U盘中制作PE盘,也可以试用ventoy工具制作成多系统启动盘,甚至可以烧录一张光盘来使用。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/220223-deepin_partsinddefilemanager/index.html b/public/posts/220223-deepin_partsinddefilemanager/index.html new file mode 100644 index 0000000..3ae8ab7 --- /dev/null +++ b/public/posts/220223-deepin_partsinddefilemanager/index.html @@ -0,0 +1,681 @@ + + + + + + + +Deepin/UOS文件管理器隐藏不需要的盘符 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Deepin/UOS文件管理器隐藏不需要的盘符
    +
    + + +
    +

    Deepin/UOS文件管理器隐藏不需要的盘符

    + +
    + +
    2022/02/23·Kushidou
    +
    + +

    Deepin/UOS 里处理文件管理器显示太多盘符的问题。

    +

    现象 +

    在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在/etc/fstab文件添加或删除“”并没有效果。

    +

    image-20220223090517363

    +

    原理/原因 +

    这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。

    +

    处理方法 +

      +
    1. +

      打开/etc/udev/rules.d目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。

      +
    2. +
    3. +

      打开终端,使用root权限打开编辑

      +
    4. +
    +
    + +
    sudo deepin-editor /etc/udev/rules.d/80-udisk2.rules
    + +
    +
    +
      +
    1. +

      打开新的终端,输入执行 blkid -f ,记录LABLE、UUID两列

      +

      截图录屏_选择区域_20220223090208

      +
    2. +
    3. +

      对比80-udisks2.rules的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。

      +

      (#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换)

      +

      截图录屏_选择区域_20220223090606

      +
    4. +
    5. +

      保存文件并重启计算机,即可看到效果。

      +
    6. +
    7. +

      该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。

      +
    8. +
    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/220714-unpackclonezillaimage/index.html b/public/posts/220714-unpackclonezillaimage/index.html new file mode 100644 index 0000000..00a42ba --- /dev/null +++ b/public/posts/220714-unpackclonezillaimage/index.html @@ -0,0 +1,708 @@ + + + + + + + +从再生龙(clonezilla)镜像直接提取文件 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    从再生龙(clonezilla)镜像直接提取文件
    +
    + + +
    +

    从再生龙(clonezilla)镜像直接提取文件

    + +
    + +
    2022/07/14·Kushidou
    +
    + +

    从clonezilla的镜像直接提取文件,免于先全盘全区恢复。

    +

    再生龙(clonezilla)是一个常用的Linux系统备份工具,适用于多个版本、多种CPU架构的电脑。

    +

    再生龙生成的镜像是比较特殊的格式,如果想要从里面提取文件,通常只能进行镜像恢复后进入系统提取。但是往往我们没有那么多硬盘来恢复系统。

    +

    再生龙的原理简单来说就是把硬盘分区使用partclone工具打包成img文件,然后使用gzip进行分卷压缩。我们只需要逆向操作,利用工具,把镜像解包挂载,直接在当前系统挂载,就可以得到原分区的文件了。

    +

    挂载方法: +

    1. 环境需求 +

    我们操作再生龙镜像时,最好是使用Linux环境。这个环境中,需要安装partclone(也就是再生龙生成镜像的核心工具)。

    +
    + +
    sudo apt install partclone
    +# 以Debian系为例
    + +
    +
    +

    并且我们需要准备一个足够大的硬盘,用来存储镜像包和解包出的img文件。硬盘/分区的空间需要大于镜像本来的分区空间。

    +

    2. 合并镜像 +

    再生龙默认情况下会把一个硬盘分区分按4GB的大小进行分包,通常以gz.aagz.ab等结尾,我们需要把这些包重新整合成一个gz包。

    +

    整合成gz包后,我们就可以解压gz来得到镜像img了。我们可以把两个步骤用流的方式写入一条命令,节省硬盘空间。

    +
    + +
    cat sda1.ext4-ptcl-img.gz.* | gzip -d -c > sda1.img
    + +
    +
    +

    这一步生成的img文件不能直接使用(google上也有说能用的,但是我试下来不能),需要进行一次转换。经过实际操作,这个文件所占用的空间是镜像原分区的大小,但是统计硬盘使用量时却只计算实际大小,希望了解的人可以帮忙解释一下。

    +
    + +
    partclone.extfs -r -s sda1.img -o sda1-ex.img --restore_raw_file
    + +
    +
    +

    当然上面两条指令也可以使用流的方式再次封装到一条,但是消耗的时间、失败后重新来过的时间,都会更长。

    +

    3.挂载镜像 +

    将最后生成的img镜像挂载到我们的/mnt(或其他)目录,进入这个目录就可以看到原来全部的文件了。

    +
    + +
    sudo mount -o loop -t ext4 sda1-ex.img /mnt
    + +
    +
    +

    最后我们只需要进入 /mnt 目录,就可以想直接操作硬盘一样把文件复制出来了。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/230508-remake-deb/index.html b/public/posts/230508-remake-deb/index.html new file mode 100644 index 0000000..40ff41c --- /dev/null +++ b/public/posts/230508-remake-deb/index.html @@ -0,0 +1,682 @@ + + + + + + + +讨论在Debian系操作系统中重新提取已安装的软件并重新打包 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    讨论在Debian系操作系统中重新提取已安装的软件并重新打包
    +
    + + +
    +

    讨论在Debian系操作系统中重新提取已安装的软件并重新打包

    + +
    + +
    2023/05/08·Kushidou
    +
    + +

    Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用?

    +

    之前有一个需求,要从UOS里面提取一个软件,并且将其安装到另一台设备中。当时找了一圈都没有找到相关的提取教程。但是从逻辑上来讲,dpkg安装的软件会保存deb包的相关信息,所以理论上讲是可以提取出软件包的所有内容,然后我们重新打包的。

    +

    今天在一个机缘巧合下(其实是懒得研究dpkg原理,瞎猫撞上死耗子),发现了dpkg保存文件信息的地点,于是接下来可以展开畅想:提取已经安装的软件并重新打包。

    +

    文件的保存地点 +

    我在找fcitx有关的配置文件进行全盘搜索的时候,找到了一条目录/usr/lib/dpkg-db/info,这个目录里面保留了fcitx的"conffiles", “md5sums”, “postinst”, “prerm"四个文件,以及一个list文件。恰好fcitx.deb解包出来的DEBIAN文件夹内也是这四个文件加上额外的control文件,。

    +

    那么合理猜测,dpkg安装软件的时候,不仅把软件本身的文件移动到对应位置,还把DEBIAN里面的信息和脚本也转移到/usr/lib/dpkg-db/里面保存了。

    +

    fcitx.list文件中记录的是所有的文件及其路径。想必这里面就是全部的文件了。

    +

    至于control文件,则都被保存到了/usr/lib/dpkg-db/status这个文本文件中。

    +

    流程设想 +

    既然deb包所有必要的文件都找到了,那么就可以尝试一下还原过程了。在这篇文章里我研究过deb包的组成、格式和打包方法。

    +

    比如对于软件包A:

    +
      +
    1. +

      创建一个工作目录,并在其中新建DEBIAN目录。

      +
    2. +
    3. +

      /usr/lib/dpkg-db/info中所有该软件的脚本和信息(除了A.list)转移到DEBIAN中,并重命名成规范名称。

      +
    4. +
    5. +

      /usr/lib/dpkg-db/status中与软件A有关的部分提取出来,保存到DEBIAN/control文件中。

      +
    6. +
    7. +

      根据A.list文件,找到软件包含的所有文件,将其按原样整理到工作目录内。

      +
    8. +
    9. +

      使用dpkg -b指令重新打包。

      +
    10. +
    +

    局限性 +

    以上毕竟只是设想,没有进行试验,所以估计会出现这些问题,希望有需要的人尝试后可以解决。

    +
      +
    • +

      提取信息的难度: 由于/usr/lib/dpkg-db/status文件是一个包含几乎所有deb信息的文本文件,体积大内容乱,对于自动化脚本来说有些难度。加上一些软件的衍生品会在软件名上直接加后缀,对于提取的脚本来讲可能造成混淆,提取出不必要的东西。

      +
    • +
    • +

      获取源文件的难度: 不排除一些软件安装时会删除、修改自身的一些文件,可能打包会后会缺失内容。一些软件安装过程中会进行手动配置从而造成内容改变,重做出来的软件包不一定完整。

      +
    • +
    • +

      签名问题: 我们打包的软件没有原作者的数字签名,可能无法安装,也可能时候安装官方软件时冲突。

      +
    • +
    • +

      这个方案只能处理普通的软件,一些deb打包源码,安装时进行编译的,应该是不能用这种方案复原的。(apt的deb-src源)

      +
    • +
    +

    最后,希望有大佬可以验证一下这个方案。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/230908-py-post/index.html b/public/posts/230908-py-post/index.html new file mode 100644 index 0000000..9f5c372 --- /dev/null +++ b/public/posts/230908-py-post/index.html @@ -0,0 +1,740 @@ + + + + + + + +Python socket 收取POST协议内容时没有payload – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Python socket 收取POST协议内容时没有payload
    +
    + + +
    +

    Python socket 收取POST协议内容时没有payload

    + +
    + +
    2023/09/08·Kushidou
    +
    + +

    使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况

    +

    最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。

    +

    服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。

    +
    + +
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    +sock.bind((self.host, self.port))
    +sock.listen(128)
    +
    +(client, address) = self.sock.accept()
    +th = Thread(target=self.accept_request, args=(client, address))
    +
    +def accept_request(client, address):
    +	data = client_sock.recv(8192)
    +	req = data.decode("utf-8")
    +	# 处理数据的具体方法
    +    response = process_response(req, client_addr)
    +    client_sock.send(response)
    + 
    +    # clean up
    +    client_sock.shutdown(1)
    +    client_sock.close()
    + +
    +
    +

    上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。

    +

    =====2024.01.04更新=======

    +

    下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。

    +

    其实用flask接收和处理数据会更加方便快捷:

    +
    + +
    from flask import Flask, request
    +
    +# 实例化flask
    +app = Flask(__name__)
    +
    +# 接收和处理post的方法
    +@app.route('/test', methods=['POST'])
    +def recv_file_www():
    +    # post的传入参数
    +    print(request.args.to_dict())
    +    # post所承载的数据
    +    print(request.data)
    +    return 'OK'
    +    
    +if __name__ == '__main__':
    +    app.run(port=31408, host="0.0.0.0",debug=True)
    + +
    +
    +

    上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。

    +

    ======以下是原来的内容==========

    +

    经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,必须不断的recv才能完整接收数据。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。

    +

    但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:

    +

    POST的报文头会包含一个Content-length的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。

    +

    那么将上面代码的client_sock.recv()部分改写成下面的方法:

    +
    + +
    def recv(self, client_sock):
    +    lrecved = 8192          # 记录每次接收的长度
    +    lrecv_all=0             # 记录数据总长度 
    +    data_body = bytes()     # 接收的所有数据存入这里
    +    payload_len = 0         # 解析的payload长度
    +    payload_flag = 0        # 用于记录是否接收到了payload,以及实时记录接收到payload的长度
    +    while True :
    +        part_body= client_sock.recv(8192)   # 循环接收数据
    +        data_body +=  part_body             # 将接收的数据写入data_body
    +        lrecved = len(part_body)            # 获取本次接收长度
    +        lrecv_all += lrecved                # 计算总长度
    +
    +        if payload_flag == 0:               # 没有接收到payload部分时,才进入下面的判断流程
    +            if data_body[0:3].decode() == 'GET':    # 由于我get方法没出现过断开的问题,因此直接返回即可。
    +                break
    +            elif data_body[0:4].decode() == 'POST':
    +                payload_len = count_payload_len(data_body)      # 解析头部,计算payload部分的长度
    +
    +                if lrecved < payload_len: 
    +                # 偶尔会出现能接收到payload的情况,所以多加了一个判断。
    +                # 能接收payload肯定总长度大于payload_len的
    +                    print("no payloads, keep recving...")
    +                    # 计算post报文的总长度,借用payload_flag变量
    +                    payload_flag = lrecved + payload_len
    +        # 当接收到的总长度 >= POST的计算长度,表示所有数据接收完成,退出
    +        if payload_flag <= lrecv_all:
    +            break
    +                 
    +    return data_body
    + +
    +
    +

    里面解析headers的代码如下:

    +
    + +
    def count_payload_len(data):
    +    for d in data.split(b'\r\n'):                   # 通过\r\n来分隔报文头
    +        if "LENGTH" in d.upper().decode():          # 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了
    +            return int(d.decode().split(":")[1])    # 分隔出payload长度并返回
    +    return 0
    + +
    +
    +

    上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。

    +

    对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/231127-vncserver/index.html b/public/posts/231127-vncserver/index.html new file mode 100644 index 0000000..04370e0 --- /dev/null +++ b/public/posts/231127-vncserver/index.html @@ -0,0 +1,695 @@ + + + + + + + +Linux安装VNC服务端 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    Linux安装VNC服务端
    +
    + + +
    +

    Linux安装VNC服务端

    + +
    + +
    2023/11/27·Kushidou
    +
    + +

    (笔记)在Linux系统里安装远程桌面VNC客户端并连接。

    +

    1. 安装NVC服务端 +

    + +
    sudo apt install x11vnc
    + +
    +
    +

    2. 设置远程连接密码 +

    + +
    x11vnc  --storepasswd
    +# 输入两次连接密码以设定
    +# 询问 Write password to /home/xxx/.vnc/passwd 
    +# 时,输入y并回车保存密码。
    + +
    +
    +
      +
    • 该步骤不需要使用sudo!
    • +
    +

    3. 使VNC服务开机自启动 +

    使用service的方法,将vnc作为一个服务开机启动

    +

    创建/lib/systemd/system/x11vnc.service文件并填入下面的内容

    +
    + +
    [Unit]
    +Description=Start x11vnc at startup.
    +After=multi-user.target
    +[Service]
    +Type=simple
    +ExecStart=/usr/bin/x11vnc -auth guess -rfbauth /home/xxx/.vnc/passwd
    +[Install]
    +WantedBy=multi-user.target
    + +
    +
    +
      +
    • 注意,/home/xxx 需要替换成实际的用户名!
    • +
    +

    使用vnc客户端连接 +

    我使用Windows上的VncViewer,官网RealVNC,这个软件假冒的很多,下载需要注意甄别。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/231225-dockernote/index.html b/public/posts/231225-dockernote/index.html new file mode 100644 index 0000000..4e453f0 --- /dev/null +++ b/public/posts/231225-dockernote/index.html @@ -0,0 +1,822 @@ + + + + + + + +docker简单笔记 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    docker简单笔记
    +
    + + +
    +

    docker简单笔记

    + +
    + +
    2023/12/25·Kushidou
    +
    + +

    学习docker,使用docker,编写docker

    +

    前言 +

    祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。

    +

    不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。

    +

    由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。

    +

    安装docker和docker compose +

    虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。

    +

    apt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。

    +
    + +
    # 添加docker官方的gpg
    +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    +
    +# 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加
    +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu   $(. /etc/os-release && echo "$VERSION_CODENAME") stable" |   sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    +
    +# 我这边添加的结果是
    +deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu jammy stable
    + +
    +
    +

    然后可以通过apt源安装docker了

    +
    + +
    sudo apt update
    +sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin
    + +
    +
    +

    *注意,上面的指令同时安装了docker-compose。如果不安装docker,直接安装docker-compose也是可以的。

    +
    + +
    wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64
    +sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose
    + +
    +
    +

    更加具体的步骤和细节可以到官网查看:docker安装docker-compose安装

    +

    docker 基本使用方法 +

    使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。

    +
    + +
    # 拉取一个镜像
    +docker pull xxx/yyy
    +# 创建容器并运行
    +docker run --name aaa xxx/yyy
    +# 停止容器
    +docker stop docker-id
    + +
    +
    +

    关于运行容器,有很多常用的参数:

    +
    +

    -d 容器转入后台运行

    +

    -e 设置容器的环境变量

    +

    –name 容器名称

    +

    -p 80:80 将暴露的端口映射到容器内端口,外部:内部

    +

    -v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内

    +

    -e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。

    + +
    +

    停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。

    +

    查询容器id,可以使用指令docker ps。docker还有其他几个查询和控制指令

    +
    +

    docker exec 容器 指令 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。

    +

    docker ps 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。

    +

    docker images 列出已经下载的镜像列表。与 docker image 指令不同。

    +

    docker kill 容器名/id 杀死一个无法stop的容器

    +

    docker rm 容器 删除一个容器

    +

    docker rmi 镜像名 删除一个镜像

    +

    docker restart 容器ID 重启一个容器,毕竟我们不需要每次都创建新容器。

    + +
    +

    关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。

    +

    更换docker镜像源 +

    和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。

    +

    修改/etc/docker/daemon.json文件,添加下面的内容:

    +
    + +
    {
    +    "registry-mirrors": [ 
    +        "https://docker.mirrors.ustc.edu.cn" 
    +    ]
    +}
    + +
    +
    +

    编辑完成后需要重启docker服务。

    +

    具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。

    +

    docker-compose +

    docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。

    +

    首先创建一个工作目录,并创建一个docker-compose.yml的文件,默认情况下docker compose指令只认这个文件名。

    +
    + +
    version: '3'
    +services:
    +  [NAME1]:
    +    image: "image/name"
    +    ports:
    +      - 81:80
    +      - 8080:8080
    +    environment:
    +      HOSTNAME: "cc"
    +      PORT: 8181
    +    volumes:
    +      - nginx-data:/etc/nginx
    +  [NAME2]:
    +    image: 'xxx'
    + +
    +
    +

    docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,ports字段指定了内外端口的映射,即’外部:内部’,卷映射同理。environment不需要 - 开头。

    +

    编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动

    +
    +

    docker compose up 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。

    +

    docker compose down 停止容器,并删除对应的容器和网络

    +

    docker compose exec NAME command 在ymal文件内其中一个容器里运行指令

    + +
    +

    docker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。

    +

    目前浅学了一下,只有学一点更新一点吧。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/about_hugo_website/index.html b/public/posts/about_hugo_website/index.html new file mode 100644 index 0000000..8e617b1 --- /dev/null +++ b/public/posts/about_hugo_website/index.html @@ -0,0 +1,674 @@ + + + + + + + +关于使用HUGO建立博客站点 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    + +
    +
    + Posts +
    关于使用HUGO建立博客站点
    +
    + + +
    +

    关于使用HUGO建立博客站点

    + +
    + +
    2021/03/23·Kushidou
    +
    + +

    使用hugo建立本站的规划和记录。

    +

    关于使用HUGO建立博客

    在经历了worldpress的性能消耗和慢速加载后,我现在决定换用hugo+caddy的建站模式。昨天已经完成了caddy2的基础设置,现在开始测试使用hugo。

    +

    hugo的安装与首次创建 +

    安装使用chocolatey,一个windows下的仿apt、yum的软件包管理器。安装好chocolatey后,根据hugo官网的内容,choco install hugo -confirm安装基础的hugo(好处是不用再配置环境变量了)。

    +

    为hugo创建目录,进入后hugo site new website,hugo会自动建立它所需要的层级结构。

    +

    添加第一个页面,`hugo new xxx/xxx.md’,创建的内容会出现在content下,创建多级目录也适合将来归类。

    +

    编写文章 +

    hugo的文章是基于markdown文档的,创建的文章会自动加入以下抬头

    +
    + +
    ---
    +title: "$文章标题"
    +date: $时间
    +draft: true
    +---
    + +
    +
    +

    这个可以修改为json或ymal格式,理应可以添加更多项目,这个之后再研究。

    +

    主题/皮肤 +

    主题放在themes下,我在知乎上找到了一个高赞且顺眼的,有哪些好看的Hugo主题? - 飞雪无情的回答 - 知乎,文章中附了github连接,那么我们直接git过来就完事了。

    +

    生成网站 +

    文章+主题都有了,现在可以生成第一个网站了(虽然写这些字的时候,网站还没生成)。hugo就能直接产生网页,保存至public文件夹。hugo server可以启动一个服务器预览静态网页。

    +
    +
    + 最后更新于
    + +
    + + + +
    +
    +
    + + + + + + + + diff --git a/public/posts/assents/image-20220223090517363.png b/public/posts/assents/image-20220223090517363.png new file mode 100644 index 0000000..fbd6283 Binary files /dev/null and b/public/posts/assents/image-20220223090517363.png differ diff --git a/public/posts/assents/截图录屏_选择区域_20220223090208.png b/public/posts/assents/截图录屏_选择区域_20220223090208.png new file mode 100644 index 0000000..d85b9ff Binary files /dev/null and b/public/posts/assents/截图录屏_选择区域_20220223090208.png differ diff --git a/public/posts/assents/截图录屏_选择区域_20220223090606.png b/public/posts/assents/截图录屏_选择区域_20220223090606.png new file mode 100644 index 0000000..bff1e57 Binary files /dev/null and b/public/posts/assents/截图录屏_选择区域_20220223090606.png differ diff --git a/public/posts/index.html b/public/posts/index.html new file mode 100644 index 0000000..56c4986 --- /dev/null +++ b/public/posts/index.html @@ -0,0 +1,603 @@ + + + + + + + +Posts – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + +
    +
    + + +
    +

    Posts

    +
    + + +
    + +

    Python socket 收取POST协议内容时没有payload

    + +

    使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况

    +

    + 2023/09/08 + + 修改于 2024/01/04 + +

    +
    +
    + +

    docker简单笔记

    + +

    学习docker,使用docker,编写docker

    +

    + 2023/12/25 + +

    +
    +
    + +

    Linux安装VNC服务端

    + +

    (笔记)在Linux系统里安装远程桌面VNC客户端并连接。

    +

    + 2023/11/27 + +

    +
    +
    + +

    讨论在Debian系操作系统中重新提取已安装的软件并重新打包

    + +

    Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用?

    +

    + 2023/05/08 + +

    +
    +
    + +

    从再生龙(clonezilla)镜像直接提取文件

    + +

    从clonezilla的镜像直接提取文件,免于先全盘全区恢复。

    +

    + 2022/07/14 + +

    +
    +
    + +

    Deepin/UOS文件管理器隐藏不需要的盘符

    + +

    处理UOS出现其他盘符的问题

    +

    + 2022/02/23 + +

    +
    +
    + +

    定制自己的Linux ISO镜像

    + +

    为已有的Linux ISO镜像添加自己的功能。

    +

    + 2022/01/04 + +

    +
    +
    + +

    在Linux环境下修改用户名和主目录

    + +

    如何在Linux下完整修改当前用户名和主目录,还不影响其他文件?

    +

    + 2021/12/03 + +

    +
    +
    + +

    记一次拯救NAS硬盘

    + +

    黑群晖拿下来的硬盘不小心删除分区后进行数据拯救。

    +

    + 2021/11/15 + +

    +
    +
    + +

    DEB打包方法简介

    + +

    Debian系Linux,如ubuntu, deepin等系统的deb打包教程

    +

    + 2021/10/19 + +

    +
    +
    +
    +
    +
    + + + + + + + diff --git a/public/posts/index.xml b/public/posts/index.xml new file mode 100644 index 0000000..fedd227 --- /dev/null +++ b/public/posts/index.xml @@ -0,0 +1,19 @@ + + + 香风家的火柴盒 – Posts + https://www.small09.top/posts/ + Recent content in Posts on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 25 Dec 2023 09:06:25 +0800 + + + + + + + + + + + diff --git a/public/posts/page/1/index.html b/public/posts/page/1/index.html new file mode 100644 index 0000000..fff8ac8 --- /dev/null +++ b/public/posts/page/1/index.html @@ -0,0 +1,9 @@ + + + + https://www.small09.top/posts/ + + + + + diff --git a/public/posts/page/2/index.html b/public/posts/page/2/index.html new file mode 100644 index 0000000..b208fea --- /dev/null +++ b/public/posts/page/2/index.html @@ -0,0 +1,616 @@ + + + + + + + +Posts – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + +
    +
    + + +
    +

    Posts

    +
    + + +
    + +

    RPM安装包制作方法

    + +

    制作一个rpm安装包。

    +

    + 2021/09/28 + +

    +
    +
    + +

    Linux恢复操作系统后mdadm导致开机变慢的方法

    + +

    mdadm no arrays found in config file or automatically

    +

    + 2021/09/14 + +

    +
    +
    + +

    使用dd指令备份和恢复Linux系统(硬盘级)

    + +

    Backup Linux with dd command.

    +

    + 2021/09/14 + +

    +
    +
    + +

    Linux多显示器触屏调试与暂行解决方案

    + +

    Linux with multi touch screen

    +

    + 2021/03/11 + + 修改于 2021/09/14 + +

    +
    +
    + +

    deepin和UOS,快捷方式,开机自启动

    + +

    修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。

    +

    + 2021/04/29 + + 修改于 2021/09/14 + +

    +
    +
    + +

    Linux处理多触屏的终极解决方案

    + +

    Linux with mutli touch screen

    +

    + 2021/08/24 + + 修改于 2021/09/14 + +

    +
    +
    + +

    Linux中获得AMD显卡的状态信息

    + +

    在Linux下获得AMD GPU的状态和温度信息。

    +

    + 2021/07/19 + +

    +
    +
    + +

    免密码使用sudo

    + +

    记录一种可以免密码使用sudo的方法。

    +

    + 2021/06/30 + + 修改于 2021/07/13 + +

    +
    +
    + +

    关于使用HUGO建立博客站点

    + +

    使用hugo建立本站的规划和记录。

    +

    + 2021/03/23 + + 修改于 2021/05/24 + +

    +
    +
    + +

    Linux启用四个以上的串口

    + +

    工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。

    +

    + 2021/04/20 + +

    +
    +
    +
    +
    +
    + + + + + + + diff --git a/public/posts/page/3/index.html b/public/posts/page/3/index.html new file mode 100644 index 0000000..fb4dc18 --- /dev/null +++ b/public/posts/page/3/index.html @@ -0,0 +1,531 @@ + + + + + + + +Posts – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + +
    +
    + + + + + + + diff --git a/public/resources/blog_main.jpg b/public/resources/blog_main.jpg new file mode 100644 index 0000000..a55da1e Binary files /dev/null and b/public/resources/blog_main.jpg differ diff --git a/public/resources/daily_main.png b/public/resources/daily_main.png new file mode 100644 index 0000000..71ea96c Binary files /dev/null and b/public/resources/daily_main.png differ diff --git a/public/resources/icon_180x180.png b/public/resources/icon_180x180.png new file mode 100644 index 0000000..8d2aa84 Binary files /dev/null and b/public/resources/icon_180x180.png differ diff --git a/public/resources/icon_32x32.png b/public/resources/icon_32x32.png new file mode 100644 index 0000000..4f7f5ee Binary files /dev/null and b/public/resources/icon_32x32.png differ diff --git a/public/resources/icon_32x32_2.png b/public/resources/icon_32x32_2.png new file mode 100644 index 0000000..4f7f5ee Binary files /dev/null and b/public/resources/icon_32x32_2.png differ diff --git a/public/resources/icon_32x32_3.png b/public/resources/icon_32x32_3.png new file mode 100644 index 0000000..fabaae1 Binary files /dev/null and b/public/resources/icon_32x32_3.png differ diff --git a/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_180x180_fill_box_center_2.png b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_180x180_fill_box_center_2.png new file mode 100644 index 0000000..8d2aa84 Binary files /dev/null and b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_180x180_fill_box_center_2.png differ diff --git a/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_180x180_fill_box_center_3.png b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_180x180_fill_box_center_3.png new file mode 100644 index 0000000..f955e7c Binary files /dev/null and b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_180x180_fill_box_center_3.png differ diff --git a/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_32x32_fill_box_center_2.png b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_32x32_fill_box_center_2.png new file mode 100644 index 0000000..4f7f5ee Binary files /dev/null and b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_32x32_fill_box_center_2.png differ diff --git a/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_32x32_fill_box_center_3.png b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_32x32_fill_box_center_3.png new file mode 100644 index 0000000..fabaae1 Binary files /dev/null and b/public/resources/icon_hu2edf57696400f275bcb1d2f9022ef01c_28092_32x32_fill_box_center_3.png differ diff --git a/public/resources/photograph_main.jpg b/public/resources/photograph_main.jpg new file mode 100644 index 0000000..d8efb67 Binary files /dev/null and b/public/resources/photograph_main.jpg differ diff --git a/public/resources/头像.jpg b/public/resources/头像.jpg new file mode 100644 index 0000000..9de17bd Binary files /dev/null and b/public/resources/头像.jpg differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..c36f3b3 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,20 @@ +{ + "name": "Hextra", + "short_name": "Hextra", + "start_url": "index.html", + "icons": [ + { + "src": "android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#000000", + "background_color": "#000000", + "display": "standalone" +} diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..8e7d21b --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,146 @@ + + + + https://www.small09.top/tags/docker/ + 2023-12-25T09:06:25+08:00 + + https://www.small09.top/posts/231225-dockernote/ + 2023-12-25T09:06:25+08:00 + + https://www.small09.top/tags/linux/ + 2023-12-25T09:06:25+08:00 + + https://www.small09.top/posts/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/tags/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/tags/%E5%AE%B9%E5%99%A8%E5%92%8C%E8%99%9A%E6%8B%9F%E5%8C%96/ + 2023-12-25T09:06:25+08:00 + + https://www.small09.top/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/posts/231127-vncserver/ + 2023-11-27T09:33:32+08:00 + + https://www.small09.top/tags/%E8%BD%AF%E4%BB%B6/ + 2023-11-27T09:33:32+08:00 + + https://www.small09.top/tags/http/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/tags/python/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/posts/230908-py-post/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/tags/server/ + 2024-01-04T00:00:00+08:00 + + https://www.small09.top/tags/deepin/ + 2023-05-08T14:43:41+08:00 + + https://www.small09.top/posts/230508-remake-deb/ + 2023-05-08T14:43:41+08:00 + + https://www.small09.top/tags/clonezilla/ + 2022-07-14T13:05:51+08:00 + + https://www.small09.top/posts/220714-unpackclonezillaimage/ + 2022-07-14T13:05:51+08:00 + + https://www.small09.top/tags/deepin/uos/ + 2022-02-23T09:18:55+08:00 + + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + 2022-02-23T09:18:55+08:00 + + https://www.small09.top/posts/220104-diylinuxiso/ + 2022-01-04T09:17:38+08:00 + + https://www.small09.top/posts/211203-linuxchangeusername/ + 2021-12-03T09:01:23+08:00 + + https://www.small09.top/tags/nas/ + 2021-11-15T10:02:30+08:00 + + https://www.small09.top/tags/%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D/ + 2021-11-15T10:02:30+08:00 + + https://www.small09.top/tags/%E7%BE%A4%E6%99%96/ + 2021-11-15T10:02:30+08:00 + + https://www.small09.top/posts/211115-fixnasdriver/ + 2021-11-15T10:02:30+08:00 + + https://www.small09.top/posts/211019-dpkg_package_guide/ + 2021-10-19T10:12:23+08:00 + + https://www.small09.top/posts/210928-rpm_package/ + 2021-09-28T11:06:54+08:00 + + https://www.small09.top/posts/210914-mdadm/ + 2021-09-14T15:16:13+08:00 + + https://www.small09.top/posts/210914-linuxbackupdd/ + 2021-09-14T13:51:16+08:00 + + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + 2021-09-14T00:00:00+08:00 + + https://www.small09.top/tags/%E8%A7%A6%E6%91%B8%E5%B1%8F/ + 2021-09-14T00:00:00+08:00 + + https://www.small09.top/posts/210719-gpuinfoinlinux/ + 2021-07-19T13:35:00+08:00 + + https://www.small09.top/tags/%E6%98%BE%E5%8D%A1/ + 2021-07-19T13:35:00+08:00 + + https://www.small09.top/posts/210630-sudowithoutpasswd/ + 2021-07-13T08:43:40+08:00 + + https://www.small09.top/posts/210429-deepinautorun/ + 2021-09-14T00:00:00+08:00 + + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + 2021-04-20T00:00:00+08:00 + + https://www.small09.top/tags/%E4%B8%B2%E5%8F%A3/ + 2021-04-20T00:00:00+08:00 + + https://www.small09.top/posts/210328-linuxchangemacip/ + 2021-03-28T00:00:00+08:00 + + https://www.small09.top/tags/%E7%BD%91%E7%BB%9C/ + 2021-03-28T00:00:00+08:00 + + https://www.small09.top/posts/about_hugo_website/ + 2021-05-24T00:00:00+08:00 + + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + 2021-03-11T00:00:00+08:00 + + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + 2021-09-14T00:00:00+08:00 + + https://www.small09.top/tags/teamspeak/ + 2021-02-18T00:00:00+08:00 + + https://www.small09.top/tags/tools/ + 2021-02-18T00:00:00+08:00 + + https://www.small09.top/posts/210218-teamspeakwithdocker/ + 2021-02-18T00:00:00+08:00 + + https://www.small09.top/categories/ + + https://www.small09.top/about-card/ + + https://www.small09.top/about/ + + diff --git a/public/tags/clonezilla/index.html b/public/tags/clonezilla/index.html new file mode 100644 index 0000000..a8c6e5d --- /dev/null +++ b/public/tags/clonezilla/index.html @@ -0,0 +1,510 @@ + + + + + + + +Clonezilla – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/clonezilla/index.xml b/public/tags/clonezilla/index.xml new file mode 100644 index 0000000..31e44c6 --- /dev/null +++ b/public/tags/clonezilla/index.xml @@ -0,0 +1,104 @@ + + + 香风家的火柴盒 – Clonezilla + https://www.small09.top/tags/clonezilla/ + Recent content in Clonezilla on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Thu, 14 Jul 2022 13:05:51 +0800 + + + + + + + + + + + 从再生龙(clonezilla)镜像直接提取文件 + https://www.small09.top/posts/220714-unpackclonezillaimage/ + Thu, 14 Jul 2022 13:05:51 +0800 + + https://www.small09.top/posts/220714-unpackclonezillaimage/ + + + + <p>从clonezilla的镜像直接提取文件,免于先全盘全区恢复。</p> +<p>再生龙(clonezilla)是一个常用的Linux系统备份工具,适用于多个版本、多种CPU架构的电脑。</p> +<p>再生龙生成的镜像是比较特殊的格式,如果想要从里面提取文件,通常只能进行镜像恢复后进入系统提取。但是往往我们没有那么多硬盘来恢复系统。</p> +<p>再生龙的原理简单来说就是把硬盘分区使用partclone工具打包成img文件,然后使用gzip进行分卷压缩。我们只需要逆向操作,利用工具,把镜像解包挂载,直接在当前系统挂载,就可以得到原分区的文件了。</p> +<h2>挂载方法:<span class="hx:absolute hx:-mt-20" id="挂载方法"></span> + <a href="#%e6%8c%82%e8%bd%bd%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>1. 环境需求<span class="hx:absolute hx:-mt-20" id="1-环境需求"></span> + <a href="#1-%e7%8e%af%e5%a2%83%e9%9c%80%e6%b1%82" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>我们操作再生龙镜像时,最好是使用Linux环境。这个环境中,需要安装partclone(也就是再生龙生成镜像的核心工具)。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install partclone +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以Debian系为例</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>并且我们需要准备一个足够大的硬盘,用来存储镜像包和解包出的img文件。硬盘/分区的空间需要大于镜像本来的<strong>分区</strong>空间。</p> +<h3>2. 合并镜像<span class="hx:absolute hx:-mt-20" id="2-合并镜像"></span> + <a href="#2-%e5%90%88%e5%b9%b6%e9%95%9c%e5%83%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>再生龙默认情况下会把一个硬盘分区分按4GB的大小进行分包,通常以<code>gz.aa</code>、<code>gz.ab</code>等结尾,我们需要把这些包重新整合成一个gz包。</p> +<p>整合成gz包后,我们就可以解压gz来得到镜像img了。我们可以把两个步骤用流的方式写入一条命令,节省硬盘空间。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat sda1.ext4-ptcl-img.gz.* | gzip -d -c &gt; sda1.img</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这一步生成的img文件不能直接使用(google上也有说能用的,但是我试下来不能),需要进行一次转换。经过实际操作,这个文件所占用的空间是镜像原分区的大小,但是统计硬盘使用量时却只计算实际大小,希望了解的人可以帮忙解释一下。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>partclone.extfs -r -s sda1.img -o sda1-ex.img --restore_raw_file</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>当然上面两条指令也可以使用流的方式再次封装到一条,但是消耗的时间、失败后重新来过的时间,都会更长。</p> +<h2>3.挂载镜像<span class="hx:absolute hx:-mt-20" id="3挂载镜像"></span> + <a href="#3%e6%8c%82%e8%bd%bd%e9%95%9c%e5%83%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>将最后生成的img镜像挂载到我们的/mnt(或其他)目录,进入这个目录就可以看到原来全部的文件了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -o loop -t ext4 sda1-ex.img /mnt</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>最后我们只需要进入 <code>/mnt</code> 目录,就可以想直接操作硬盘一样把文件复制出来了。</p> + + + + + diff --git a/public/tags/deepin/index.html b/public/tags/deepin/index.html new file mode 100644 index 0000000..6d90487 --- /dev/null +++ b/public/tags/deepin/index.html @@ -0,0 +1,582 @@ + + + + + + + +Deepin – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    +
    +

    Deepin

    +
    +
    + +
    + +
    +
    +
    + + + + + + + + diff --git a/public/tags/deepin/index.xml b/public/tags/deepin/index.xml new file mode 100644 index 0000000..b96e85d --- /dev/null +++ b/public/tags/deepin/index.xml @@ -0,0 +1,623 @@ + + + 香风家的火柴盒 – Deepin + https://www.small09.top/tags/deepin/ + Recent content in Deepin on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 08 May 2023 14:43:41 +0800 + + + + + + + + + + + 讨论在Debian系操作系统中重新提取已安装的软件并重新打包 + https://www.small09.top/posts/230508-remake-deb/ + Mon, 08 May 2023 14:43:41 +0800 + + https://www.small09.top/posts/230508-remake-deb/ + + + + <p>Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用?</p> +<p>之前有一个需求,要从UOS里面提取一个软件,并且将其安装到另一台设备中。当时找了一圈都没有找到相关的提取教程。但是从逻辑上来讲,dpkg安装的软件会保存deb包的相关信息,所以理论上讲是可以提取出软件包的所有内容,然后我们重新打包的。</p> +<p>今天在一个机缘巧合下(其实是懒得研究dpkg原理,瞎猫撞上死耗子),发现了dpkg保存文件信息的地点,于是接下来可以展开畅想:提取已经安装的软件并重新打包。</p> +<h2>文件的保存地点<span class="hx:absolute hx:-mt-20" id="文件的保存地点"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%9a%84%e4%bf%9d%e5%ad%98%e5%9c%b0%e7%82%b9" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我在找fcitx有关的配置文件进行全盘搜索的时候,找到了一条目录<code>/usr/lib/dpkg-db/info</code>,这个目录里面保留了fcitx的&quot;conffiles&quot;, &ldquo;md5sums&rdquo;, &ldquo;postinst&rdquo;, &ldquo;prerm&quot;四个文件,以及一个list文件。恰好fcitx.deb解包出来的DEBIAN文件夹内也是这四个文件加上额外的control文件,。</p> +<p>那么合理猜测,dpkg安装软件的时候,不仅把软件本身的文件移动到对应位置,还把DEBIAN里面的信息和脚本也转移到<code>/usr/lib/dpkg-db/</code>里面保存了。</p> +<p>而<code>fcitx.list</code>文件中记录的是所有的文件及其路径。想必这里面就是全部的文件了。</p> +<p>至于control文件,则都被保存到了<code>/usr/lib/dpkg-db/status</code>这个文本文件中。</p> +<h2>流程设想<span class="hx:absolute hx:-mt-20" id="流程设想"></span> + <a href="#%e6%b5%81%e7%a8%8b%e8%ae%be%e6%83%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>既然deb包所有必要的文件都找到了,那么就可以尝试一下还原过程了。在<a href="https://www.small09.top/posts/211019-dpkg_package_guide/"target="_blank" rel="noopener">这篇文章</a>里我研究过deb包的组成、格式和打包方法。</p> +<p>比如对于软件包A:</p> +<ol> +<li> +<p>创建一个工作目录,并在其中新建<code>DEBIAN</code>目录。</p> +</li> +<li> +<p>将<code>/usr/lib/dpkg-db/info</code>中所有该软件的脚本和信息(除了A.list)转移到<code>DEBIAN</code>中,并重命名成规范名称。</p> +</li> +<li> +<p>将<code>/usr/lib/dpkg-db/status</code>中与软件A有关的部分提取出来,保存到<code>DEBIAN/control</code>文件中。</p> +</li> +<li> +<p>根据A.list文件,找到软件包含的所有文件,将其按原样整理到工作目录内。</p> +</li> +<li> +<p>使用<code>dpkg -b</code>指令重新打包。</p> +</li> +</ol> +<h2>局限性<span class="hx:absolute hx:-mt-20" id="局限性"></span> + <a href="#%e5%b1%80%e9%99%90%e6%80%a7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上毕竟只是设想,没有进行试验,所以估计会出现这些问题,希望有需要的人尝试后可以解决。</p> +<ul> +<li> +<p>提取信息的难度: 由于<code>/usr/lib/dpkg-db/status</code>文件是一个包含几乎所有deb信息的文本文件,体积大内容乱,对于自动化脚本来说有些难度。加上一些软件的衍生品会在软件名上直接加后缀,对于提取的脚本来讲可能造成混淆,提取出不必要的东西。</p> +</li> +<li> +<p>获取源文件的难度: 不排除一些软件安装时会删除、修改自身的一些文件,可能打包会后会缺失内容。一些软件安装过程中会进行手动配置从而造成内容改变,重做出来的软件包不一定完整。</p> +</li> +<li> +<p>签名问题: 我们打包的软件没有原作者的数字签名,可能无法安装,也可能时候安装官方软件时冲突。</p> +</li> +<li> +<p>这个方案只能处理普通的软件,一些deb打包源码,安装时进行编译的,应该是不能用这种方案复原的。(apt的deb-src源)</p> +</li> +</ul> +<p>最后,希望有大佬可以验证一下这个方案。</p> + + + + + Deepin/UOS文件管理器隐藏不需要的盘符 + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + Wed, 23 Feb 2022 09:18:55 +0800 + + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + + + + <p>Deepin/UOS 里处理文件管理器显示太多盘符的问题。</p> +<h2>现象<span class="hx:absolute hx:-mt-20" id="现象"></span> + <a href="#%e7%8e%b0%e8%b1%a1" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在<code>/etc/fstab</code>文件添加或删除“”并没有效果。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230924406.png" alt="image-20220223090517363" loading="lazy" /></p> +<h2>原理/原因<span class="hx:absolute hx:-mt-20" id="原理原因"></span> + <a href="#%e5%8e%9f%e7%90%86%e5%8e%9f%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。</p> +<h2>处理方法<span class="hx:absolute hx:-mt-20" id="处理方法"></span> + <a href="#%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li> +<p>打开<code>/etc/udev/rules.d</code>目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。</p> +</li> +<li> +<p>打开终端,使用root权限打开编辑</p> +</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo deepin-editor /etc/udev/rules.d/80-udisk2.rules</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ol start="3"> +<li> +<p><strong>打开新的终端</strong>,输入执行 <code>blkid -f</code> ,记录LABLE、UUID两列</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230928320.png" alt="截图录屏_选择区域_20220223090208" loading="lazy" /></p> +</li> +<li> +<p>对比<code>80-udisks2.rules</code>的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。</p> +<p>(#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换)</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230927888.png" alt="截图录屏_选择区域_20220223090606" loading="lazy" /></p> +</li> +<li> +<p>保存文件并重启计算机,即可看到效果。</p> +</li> +<li> +<p>该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。</p> +</li> +</ol> + + + + + DEB打包方法简介 + https://www.small09.top/posts/211019-dpkg_package_guide/ + Tue, 19 Oct 2021 10:12:23 +0800 + + https://www.small09.top/posts/211019-dpkg_package_guide/ + + + + <p>简单介绍deb打包的方法,包括目录树格式和打包注意事项。</p> +<h2>文件结构<span class="hx:absolute hx:-mt-20" id="文件结构"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%bb%93%e6%9e%84" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录<code>/</code>。一个工作空间的目录树如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code> PackageDIR + ├─DEBIAN + ├─opt + │ └─kushidou-touchscreen + │ └─desktops + ├─usr + │ ├─bin + │ └─share + └─var</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中,<code>DEBIAN</code>目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。</p> +<h2>DEBIAN<span class="hx:absolute hx:-mt-20" id="debian"></span> + <a href="#debian" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>DEBIAN</code>目录下有一个control文件和至多四个脚本文件。</p> +<h3>control<span class="hx:absolute hx:-mt-20" id="control"></span> + <a href="#control" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>control</code>文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。</p> +<table> + <thead> + <tr> + <th>NAME</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td>Package</td> + <td>软件名,<code>dpkg -l</code> 列出的名字</td> + </tr> + <tr> + <td>Version</td> + <td>版本号-修订号</td> + </tr> + <tr> + <td>Section</td> + <td>软件类型,比如mail, test, x11等</td> + </tr> + <tr> + <td>Priority</td> + <td>优先级/重要性,比如optional 可选的</td> + </tr> + <tr> + <td>Architecture</td> + <td>软件架构,比如x86, arm64</td> + </tr> + <tr> + <td>Depends</td> + <td>依赖环境,用 <code>,</code> 隔开,用<code>(&gt;=&lt; )</code>指定版本号</td> + </tr> + <tr> + <td>Suggests</td> + <td>推荐安装,指非必要依赖,可以提供更多功能</td> + </tr> + <tr> + <td>Maintainer</td> + <td>作者 &lt;邮箱或者其他联系方式&gt;</td> + </tr> + <tr> + <td>Descritprion</td> + <td>简介,可以换行书写</td> + </tr> + </tbody> +</table> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20211019101901.png" alt="image-20211019094048011" loading="lazy" /></p> +<h3>额外脚本<span class="hx:absolute hx:-mt-20" id="额外脚本"></span> + <a href="#%e9%a2%9d%e5%a4%96%e8%84%9a%e6%9c%ac" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>DEBIAN</code>目录下之多放置四个可执行脚本,包括<strong>安装前<code>preinst</code></strong>、<strong>安装后<code>postinst</code></strong>、<strong>卸载前<code>prerm</code></strong>、<strong>卸载前<code>postrm</code></strong>,四个脚本都需要以<code>#!/bin/bash</code>开头,内容与普通脚本无异,文件名不需要后缀。</p> +<p>这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以<code>root</code>身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。</p> +<p>此外<code>postinst</code>可以用来启动已安装的程序,<code>prerm</code>常用来停止正在运行的程序。</p> +<h3>md5sums<span class="hx:absolute hx:-mt-20" id="md5sums"></span> + <a href="#md5sums" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。</p> +<h2>打包<span class="hx:absolute hx:-mt-20" id="打包"></span> + <a href="#%e6%89%93%e5%8c%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>所有文件配置完成后,进入工作空间上级目录,执行指令</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dpkg -b ./PackageDIR packagename.deb</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>deb包命名的一般规则为 <strong>软件名_版本-修订号_架构.deb</strong></p> +<h2>其他信息<span class="hx:absolute hx:-mt-20" id="其他信息"></span> + <a href="#%e5%85%b6%e4%bb%96%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。</p> + + + + + 使用dd指令备份和恢复Linux系统(硬盘级) + https://www.small09.top/posts/210914-linuxbackupdd/ + Tue, 14 Sep 2021 13:51:16 +0800 + + https://www.small09.top/posts/210914-linuxbackupdd/ + + + + <p>害,谁不是个DD (誰でも 大好き)了呢!</p> +<p>但是这玩意儿太慢了。。。</p> +<h2>起因<span class="hx:absolute hx:-mt-20" id="起因"></span> + <a href="#%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。</p> +<h2>DD说明<span class="hx:absolute hx:-mt-20" id="dd说明"></span> + <a href="#dd%e8%af%b4%e6%98%8e" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。</p> +<p>dd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。</p> +<p>实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。</p> +<h2>DD备份<span class="hx:absolute hx:-mt-20" id="dd备份"></span> + <a href="#dd%e5%a4%87%e4%bb%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda of<span style="color:#f92672">=</span>/dev/sdb <span style="color:#f92672">=</span>&gt; 备份整个磁盘到另外一个磁盘 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>sda.img <span style="color:#f92672">=</span>&gt; 备份整个磁盘为某个文件 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 备份并且压缩 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 指定块大小,备份并压缩(据说能提速)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>DD复原<span class="hx:absolute hx:-mt-20" id="dd复原"></span> + <a href="#dd%e5%a4%8d%e5%8e%9f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从另一个磁盘恢复回来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从压缩的文件恢复出来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M <span style="color:#f92672">=</span>&gt;前面指定了块大小,这里也需要</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>本文参考(抄)了以下内容:</p> +<p><a href="http://vpsxyz.com/archives/87"target="_blank" rel="noopener">Linux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像</a></p> +<p><a href="https://blog.csdn.net/weixin_33971130/article/details/85662628"target="_blank" rel="noopener">linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)</a></p> +<p>还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ )</p> + + + + + Linux处理多触屏的终极解决方案 + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + Tue, 24 Aug 2021 00:00:00 +0800 + + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + + + + <p>一般Linux操作系统接入多个触摸屏的最终解!</p> +<p>从<a href="https://www.small09.top/posts/210311-multitouchscreeninlinux/"target="_blank" rel="noopener">21年3月中旬开始</a>,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。</p> +<p><em>重要:</em> 本文全文基于软件<code>xinput</code></p> +<p><em>更重要:</em> 如简介所说,仍有一种方法无法对应,具体为<code>【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】</code>,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。</p> +<h2>0、核心:xinput<span class="hx:absolute hx:-mt-20" id="0核心xinput"></span> + <a href="#0%e6%a0%b8%e5%bf%83xinput" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)</p> +<blockquote> + <p>xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级</p> +<p>xinput list &ndash;id-only : 仅仅列出设备ID(序号,每次开机都可能改变)</p> +<p>xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕</p> +<p>xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。</p> + +</blockquote> +<h2>1、简约:两个完全不同的触摸显示器<span class="hx:absolute hx:-mt-20" id="1简约两个完全不同的触摸显示器"></span> + <a href="#1%e7%ae%80%e7%ba%a6%e4%b8%a4%e4%b8%aa%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c%e7%9a%84%e8%a7%a6%e6%91%b8%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>xinput map-to-output &quot;$DEVICE NAME A&quot; PORT-A</code></p> +<p>这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。</p> +<p>首先通过xinput查看所有的输入设备:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>~$ xinput +</span></span><span style="display:flex;"><span>┌ Virtual core pointer id<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">[</span>master pointer <span style="color:#f92672">(</span>3<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK TLITEK-TP id<span style="color:#f92672">=</span><span style="color:#ae81ff">9</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK Multi-Touch id<span style="color:#f92672">=</span><span style="color:#ae81ff">7</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">(</span>下略<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>可以看到<code>ILITEK ILITEK-TP</code>和<code>ILITEK Multi-Touch</code>显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:</p> +<p><code>xinput map-to-output &quot;ILITEK ILITEK-TP&quot; HDMI-0</code></p> +<p><code>xinput map-to-output &quot;ILITEK Multi-Touch&quot; HDMI-1</code></p> +<p>这种方式的要点是1.必须带英文<code>&quot;&quot;</code>,2.引号内名称须完整。<code>map-to-output</code>所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。</p> +<h2>2、复杂但通用:VID:PID不同的显示器<span class="hx:absolute hx:-mt-20" id="2复杂但通用vidpid不同的显示器"></span> + <a href="#2%e5%a4%8d%e6%9d%82%e4%bd%86%e9%80%9a%e7%94%a8vidpid%e4%b8%8d%e5%90%8c%e7%9a%84%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。</p> +<p>通过<code>xinput list-props [id]</code>可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。<code>list-props</code>的结果示例如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#f92672">(</span>后续再补<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Device Product ID</code>一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA<span style="color:#f92672">=</span>222a +</span></span><span style="display:flex;"><span>PIDA<span style="color:#f92672">=</span><span style="color:#ae81ff">0001</span> +</span></span><span style="display:flex;"><span>PORTA<span style="color:#f92672">=</span>VGA-0 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 或者这里直接填写Device Product ID里面的结果,先VID后PID。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 字母O,不是数字0,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$VIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span>PIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$PIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># =========自动处理步骤,请勿随意修改==========</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 根据设备ID取其序号</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3、屏映射,多屏重复本段</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$VIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$PIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> xinput map-to-output $I $PORTA +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span>exit <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这个脚本使用<code>xinput list --id-only</code>仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用<code>map-to-output</code>进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。</p> +<h2>3、[2]的进阶版<span class="hx:absolute hx:-mt-20" id="32的进阶版"></span> + <a href="#32%e7%9a%84%e8%bf%9b%e9%98%b6%e7%89%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。</p> +<p>在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是<code>pyudev</code>库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装<code>python3+pyudev</code>,并且使用desktop文件在开机时启动,即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/python3</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- coding: UTF-8 --</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- Author: Blues --</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pyudev +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>context <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Context() +</span></span><span style="display:flex;"><span>monitor <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Monitor<span style="color:#f92672">.</span>from_netlink(context) +</span></span><span style="display:flex;"><span>monitor<span style="color:#f92672">.</span>filter_by(subsystem<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;hid&#39;</span>) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> device <span style="color:#f92672">in</span> iter(monitor<span style="color:#f92672">.</span>poll, <span style="color:#66d9ef">None</span>): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>driver <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;hid-multitouch&#34;</span>: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;add&#34;</span> <span style="color:#f92672">or</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;change&#34;</span>: +</span></span><span style="display:flex;"><span> os<span style="color:#f92672">.</span>system(<span style="color:#e6db74">&#34;/var/my-touchscreen/remap-screen&#34;</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照<em>设备名、VID、UID、物理接口</em>四大特征,自动映射到对应的显示屏上了。</p> + + + + + deepin和UOS,快捷方式,开机自启动 + https://www.small09.top/posts/210429-deepinautorun/ + Thu, 29 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210429-deepinautorun/ + + + + <p>修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。</p> +<h2>桌面快捷方式<span class="hx:absolute hx:-mt-20" id="桌面快捷方式"></span> + <a href="#%e6%a1%8c%e9%9d%a2%e5%bf%ab%e6%8d%b7%e6%96%b9%e5%bc%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>deepin的桌面快捷方式是以<code>*.desktop</code>文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>[Desktop Entry] +Name=ddd +Exec=&#34;/usr/sbin/xx.sh&#34; +Type=Application</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Desktop Entry</code>表示声明该文件为快捷方式文件。<code>Name</code>是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。<code>Exec</code>是程序的启动脚本,或者启动指令。<code>Type</code>是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。</p> +<p>把这个文件放在<code>~/Desktop</code>,就可以看到桌面上多了一个快捷方式了。</p> +<h2>开始菜单<span class="hx:absolute hx:-mt-20" id="开始菜单"></span> + <a href="#%e5%bc%80%e5%a7%8b%e8%8f%9c%e5%8d%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications</code>,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。</p> +<h2>开机自动执行<span class="hx:absolute hx:-mt-20" id="开机自动执行"></span> + <a href="#%e5%bc%80%e6%9c%ba%e8%87%aa%e5%8a%a8%e6%89%a7%e8%a1%8c" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>UOS/Deepin和Ubuntu<span class="hx:absolute hx:-mt-20" id="uosdeepin和ubuntu"></span> + <a href="#uosdeepin%e5%92%8cubuntu" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>cp ~/Desktop/刚刚的.desktop ~/.config/autostart</code></p> +<p>需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(<code>lightdm.service</code>)执行,导致error从而无法进入桌面,将脚本放进<code>/etc/profile.d</code>之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。</p> +<p>经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在<code>~/.config/</code>下手动创建一个<code>autostart</code>目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。</p> +<h3>麒麟和其他使用X11桌面的Linux<span class="hx:absolute hx:-mt-20" id="麒麟和其他使用x11桌面的linux"></span> + <a href="#%e9%ba%92%e9%ba%9f%e5%92%8c%e5%85%b6%e4%bb%96%e4%bd%bf%e7%94%a8x11%e6%a1%8c%e9%9d%a2%e7%9a%84linux" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>本条目包含UOS和Ubuntu、deepin系统</p> +<p>自启动目录为<code>/etc/xdg/autostart</code>,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。</p> + + + + + Linux多显示器触屏调试与暂行解决方案 + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + + + + <p>由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。</p> +<p><em>2021-03-22更新部分内容</em> +<em>2021-04-28再次更新内容</em></p> +<p><strong>2021-09-14 文章已有新版本,请访问《<a href="https://www.small09.top/posts/210824-linuxtouchscreenfin/"target="_blank" rel="noopener">Linux处理多触屏的终极解决方案</a>》</strong></p> +<h2>问题来源<span class="hx:absolute hx:-mt-20" id="问题来源"></span> + <a href="#%e9%97%ae%e9%a2%98%e6%9d%a5%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为</p> +<p><code>xinput map-to-output $触屏设备id $显示屏名称</code></p> +<blockquote> + <p>2021-04-28更新</p> +<p><code>xinput map-to-output</code>指令支持触摸屏名称作为参数,比如我通过<code>xinput</code>指令看到有一个触摸屏的设备名称为<code>ILITEK XXXX</code>,那么我映射的时候直接输入</p> +<blockquote> + <p><code>xinput map-to-output &quot;ILITEK XXX&quot; HDMI-1</code></p> + +</blockquote> + +</blockquote> +<h2>尝试解决<span class="hx:absolute hx:-mt-20" id="尝试解决"></span> + <a href="#%e5%b0%9d%e8%af%95%e8%a7%a3%e5%86%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ul> +<li>首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。</li> +<li><del>然后<code>xinput list-props $id</code>查看设备的VID,这个在后续自动化处理有用。</del></li> +<li><code>xrandr</code>查看<code>$显示器名称</code></li> +</ul> +<h2>自动化处理<span class="hx:absolute hx:-mt-20" id="自动化处理"></span> + <a href="#%e8%87%aa%e5%8a%a8%e5%8c%96%e5%a4%84%e7%90%86" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 2021-04-28更新:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 本部分后面的都可以不看了!</span> +</span></span><span style="display:flex;"><span>xinput map-to-output <span style="color:#e6db74">&#34;DEVICE_NAME&#34;</span> DISPLAY +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><strong>仅作归档!下面这些方法过于繁琐!</strong></p> +<p>利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。<code>bash this.sh VID1 VID2 ScreenID</code></p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。</span> +</span></span><span style="display:flex;"><span>MYID_FIRST<span style="color:#f92672">=</span>$1 +</span></span><span style="display:flex;"><span>MYID_SECOND<span style="color:#f92672">=</span>$2 +</span></span><span style="display:flex;"><span>OUT_PUT<span style="color:#f92672">=</span>$3 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#[1]</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> MY_INDEX<span style="color:#f92672">=</span>$I +</span></span><span style="display:flex;"><span> break; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>xinput map-to-output $MY_INDEX $OUT_PUT +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI2</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI1</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput | grep -iw DEVICE_NAME<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID#*id=<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID%% *<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>xinput map-to-output $TOUCH_ID SCREEN_NAME</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),<del>使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id</del>。</p> +<p>经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在<code>~/.bashrc</code>中,这样只需要打开一次终端,就可以实现屏幕重新映射。</p> +<h2>后续问题<span class="hx:absolute hx:-mt-20" id="后续问题"></span> + <a href="#%e5%90%8e%e7%bb%ad%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li>当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。</li> +<li>当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数</li> +<li>如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。</li> +</ol> +<h2>触屏校准(如果需要)<span class="hx:absolute hx:-mt-20" id="触屏校准如果需要"></span> + <a href="#%e8%a7%a6%e5%b1%8f%e6%a0%a1%e5%87%86%e5%a6%82%e6%9e%9c%e9%9c%80%e8%a6%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>网上说的利用校准来区分多触摸屏的方法是<strong>完全错误</strong>的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)</p> +<ul> +<li><code>sudo apt install xinput-calibrator</code>,需要更新源</li> +<li><code>sudo xinput_calibrator</code></li> +<li>将校准信息保存到<code>/usr/share/X11/xorg.conf.d/10-evdev.conf</code>的&quot;Driver evdev”下面一行,重启生效。</li> +</ul> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077</p> + + + + + diff --git a/public/tags/deepin/uos/index.html b/public/tags/deepin/uos/index.html new file mode 100644 index 0000000..c15e6a8 --- /dev/null +++ b/public/tags/deepin/uos/index.html @@ -0,0 +1,570 @@ + + + + + + + +Deepin/UOS – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/deepin/uos/index.xml b/public/tags/deepin/uos/index.xml new file mode 100644 index 0000000..2dffa78 --- /dev/null +++ b/public/tags/deepin/uos/index.xml @@ -0,0 +1,566 @@ + + + 香风家的火柴盒 – Deepin/UOS + https://www.small09.top/tags/deepin/uos/ + Recent content in Deepin/UOS on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Wed, 23 Feb 2022 09:18:55 +0800 + + + + + + + + + + + Deepin/UOS文件管理器隐藏不需要的盘符 + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + Wed, 23 Feb 2022 09:18:55 +0800 + + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + + + + <p>Deepin/UOS 里处理文件管理器显示太多盘符的问题。</p> +<h2>现象<span class="hx:absolute hx:-mt-20" id="现象"></span> + <a href="#%e7%8e%b0%e8%b1%a1" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在<code>/etc/fstab</code>文件添加或删除“”并没有效果。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230924406.png" alt="image-20220223090517363" loading="lazy" /></p> +<h2>原理/原因<span class="hx:absolute hx:-mt-20" id="原理原因"></span> + <a href="#%e5%8e%9f%e7%90%86%e5%8e%9f%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。</p> +<h2>处理方法<span class="hx:absolute hx:-mt-20" id="处理方法"></span> + <a href="#%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li> +<p>打开<code>/etc/udev/rules.d</code>目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。</p> +</li> +<li> +<p>打开终端,使用root权限打开编辑</p> +</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo deepin-editor /etc/udev/rules.d/80-udisk2.rules</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ol start="3"> +<li> +<p><strong>打开新的终端</strong>,输入执行 <code>blkid -f</code> ,记录LABLE、UUID两列</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230928320.png" alt="截图录屏_选择区域_20220223090208" loading="lazy" /></p> +</li> +<li> +<p>对比<code>80-udisks2.rules</code>的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。</p> +<p>(#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换)</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230927888.png" alt="截图录屏_选择区域_20220223090606" loading="lazy" /></p> +</li> +<li> +<p>保存文件并重启计算机,即可看到效果。</p> +</li> +<li> +<p>该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。</p> +</li> +</ol> + + + + + DEB打包方法简介 + https://www.small09.top/posts/211019-dpkg_package_guide/ + Tue, 19 Oct 2021 10:12:23 +0800 + + https://www.small09.top/posts/211019-dpkg_package_guide/ + + + + <p>简单介绍deb打包的方法,包括目录树格式和打包注意事项。</p> +<h2>文件结构<span class="hx:absolute hx:-mt-20" id="文件结构"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%bb%93%e6%9e%84" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录<code>/</code>。一个工作空间的目录树如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code> PackageDIR + ├─DEBIAN + ├─opt + │ └─kushidou-touchscreen + │ └─desktops + ├─usr + │ ├─bin + │ └─share + └─var</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中,<code>DEBIAN</code>目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。</p> +<h2>DEBIAN<span class="hx:absolute hx:-mt-20" id="debian"></span> + <a href="#debian" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>DEBIAN</code>目录下有一个control文件和至多四个脚本文件。</p> +<h3>control<span class="hx:absolute hx:-mt-20" id="control"></span> + <a href="#control" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>control</code>文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。</p> +<table> + <thead> + <tr> + <th>NAME</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td>Package</td> + <td>软件名,<code>dpkg -l</code> 列出的名字</td> + </tr> + <tr> + <td>Version</td> + <td>版本号-修订号</td> + </tr> + <tr> + <td>Section</td> + <td>软件类型,比如mail, test, x11等</td> + </tr> + <tr> + <td>Priority</td> + <td>优先级/重要性,比如optional 可选的</td> + </tr> + <tr> + <td>Architecture</td> + <td>软件架构,比如x86, arm64</td> + </tr> + <tr> + <td>Depends</td> + <td>依赖环境,用 <code>,</code> 隔开,用<code>(&gt;=&lt; )</code>指定版本号</td> + </tr> + <tr> + <td>Suggests</td> + <td>推荐安装,指非必要依赖,可以提供更多功能</td> + </tr> + <tr> + <td>Maintainer</td> + <td>作者 &lt;邮箱或者其他联系方式&gt;</td> + </tr> + <tr> + <td>Descritprion</td> + <td>简介,可以换行书写</td> + </tr> + </tbody> +</table> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20211019101901.png" alt="image-20211019094048011" loading="lazy" /></p> +<h3>额外脚本<span class="hx:absolute hx:-mt-20" id="额外脚本"></span> + <a href="#%e9%a2%9d%e5%a4%96%e8%84%9a%e6%9c%ac" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>DEBIAN</code>目录下之多放置四个可执行脚本,包括<strong>安装前<code>preinst</code></strong>、<strong>安装后<code>postinst</code></strong>、<strong>卸载前<code>prerm</code></strong>、<strong>卸载前<code>postrm</code></strong>,四个脚本都需要以<code>#!/bin/bash</code>开头,内容与普通脚本无异,文件名不需要后缀。</p> +<p>这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以<code>root</code>身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。</p> +<p>此外<code>postinst</code>可以用来启动已安装的程序,<code>prerm</code>常用来停止正在运行的程序。</p> +<h3>md5sums<span class="hx:absolute hx:-mt-20" id="md5sums"></span> + <a href="#md5sums" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。</p> +<h2>打包<span class="hx:absolute hx:-mt-20" id="打包"></span> + <a href="#%e6%89%93%e5%8c%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>所有文件配置完成后,进入工作空间上级目录,执行指令</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dpkg -b ./PackageDIR packagename.deb</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>deb包命名的一般规则为 <strong>软件名_版本-修订号_架构.deb</strong></p> +<h2>其他信息<span class="hx:absolute hx:-mt-20" id="其他信息"></span> + <a href="#%e5%85%b6%e4%bb%96%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。</p> + + + + + 使用dd指令备份和恢复Linux系统(硬盘级) + https://www.small09.top/posts/210914-linuxbackupdd/ + Tue, 14 Sep 2021 13:51:16 +0800 + + https://www.small09.top/posts/210914-linuxbackupdd/ + + + + <p>害,谁不是个DD (誰でも 大好き)了呢!</p> +<p>但是这玩意儿太慢了。。。</p> +<h2>起因<span class="hx:absolute hx:-mt-20" id="起因"></span> + <a href="#%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。</p> +<h2>DD说明<span class="hx:absolute hx:-mt-20" id="dd说明"></span> + <a href="#dd%e8%af%b4%e6%98%8e" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。</p> +<p>dd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。</p> +<p>实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。</p> +<h2>DD备份<span class="hx:absolute hx:-mt-20" id="dd备份"></span> + <a href="#dd%e5%a4%87%e4%bb%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda of<span style="color:#f92672">=</span>/dev/sdb <span style="color:#f92672">=</span>&gt; 备份整个磁盘到另外一个磁盘 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>sda.img <span style="color:#f92672">=</span>&gt; 备份整个磁盘为某个文件 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 备份并且压缩 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 指定块大小,备份并压缩(据说能提速)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>DD复原<span class="hx:absolute hx:-mt-20" id="dd复原"></span> + <a href="#dd%e5%a4%8d%e5%8e%9f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从另一个磁盘恢复回来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从压缩的文件恢复出来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M <span style="color:#f92672">=</span>&gt;前面指定了块大小,这里也需要</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>本文参考(抄)了以下内容:</p> +<p><a href="http://vpsxyz.com/archives/87"target="_blank" rel="noopener">Linux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像</a></p> +<p><a href="https://blog.csdn.net/weixin_33971130/article/details/85662628"target="_blank" rel="noopener">linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)</a></p> +<p>还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ )</p> + + + + + Linux处理多触屏的终极解决方案 + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + Tue, 24 Aug 2021 00:00:00 +0800 + + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + + + + <p>一般Linux操作系统接入多个触摸屏的最终解!</p> +<p>从<a href="https://www.small09.top/posts/210311-multitouchscreeninlinux/"target="_blank" rel="noopener">21年3月中旬开始</a>,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。</p> +<p><em>重要:</em> 本文全文基于软件<code>xinput</code></p> +<p><em>更重要:</em> 如简介所说,仍有一种方法无法对应,具体为<code>【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】</code>,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。</p> +<h2>0、核心:xinput<span class="hx:absolute hx:-mt-20" id="0核心xinput"></span> + <a href="#0%e6%a0%b8%e5%bf%83xinput" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)</p> +<blockquote> + <p>xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级</p> +<p>xinput list &ndash;id-only : 仅仅列出设备ID(序号,每次开机都可能改变)</p> +<p>xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕</p> +<p>xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。</p> + +</blockquote> +<h2>1、简约:两个完全不同的触摸显示器<span class="hx:absolute hx:-mt-20" id="1简约两个完全不同的触摸显示器"></span> + <a href="#1%e7%ae%80%e7%ba%a6%e4%b8%a4%e4%b8%aa%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c%e7%9a%84%e8%a7%a6%e6%91%b8%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>xinput map-to-output &quot;$DEVICE NAME A&quot; PORT-A</code></p> +<p>这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。</p> +<p>首先通过xinput查看所有的输入设备:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>~$ xinput +</span></span><span style="display:flex;"><span>┌ Virtual core pointer id<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">[</span>master pointer <span style="color:#f92672">(</span>3<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK TLITEK-TP id<span style="color:#f92672">=</span><span style="color:#ae81ff">9</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK Multi-Touch id<span style="color:#f92672">=</span><span style="color:#ae81ff">7</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">(</span>下略<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>可以看到<code>ILITEK ILITEK-TP</code>和<code>ILITEK Multi-Touch</code>显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:</p> +<p><code>xinput map-to-output &quot;ILITEK ILITEK-TP&quot; HDMI-0</code></p> +<p><code>xinput map-to-output &quot;ILITEK Multi-Touch&quot; HDMI-1</code></p> +<p>这种方式的要点是1.必须带英文<code>&quot;&quot;</code>,2.引号内名称须完整。<code>map-to-output</code>所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。</p> +<h2>2、复杂但通用:VID:PID不同的显示器<span class="hx:absolute hx:-mt-20" id="2复杂但通用vidpid不同的显示器"></span> + <a href="#2%e5%a4%8d%e6%9d%82%e4%bd%86%e9%80%9a%e7%94%a8vidpid%e4%b8%8d%e5%90%8c%e7%9a%84%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。</p> +<p>通过<code>xinput list-props [id]</code>可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。<code>list-props</code>的结果示例如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#f92672">(</span>后续再补<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Device Product ID</code>一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA<span style="color:#f92672">=</span>222a +</span></span><span style="display:flex;"><span>PIDA<span style="color:#f92672">=</span><span style="color:#ae81ff">0001</span> +</span></span><span style="display:flex;"><span>PORTA<span style="color:#f92672">=</span>VGA-0 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 或者这里直接填写Device Product ID里面的结果,先VID后PID。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 字母O,不是数字0,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$VIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span>PIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$PIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># =========自动处理步骤,请勿随意修改==========</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 根据设备ID取其序号</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3、屏映射,多屏重复本段</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$VIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$PIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> xinput map-to-output $I $PORTA +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span>exit <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这个脚本使用<code>xinput list --id-only</code>仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用<code>map-to-output</code>进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。</p> +<h2>3、[2]的进阶版<span class="hx:absolute hx:-mt-20" id="32的进阶版"></span> + <a href="#32%e7%9a%84%e8%bf%9b%e9%98%b6%e7%89%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。</p> +<p>在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是<code>pyudev</code>库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装<code>python3+pyudev</code>,并且使用desktop文件在开机时启动,即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/python3</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- coding: UTF-8 --</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- Author: Blues --</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pyudev +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>context <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Context() +</span></span><span style="display:flex;"><span>monitor <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Monitor<span style="color:#f92672">.</span>from_netlink(context) +</span></span><span style="display:flex;"><span>monitor<span style="color:#f92672">.</span>filter_by(subsystem<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;hid&#39;</span>) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> device <span style="color:#f92672">in</span> iter(monitor<span style="color:#f92672">.</span>poll, <span style="color:#66d9ef">None</span>): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>driver <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;hid-multitouch&#34;</span>: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;add&#34;</span> <span style="color:#f92672">or</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;change&#34;</span>: +</span></span><span style="display:flex;"><span> os<span style="color:#f92672">.</span>system(<span style="color:#e6db74">&#34;/var/my-touchscreen/remap-screen&#34;</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照<em>设备名、VID、UID、物理接口</em>四大特征,自动映射到对应的显示屏上了。</p> + + + + + deepin和UOS,快捷方式,开机自启动 + https://www.small09.top/posts/210429-deepinautorun/ + Thu, 29 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210429-deepinautorun/ + + + + <p>修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。</p> +<h2>桌面快捷方式<span class="hx:absolute hx:-mt-20" id="桌面快捷方式"></span> + <a href="#%e6%a1%8c%e9%9d%a2%e5%bf%ab%e6%8d%b7%e6%96%b9%e5%bc%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>deepin的桌面快捷方式是以<code>*.desktop</code>文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>[Desktop Entry] +Name=ddd +Exec=&#34;/usr/sbin/xx.sh&#34; +Type=Application</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Desktop Entry</code>表示声明该文件为快捷方式文件。<code>Name</code>是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。<code>Exec</code>是程序的启动脚本,或者启动指令。<code>Type</code>是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。</p> +<p>把这个文件放在<code>~/Desktop</code>,就可以看到桌面上多了一个快捷方式了。</p> +<h2>开始菜单<span class="hx:absolute hx:-mt-20" id="开始菜单"></span> + <a href="#%e5%bc%80%e5%a7%8b%e8%8f%9c%e5%8d%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications</code>,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。</p> +<h2>开机自动执行<span class="hx:absolute hx:-mt-20" id="开机自动执行"></span> + <a href="#%e5%bc%80%e6%9c%ba%e8%87%aa%e5%8a%a8%e6%89%a7%e8%a1%8c" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>UOS/Deepin和Ubuntu<span class="hx:absolute hx:-mt-20" id="uosdeepin和ubuntu"></span> + <a href="#uosdeepin%e5%92%8cubuntu" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>cp ~/Desktop/刚刚的.desktop ~/.config/autostart</code></p> +<p>需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(<code>lightdm.service</code>)执行,导致error从而无法进入桌面,将脚本放进<code>/etc/profile.d</code>之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。</p> +<p>经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在<code>~/.config/</code>下手动创建一个<code>autostart</code>目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。</p> +<h3>麒麟和其他使用X11桌面的Linux<span class="hx:absolute hx:-mt-20" id="麒麟和其他使用x11桌面的linux"></span> + <a href="#%e9%ba%92%e9%ba%9f%e5%92%8c%e5%85%b6%e4%bb%96%e4%bd%bf%e7%94%a8x11%e6%a1%8c%e9%9d%a2%e7%9a%84linux" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>本条目包含UOS和Ubuntu、deepin系统</p> +<p>自启动目录为<code>/etc/xdg/autostart</code>,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。</p> + + + + + Linux多显示器触屏调试与暂行解决方案 + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + + + + <p>由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。</p> +<p><em>2021-03-22更新部分内容</em> +<em>2021-04-28再次更新内容</em></p> +<p><strong>2021-09-14 文章已有新版本,请访问《<a href="https://www.small09.top/posts/210824-linuxtouchscreenfin/"target="_blank" rel="noopener">Linux处理多触屏的终极解决方案</a>》</strong></p> +<h2>问题来源<span class="hx:absolute hx:-mt-20" id="问题来源"></span> + <a href="#%e9%97%ae%e9%a2%98%e6%9d%a5%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为</p> +<p><code>xinput map-to-output $触屏设备id $显示屏名称</code></p> +<blockquote> + <p>2021-04-28更新</p> +<p><code>xinput map-to-output</code>指令支持触摸屏名称作为参数,比如我通过<code>xinput</code>指令看到有一个触摸屏的设备名称为<code>ILITEK XXXX</code>,那么我映射的时候直接输入</p> +<blockquote> + <p><code>xinput map-to-output &quot;ILITEK XXX&quot; HDMI-1</code></p> + +</blockquote> + +</blockquote> +<h2>尝试解决<span class="hx:absolute hx:-mt-20" id="尝试解决"></span> + <a href="#%e5%b0%9d%e8%af%95%e8%a7%a3%e5%86%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ul> +<li>首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。</li> +<li><del>然后<code>xinput list-props $id</code>查看设备的VID,这个在后续自动化处理有用。</del></li> +<li><code>xrandr</code>查看<code>$显示器名称</code></li> +</ul> +<h2>自动化处理<span class="hx:absolute hx:-mt-20" id="自动化处理"></span> + <a href="#%e8%87%aa%e5%8a%a8%e5%8c%96%e5%a4%84%e7%90%86" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 2021-04-28更新:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 本部分后面的都可以不看了!</span> +</span></span><span style="display:flex;"><span>xinput map-to-output <span style="color:#e6db74">&#34;DEVICE_NAME&#34;</span> DISPLAY +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><strong>仅作归档!下面这些方法过于繁琐!</strong></p> +<p>利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。<code>bash this.sh VID1 VID2 ScreenID</code></p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。</span> +</span></span><span style="display:flex;"><span>MYID_FIRST<span style="color:#f92672">=</span>$1 +</span></span><span style="display:flex;"><span>MYID_SECOND<span style="color:#f92672">=</span>$2 +</span></span><span style="display:flex;"><span>OUT_PUT<span style="color:#f92672">=</span>$3 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#[1]</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> MY_INDEX<span style="color:#f92672">=</span>$I +</span></span><span style="display:flex;"><span> break; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>xinput map-to-output $MY_INDEX $OUT_PUT +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI2</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI1</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput | grep -iw DEVICE_NAME<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID#*id=<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID%% *<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>xinput map-to-output $TOUCH_ID SCREEN_NAME</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),<del>使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id</del>。</p> +<p>经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在<code>~/.bashrc</code>中,这样只需要打开一次终端,就可以实现屏幕重新映射。</p> +<h2>后续问题<span class="hx:absolute hx:-mt-20" id="后续问题"></span> + <a href="#%e5%90%8e%e7%bb%ad%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li>当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。</li> +<li>当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数</li> +<li>如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。</li> +</ol> +<h2>触屏校准(如果需要)<span class="hx:absolute hx:-mt-20" id="触屏校准如果需要"></span> + <a href="#%e8%a7%a6%e5%b1%8f%e6%a0%a1%e5%87%86%e5%a6%82%e6%9e%9c%e9%9c%80%e8%a6%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>网上说的利用校准来区分多触摸屏的方法是<strong>完全错误</strong>的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)</p> +<ul> +<li><code>sudo apt install xinput-calibrator</code>,需要更新源</li> +<li><code>sudo xinput_calibrator</code></li> +<li>将校准信息保存到<code>/usr/share/X11/xorg.conf.d/10-evdev.conf</code>的&quot;Driver evdev”下面一行,重启生效。</li> +</ul> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077</p> + + + + + diff --git a/public/tags/docker/index.html b/public/tags/docker/index.html new file mode 100644 index 0000000..3e54839 --- /dev/null +++ b/public/tags/docker/index.html @@ -0,0 +1,522 @@ + + + + + + + +Docker – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/docker/index.xml b/public/tags/docker/index.xml new file mode 100644 index 0000000..9e3a30a --- /dev/null +++ b/public/tags/docker/index.xml @@ -0,0 +1,246 @@ + + + 香风家的火柴盒 – Docker + https://www.small09.top/tags/docker/ + Recent content in Docker on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 25 Dec 2023 09:06:25 +0800 + + + + + + + + + + + docker简单笔记 + https://www.small09.top/posts/231225-dockernote/ + Mon, 25 Dec 2023 09:06:25 +0800 + + https://www.small09.top/posts/231225-dockernote/ + + + + <p>学习docker,使用docker,编写docker</p> +<h2>前言<span class="hx:absolute hx:-mt-20" id="前言"></span> + <a href="#%e5%89%8d%e8%a8%80" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。</p> +<p>不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。</p> +<p>由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。</p> +<h2>安装docker和docker compose<span class="hx:absolute hx:-mt-20" id="安装docker和docker-compose"></span> + <a href="#%e5%ae%89%e8%a3%85docker%e5%92%8cdocker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。</p> +<p>apt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 添加docker官方的gpg</span> +</span></span><span style="display:flex;"><span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加</span> +</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;deb [arch=</span><span style="color:#66d9ef">$(</span>dpkg --print-architecture<span style="color:#66d9ef">)</span><span style="color:#e6db74"> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu </span><span style="color:#66d9ef">$(</span>. /etc/os-release <span style="color:#f92672">&amp;&amp;</span> echo <span style="color:#e6db74">&#34;</span>$VERSION_CODENAME<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74"> stable&#34;</span> | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 我这边添加的结果是</span> +</span></span><span style="display:flex;"><span>deb <span style="color:#f92672">[</span>arch<span style="color:#f92672">=</span>amd64 signed-by<span style="color:#f92672">=</span>/usr/share/keyrings/docker-archive-keyring.gpg<span style="color:#f92672">]</span> https://download.docker.com/linux/ubuntu jammy stable</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后可以通过apt源安装docker了</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update +</span></span><span style="display:flex;"><span>sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>*<em>注意,上面的指令同时安装了docker-compose</em>。如果不安装docker,直接安装docker-compose也是可以的。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 +</span></span><span style="display:flex;"><span>sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>更加具体的步骤和细节可以到官网查看:<a href="https://docs.docker.com/engine/install/ubuntu/"target="_blank" rel="noopener">docker安装</a>、<a href="https://docs.docker.com/compose/install/standalone/"target="_blank" rel="noopener">docker-compose安装</a></p> +<h2>docker 基本使用方法<span class="hx:absolute hx:-mt-20" id="docker-基本使用方法"></span> + <a href="#docker-%e5%9f%ba%e6%9c%ac%e4%bd%bf%e7%94%a8%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 拉取一个镜像</span> +</span></span><span style="display:flex;"><span>docker pull xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 创建容器并运行</span> +</span></span><span style="display:flex;"><span>docker run --name aaa xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 停止容器</span> +</span></span><span style="display:flex;"><span>docker stop docker-id</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>关于运行容器,有很多常用的参数:</p> +<blockquote> + <p>-d 容器转入后台运行</p> +<p>-e 设置容器的环境变量</p> +<p>&ndash;name 容器名称</p> +<p>-p 80:80 将暴露的端口映射到容器内端口,外部:内部</p> +<p>-v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内</p> +<p>-e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。</p> + +</blockquote> +<p>停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。</p> +<p>查询容器id,可以使用指令<code>docker ps</code>。docker还有其他几个查询和控制指令</p> +<blockquote> + <p><code>docker exec 容器 指令</code> 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。</p> +<p><code>docker ps</code> 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。</p> +<p><code>docker images</code> 列出已经下载的镜像列表。与 <code>docker image</code> 指令不同。</p> +<p><code>docker kill 容器名/id</code> 杀死一个无法stop的容器</p> +<p><code>docker rm 容器</code> 删除一个<strong>容器</strong></p> +<p><code>docker rmi 镜像名</code> 删除一个<strong>镜像</strong></p> +<p><code>docker restart 容器ID</code> 重启一个容器,毕竟我们不需要每次都创建新容器。</p> + +</blockquote> +<p>关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。</p> +<h2>更换docker镜像源<span class="hx:absolute hx:-mt-20" id="更换docker镜像源"></span> + <a href="#%e6%9b%b4%e6%8d%a2docker%e9%95%9c%e5%83%8f%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。</p> +<p>修改<code>/etc/docker/daemon.json</code>文件,添加下面的内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;registry-mirrors&#34;</span>: [ +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;https://docker.mirrors.ustc.edu.cn&#34;</span> +</span></span><span style="display:flex;"><span> ] +</span></span><span style="display:flex;"><span>}</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>编辑完成后需要重启docker服务。</p> +<p>具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。</p> +<h2>docker-compose<span class="hx:absolute hx:-mt-20" id="docker-compose"></span> + <a href="#docker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。</p> +<p>首先创建一个工作目录,并创建一个<code>docker-compose.yml</code>的文件,默认情况下docker compose指令只认这个文件名。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>version: &#39;3&#39; +services: + [NAME1]: + image: &#34;image/name&#34; + ports: + - 81:80 + - 8080:8080 + environment: + HOSTNAME: &#34;cc&#34; + PORT: 8181 + volumes: + - nginx-data:/etc/nginx + [NAME2]: + image: &#39;xxx&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,<code>ports</code>字段指定了内外端口的映射,即&rsquo;外部:内部&rsquo;,卷映射同理。<code>environment</code>不需要 - 开头。</p> +<p>编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动</p> +<blockquote> + <p><code>docker compose up</code> 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。</p> +<p><code>docker compose down</code> 停止容器,并删除对应的容器和网络</p> +<p><code>docker compose exec NAME command</code> 在ymal文件内其中一个容器里运行指令</p> + +</blockquote> +<p>docker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。</p> +<p>目前浅学了一下,只有学一点更新一点吧。</p> + + + + + 使用Docker安装TeamSpeak + https://www.small09.top/posts/210218-teamspeakwithdocker/ + Thu, 18 Feb 2021 00:00:00 +0800 + + https://www.small09.top/posts/210218-teamspeakwithdocker/ + + + + <p>TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。</p> +<h2>Teamspeak<span class="hx:absolute hx:-mt-20" id="teamspeak"></span> + <a href="#teamspeak" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。</p> +<p>所以,选择docker安装ts就是一个很不错的选择。</p> +<h2>Docker及其安装<span class="hx:absolute hx:-mt-20" id="docker及其安装"></span> + <a href="#docker%e5%8f%8a%e5%85%b6%e5%ae%89%e8%a3%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。</p> +<p>我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo yum install docker <span style="color:#75715e">#安装docker程序,会自动选择合适的</span> +</span></span><span style="display:flex;"><span>sudo systemctl start docker <span style="color:#75715e">#启动docker服务</span> +</span></span><span style="display:flex;"><span>sudo systemctl enable docker <span style="color:#75715e">#允许docker开机自启</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>Docker安装TS<span class="hx:absolute hx:-mt-20" id="docker安装ts"></span> + <a href="#docker%e5%ae%89%e8%a3%85ts" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak</code></p> +<p>直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。</p> +<h2>端口<span class="hx:absolute hx:-mt-20" id="端口"></span> + <a href="#%e7%ab%af%e5%8f%a3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>从指令可以看出,docker需要使用到<strong>9987,10011,30033</strong>三个端口,所以需要到服务器后台开启防火墙上对应端口。</p> +<h2>管理员密钥<span class="hx:absolute hx:-mt-20" id="管理员密钥"></span> + <a href="#%e7%ae%a1%e7%90%86%e5%91%98%e5%af%86%e9%92%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。</p> +<p><code>docker ps -a</code>查找所有容器。</p> +<p><code>docker logs -f [CONTATINER ID]</code>查看log,找到token即可。客户端设置之后再说。</p> +<p>密钥只会在第一次运行时打印,注意保存。</p> +<p><em>参考:<a href="https://woolio.cn/683/"target="_blank" rel="noopener">https://woolio.cn/683/</a></em></p> + + + + + diff --git a/public/tags/http/index.html b/public/tags/http/index.html new file mode 100644 index 0000000..d632b5b --- /dev/null +++ b/public/tags/http/index.html @@ -0,0 +1,510 @@ + + + + + + + +Http – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/http/index.xml b/public/tags/http/index.xml new file mode 100644 index 0000000..65709cc --- /dev/null +++ b/public/tags/http/index.xml @@ -0,0 +1,166 @@ + + + 香风家的火柴盒 – Http + https://www.small09.top/tags/http/ + Recent content in Http on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Fri, 08 Sep 2023 00:00:00 +0800 + + + + + + + + + + + Python socket 收取POST协议内容时没有payload + https://www.small09.top/posts/230908-py-post/ + Fri, 08 Sep 2023 00:00:00 +0800 + + https://www.small09.top/posts/230908-py-post/ + + + + <p>使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况</p> +<p>最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。</p> +<p>服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sock <span style="color:#f92672">=</span> socket<span style="color:#f92672">.</span>socket(socket<span style="color:#f92672">.</span>AF_INET, socket<span style="color:#f92672">.</span>SOCK_STREAM) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>bind((self<span style="color:#f92672">.</span>host, self<span style="color:#f92672">.</span>port)) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>listen(<span style="color:#ae81ff">128</span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>(client, address) <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>sock<span style="color:#f92672">.</span>accept() +</span></span><span style="display:flex;"><span>th <span style="color:#f92672">=</span> Thread(target<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>accept_request, args<span style="color:#f92672">=</span>(client, address)) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">accept_request</span>(client, address): +</span></span><span style="display:flex;"><span> data <span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) +</span></span><span style="display:flex;"><span> req <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>decode(<span style="color:#e6db74">&#34;utf-8&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 处理数据的具体方法</span> +</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> process_response(req, client_addr) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>send(response) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># clean up</span> +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>shutdown(<span style="color:#ae81ff">1</span>) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>close()</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。</p> +<p><strong>=====2024.01.04更新=======</strong></p> +<p>下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。</p> +<p>其实用flask接收和处理数据会更加方便快捷:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> flask <span style="color:#f92672">import</span> Flask, request +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 实例化flask</span> +</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> Flask(__name__) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 接收和处理post的方法</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.route</span>(<span style="color:#e6db74">&#39;/test&#39;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#39;POST&#39;</span>]) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv_file_www</span>(): +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post的传入参数</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>args<span style="color:#f92672">.</span>to_dict()) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post所承载的数据</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>data) +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;OK&#39;</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;__main__&#39;</span>: +</span></span><span style="display:flex;"><span> app<span style="color:#f92672">.</span>run(port<span style="color:#f92672">=</span><span style="color:#ae81ff">31408</span>, host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0.0.0.0&#34;</span>,debug<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。</p> +<p><strong>======以下是原来的内容==========</strong></p> +<p>经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,<strong>必须不断的recv才能完整接收数据</strong>。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。</p> +<p>但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:</p> +<p>POST的报文头会包含一个<code>Content-length</code>的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。</p> +<p>那么将上面代码的<code>client_sock.recv()</code>部分改写成下面的方法:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv</span>(self, client_sock): +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> <span style="color:#ae81ff">8192</span> <span style="color:#75715e"># 记录每次接收的长度</span> +</span></span><span style="display:flex;"><span> lrecv_all<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> <span style="color:#75715e"># 记录数据总长度 </span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">=</span> bytes() <span style="color:#75715e"># 接收的所有数据存入这里</span> +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 解析的payload长度</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 用于记录是否接收到了payload,以及实时记录接收到payload的长度</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span> : +</span></span><span style="display:flex;"><span> part_body<span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) <span style="color:#75715e"># 循环接收数据</span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">+=</span> part_body <span style="color:#75715e"># 将接收的数据写入data_body</span> +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> len(part_body) <span style="color:#75715e"># 获取本次接收长度</span> +</span></span><span style="display:flex;"><span> lrecv_all <span style="color:#f92672">+=</span> lrecved <span style="color:#75715e"># 计算总长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>: <span style="color:#75715e"># 没有接收到payload部分时,才进入下面的判断流程</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">3</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;GET&#39;</span>: <span style="color:#75715e"># 由于我get方法没出现过断开的问题,因此直接返回即可。</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;POST&#39;</span>: +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> count_payload_len(data_body) <span style="color:#75715e"># 解析头部,计算payload部分的长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> lrecved <span style="color:#f92672">&lt;</span> payload_len: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 偶尔会出现能接收到payload的情况,所以多加了一个判断。</span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 能接收payload肯定总长度大于payload_len的</span> +</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;no payloads, keep recving...&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 计算post报文的总长度,借用payload_flag变量</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> lrecved <span style="color:#f92672">+</span> payload_len +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 当接收到的总长度 &gt;= POST的计算长度,表示所有数据接收完成,退出</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">&lt;=</span> lrecv_all: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> data_body</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>里面解析headers的代码如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">count_payload_len</span>(data): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> d <span style="color:#f92672">in</span> data<span style="color:#f92672">.</span>split(<span style="color:#e6db74">b</span><span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">&#39;</span>): <span style="color:#75715e"># 通过\r\n来分隔报文头</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;LENGTH&#34;</span> <span style="color:#f92672">in</span> d<span style="color:#f92672">.</span>upper()<span style="color:#f92672">.</span>decode(): <span style="color:#75715e"># 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> int(d<span style="color:#f92672">.</span>decode()<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;:&#34;</span>)[<span style="color:#ae81ff">1</span>]) <span style="color:#75715e"># 分隔出payload长度并返回</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。</p> +<p>对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。</p> + + + + + diff --git a/public/tags/index.html b/public/tags/index.html new file mode 100644 index 0000000..9a90638 --- /dev/null +++ b/public/tags/index.html @@ -0,0 +1,671 @@ + + + + + + + +Tags – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/index.xml b/public/tags/index.xml new file mode 100644 index 0000000..66161f5 --- /dev/null +++ b/public/tags/index.xml @@ -0,0 +1,19 @@ + + + 香风家的火柴盒 – Tags + https://www.small09.top/tags/ + Recent content in Tags on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 25 Dec 2023 09:06:25 +0800 + + + + + + + + + + + diff --git a/public/tags/linux/index.html b/public/tags/linux/index.html new file mode 100644 index 0000000..f7d2635 --- /dev/null +++ b/public/tags/linux/index.html @@ -0,0 +1,726 @@ + + + + + + + +Linux – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + +
    +
    +
    +

    Linux

    +
    +
    + +
    +
    +

    + + docker简单笔记 + +

    +

    2023/12/25

    +
    +

    + + Linux安装VNC服务端 + +

    +

    2023/11/27

    +
    +

    + + DEB打包方法简介 + +

    +

    2021/10/19

    +
    +

    + + 免密码使用sudo + +

    +

    2021/06/30

    +
    +

    + + Linux修改mac地址 + +

    +

    2021/03/28

    +
    +

    + + Linux串口和udev理解 + +

    +

    2021/03/11

    +
    +
    +
    +
    + + + + + + + + diff --git a/public/tags/linux/index.xml b/public/tags/linux/index.xml new file mode 100644 index 0000000..3ae3db7 --- /dev/null +++ b/public/tags/linux/index.xml @@ -0,0 +1,1636 @@ + + + 香风家的火柴盒 – Linux + https://www.small09.top/tags/linux/ + Recent content in Linux on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 25 Dec 2023 09:06:25 +0800 + + + + + + + + + + + docker简单笔记 + https://www.small09.top/posts/231225-dockernote/ + Mon, 25 Dec 2023 09:06:25 +0800 + + https://www.small09.top/posts/231225-dockernote/ + + + + <p>学习docker,使用docker,编写docker</p> +<h2>前言<span class="hx:absolute hx:-mt-20" id="前言"></span> + <a href="#%e5%89%8d%e8%a8%80" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。</p> +<p>不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。</p> +<p>由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。</p> +<h2>安装docker和docker compose<span class="hx:absolute hx:-mt-20" id="安装docker和docker-compose"></span> + <a href="#%e5%ae%89%e8%a3%85docker%e5%92%8cdocker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。</p> +<p>apt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 添加docker官方的gpg</span> +</span></span><span style="display:flex;"><span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加</span> +</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;deb [arch=</span><span style="color:#66d9ef">$(</span>dpkg --print-architecture<span style="color:#66d9ef">)</span><span style="color:#e6db74"> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu </span><span style="color:#66d9ef">$(</span>. /etc/os-release <span style="color:#f92672">&amp;&amp;</span> echo <span style="color:#e6db74">&#34;</span>$VERSION_CODENAME<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74"> stable&#34;</span> | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 我这边添加的结果是</span> +</span></span><span style="display:flex;"><span>deb <span style="color:#f92672">[</span>arch<span style="color:#f92672">=</span>amd64 signed-by<span style="color:#f92672">=</span>/usr/share/keyrings/docker-archive-keyring.gpg<span style="color:#f92672">]</span> https://download.docker.com/linux/ubuntu jammy stable</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后可以通过apt源安装docker了</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update +</span></span><span style="display:flex;"><span>sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>*<em>注意,上面的指令同时安装了docker-compose</em>。如果不安装docker,直接安装docker-compose也是可以的。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 +</span></span><span style="display:flex;"><span>sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>更加具体的步骤和细节可以到官网查看:<a href="https://docs.docker.com/engine/install/ubuntu/"target="_blank" rel="noopener">docker安装</a>、<a href="https://docs.docker.com/compose/install/standalone/"target="_blank" rel="noopener">docker-compose安装</a></p> +<h2>docker 基本使用方法<span class="hx:absolute hx:-mt-20" id="docker-基本使用方法"></span> + <a href="#docker-%e5%9f%ba%e6%9c%ac%e4%bd%bf%e7%94%a8%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 拉取一个镜像</span> +</span></span><span style="display:flex;"><span>docker pull xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 创建容器并运行</span> +</span></span><span style="display:flex;"><span>docker run --name aaa xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 停止容器</span> +</span></span><span style="display:flex;"><span>docker stop docker-id</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>关于运行容器,有很多常用的参数:</p> +<blockquote> + <p>-d 容器转入后台运行</p> +<p>-e 设置容器的环境变量</p> +<p>&ndash;name 容器名称</p> +<p>-p 80:80 将暴露的端口映射到容器内端口,外部:内部</p> +<p>-v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内</p> +<p>-e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。</p> + +</blockquote> +<p>停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。</p> +<p>查询容器id,可以使用指令<code>docker ps</code>。docker还有其他几个查询和控制指令</p> +<blockquote> + <p><code>docker exec 容器 指令</code> 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。</p> +<p><code>docker ps</code> 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。</p> +<p><code>docker images</code> 列出已经下载的镜像列表。与 <code>docker image</code> 指令不同。</p> +<p><code>docker kill 容器名/id</code> 杀死一个无法stop的容器</p> +<p><code>docker rm 容器</code> 删除一个<strong>容器</strong></p> +<p><code>docker rmi 镜像名</code> 删除一个<strong>镜像</strong></p> +<p><code>docker restart 容器ID</code> 重启一个容器,毕竟我们不需要每次都创建新容器。</p> + +</blockquote> +<p>关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。</p> +<h2>更换docker镜像源<span class="hx:absolute hx:-mt-20" id="更换docker镜像源"></span> + <a href="#%e6%9b%b4%e6%8d%a2docker%e9%95%9c%e5%83%8f%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。</p> +<p>修改<code>/etc/docker/daemon.json</code>文件,添加下面的内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;registry-mirrors&#34;</span>: [ +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;https://docker.mirrors.ustc.edu.cn&#34;</span> +</span></span><span style="display:flex;"><span> ] +</span></span><span style="display:flex;"><span>}</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>编辑完成后需要重启docker服务。</p> +<p>具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。</p> +<h2>docker-compose<span class="hx:absolute hx:-mt-20" id="docker-compose"></span> + <a href="#docker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。</p> +<p>首先创建一个工作目录,并创建一个<code>docker-compose.yml</code>的文件,默认情况下docker compose指令只认这个文件名。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>version: &#39;3&#39; +services: + [NAME1]: + image: &#34;image/name&#34; + ports: + - 81:80 + - 8080:8080 + environment: + HOSTNAME: &#34;cc&#34; + PORT: 8181 + volumes: + - nginx-data:/etc/nginx + [NAME2]: + image: &#39;xxx&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,<code>ports</code>字段指定了内外端口的映射,即&rsquo;外部:内部&rsquo;,卷映射同理。<code>environment</code>不需要 - 开头。</p> +<p>编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动</p> +<blockquote> + <p><code>docker compose up</code> 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。</p> +<p><code>docker compose down</code> 停止容器,并删除对应的容器和网络</p> +<p><code>docker compose exec NAME command</code> 在ymal文件内其中一个容器里运行指令</p> + +</blockquote> +<p>docker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。</p> +<p>目前浅学了一下,只有学一点更新一点吧。</p> + + + + + Linux安装VNC服务端 + https://www.small09.top/posts/231127-vncserver/ + Mon, 27 Nov 2023 09:33:32 +0800 + + https://www.small09.top/posts/231127-vncserver/ + + + + <p><em>(笔记)在Linux系统里安装远程桌面VNC客户端并连接。</em></p> +<h3>1. 安装NVC服务端<span class="hx:absolute hx:-mt-20" id="1-安装nvc服务端"></span> + <a href="#1-%e5%ae%89%e8%a3%85nvc%e6%9c%8d%e5%8a%a1%e7%ab%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install x11vnc</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h3>2. 设置远程连接密码<span class="hx:absolute hx:-mt-20" id="2-设置远程连接密码"></span> + <a href="#2-%e8%ae%be%e7%bd%ae%e8%bf%9c%e7%a8%8b%e8%bf%9e%e6%8e%a5%e5%af%86%e7%a0%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>x11vnc --storepasswd +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 输入两次连接密码以设定</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 询问 Write password to /home/xxx/.vnc/passwd </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 时,输入y并回车保存密码。</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ul> +<li>该步骤不需要使用sudo!</li> +</ul> +<h3>3. 使VNC服务开机自启动<span class="hx:absolute hx:-mt-20" id="3-使vnc服务开机自启动"></span> + <a href="#3-%e4%bd%bfvnc%e6%9c%8d%e5%8a%a1%e5%bc%80%e6%9c%ba%e8%87%aa%e5%90%af%e5%8a%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>使用service的方法,将vnc作为一个服务开机启动</p> +<p>创建<code>/lib/systemd/system/x11vnc.service</code>文件并填入下面的内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Start x11vnc at startup.</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">simple</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/x11vnc -auth guess -rfbauth /home/xxx/.vnc/passwd</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ul> +<li>注意,/home/xxx 需要替换成实际的用户名!</li> +</ul> +<h3>使用vnc客户端连接<span class="hx:absolute hx:-mt-20" id="使用vnc客户端连接"></span> + <a href="#%e4%bd%bf%e7%94%a8vnc%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>我使用Windows上的VncViewer,官网<a href="https://www.realvnc.com/en/"target="_blank" rel="noopener">RealVNC</a>,这个软件假冒的很多,下载需要注意甄别。</p> + + + + + 讨论在Debian系操作系统中重新提取已安装的软件并重新打包 + https://www.small09.top/posts/230508-remake-deb/ + Mon, 08 May 2023 14:43:41 +0800 + + https://www.small09.top/posts/230508-remake-deb/ + + + + <p>Debian系操作系统采用dpkg作为包管理器,在安装软件后会存储deb包信息,是否可以提取这些信息并且重新打deb包转移到其他平台使用?</p> +<p>之前有一个需求,要从UOS里面提取一个软件,并且将其安装到另一台设备中。当时找了一圈都没有找到相关的提取教程。但是从逻辑上来讲,dpkg安装的软件会保存deb包的相关信息,所以理论上讲是可以提取出软件包的所有内容,然后我们重新打包的。</p> +<p>今天在一个机缘巧合下(其实是懒得研究dpkg原理,瞎猫撞上死耗子),发现了dpkg保存文件信息的地点,于是接下来可以展开畅想:提取已经安装的软件并重新打包。</p> +<h2>文件的保存地点<span class="hx:absolute hx:-mt-20" id="文件的保存地点"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%9a%84%e4%bf%9d%e5%ad%98%e5%9c%b0%e7%82%b9" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我在找fcitx有关的配置文件进行全盘搜索的时候,找到了一条目录<code>/usr/lib/dpkg-db/info</code>,这个目录里面保留了fcitx的&quot;conffiles&quot;, &ldquo;md5sums&rdquo;, &ldquo;postinst&rdquo;, &ldquo;prerm&quot;四个文件,以及一个list文件。恰好fcitx.deb解包出来的DEBIAN文件夹内也是这四个文件加上额外的control文件,。</p> +<p>那么合理猜测,dpkg安装软件的时候,不仅把软件本身的文件移动到对应位置,还把DEBIAN里面的信息和脚本也转移到<code>/usr/lib/dpkg-db/</code>里面保存了。</p> +<p>而<code>fcitx.list</code>文件中记录的是所有的文件及其路径。想必这里面就是全部的文件了。</p> +<p>至于control文件,则都被保存到了<code>/usr/lib/dpkg-db/status</code>这个文本文件中。</p> +<h2>流程设想<span class="hx:absolute hx:-mt-20" id="流程设想"></span> + <a href="#%e6%b5%81%e7%a8%8b%e8%ae%be%e6%83%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>既然deb包所有必要的文件都找到了,那么就可以尝试一下还原过程了。在<a href="https://www.small09.top/posts/211019-dpkg_package_guide/"target="_blank" rel="noopener">这篇文章</a>里我研究过deb包的组成、格式和打包方法。</p> +<p>比如对于软件包A:</p> +<ol> +<li> +<p>创建一个工作目录,并在其中新建<code>DEBIAN</code>目录。</p> +</li> +<li> +<p>将<code>/usr/lib/dpkg-db/info</code>中所有该软件的脚本和信息(除了A.list)转移到<code>DEBIAN</code>中,并重命名成规范名称。</p> +</li> +<li> +<p>将<code>/usr/lib/dpkg-db/status</code>中与软件A有关的部分提取出来,保存到<code>DEBIAN/control</code>文件中。</p> +</li> +<li> +<p>根据A.list文件,找到软件包含的所有文件,将其按原样整理到工作目录内。</p> +</li> +<li> +<p>使用<code>dpkg -b</code>指令重新打包。</p> +</li> +</ol> +<h2>局限性<span class="hx:absolute hx:-mt-20" id="局限性"></span> + <a href="#%e5%b1%80%e9%99%90%e6%80%a7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上毕竟只是设想,没有进行试验,所以估计会出现这些问题,希望有需要的人尝试后可以解决。</p> +<ul> +<li> +<p>提取信息的难度: 由于<code>/usr/lib/dpkg-db/status</code>文件是一个包含几乎所有deb信息的文本文件,体积大内容乱,对于自动化脚本来说有些难度。加上一些软件的衍生品会在软件名上直接加后缀,对于提取的脚本来讲可能造成混淆,提取出不必要的东西。</p> +</li> +<li> +<p>获取源文件的难度: 不排除一些软件安装时会删除、修改自身的一些文件,可能打包会后会缺失内容。一些软件安装过程中会进行手动配置从而造成内容改变,重做出来的软件包不一定完整。</p> +</li> +<li> +<p>签名问题: 我们打包的软件没有原作者的数字签名,可能无法安装,也可能时候安装官方软件时冲突。</p> +</li> +<li> +<p>这个方案只能处理普通的软件,一些deb打包源码,安装时进行编译的,应该是不能用这种方案复原的。(apt的deb-src源)</p> +</li> +</ul> +<p>最后,希望有大佬可以验证一下这个方案。</p> + + + + + 从再生龙(clonezilla)镜像直接提取文件 + https://www.small09.top/posts/220714-unpackclonezillaimage/ + Thu, 14 Jul 2022 13:05:51 +0800 + + https://www.small09.top/posts/220714-unpackclonezillaimage/ + + + + <p>从clonezilla的镜像直接提取文件,免于先全盘全区恢复。</p> +<p>再生龙(clonezilla)是一个常用的Linux系统备份工具,适用于多个版本、多种CPU架构的电脑。</p> +<p>再生龙生成的镜像是比较特殊的格式,如果想要从里面提取文件,通常只能进行镜像恢复后进入系统提取。但是往往我们没有那么多硬盘来恢复系统。</p> +<p>再生龙的原理简单来说就是把硬盘分区使用partclone工具打包成img文件,然后使用gzip进行分卷压缩。我们只需要逆向操作,利用工具,把镜像解包挂载,直接在当前系统挂载,就可以得到原分区的文件了。</p> +<h2>挂载方法:<span class="hx:absolute hx:-mt-20" id="挂载方法"></span> + <a href="#%e6%8c%82%e8%bd%bd%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>1. 环境需求<span class="hx:absolute hx:-mt-20" id="1-环境需求"></span> + <a href="#1-%e7%8e%af%e5%a2%83%e9%9c%80%e6%b1%82" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>我们操作再生龙镜像时,最好是使用Linux环境。这个环境中,需要安装partclone(也就是再生龙生成镜像的核心工具)。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install partclone +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以Debian系为例</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>并且我们需要准备一个足够大的硬盘,用来存储镜像包和解包出的img文件。硬盘/分区的空间需要大于镜像本来的<strong>分区</strong>空间。</p> +<h3>2. 合并镜像<span class="hx:absolute hx:-mt-20" id="2-合并镜像"></span> + <a href="#2-%e5%90%88%e5%b9%b6%e9%95%9c%e5%83%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>再生龙默认情况下会把一个硬盘分区分按4GB的大小进行分包,通常以<code>gz.aa</code>、<code>gz.ab</code>等结尾,我们需要把这些包重新整合成一个gz包。</p> +<p>整合成gz包后,我们就可以解压gz来得到镜像img了。我们可以把两个步骤用流的方式写入一条命令,节省硬盘空间。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cat sda1.ext4-ptcl-img.gz.* | gzip -d -c &gt; sda1.img</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这一步生成的img文件不能直接使用(google上也有说能用的,但是我试下来不能),需要进行一次转换。经过实际操作,这个文件所占用的空间是镜像原分区的大小,但是统计硬盘使用量时却只计算实际大小,希望了解的人可以帮忙解释一下。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>partclone.extfs -r -s sda1.img -o sda1-ex.img --restore_raw_file</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>当然上面两条指令也可以使用流的方式再次封装到一条,但是消耗的时间、失败后重新来过的时间,都会更长。</p> +<h2>3.挂载镜像<span class="hx:absolute hx:-mt-20" id="3挂载镜像"></span> + <a href="#3%e6%8c%82%e8%bd%bd%e9%95%9c%e5%83%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>将最后生成的img镜像挂载到我们的/mnt(或其他)目录,进入这个目录就可以看到原来全部的文件了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -o loop -t ext4 sda1-ex.img /mnt</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>最后我们只需要进入 <code>/mnt</code> 目录,就可以想直接操作硬盘一样把文件复制出来了。</p> + + + + + Deepin/UOS文件管理器隐藏不需要的盘符 + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + Wed, 23 Feb 2022 09:18:55 +0800 + + https://www.small09.top/posts/220223-deepin_partsinddefilemanager/ + + + + <p>Deepin/UOS 里处理文件管理器显示太多盘符的问题。</p> +<h2>现象<span class="hx:absolute hx:-mt-20" id="现象"></span> + <a href="#%e7%8e%b0%e8%b1%a1" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在使用UOS操作系统时,有时候打开文件管理器,发现除了平常常见的“系统盘”、“数据盘”外,出现了更多的盘符(不属于移动u盘)。在<code>/etc/fstab</code>文件添加或删除“”并没有效果。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230924406.png" alt="image-20220223090517363" loading="lazy" /></p> +<h2>原理/原因<span class="hx:absolute hx:-mt-20" id="原理原因"></span> + <a href="#%e5%8e%9f%e7%90%86%e5%8e%9f%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>这是因为系统/etc/udev/rules.d文件夹做了非法修改。有些人盲目替换了文件夹文件,导致配置文件变化,系统无法正确执行隐藏操作。</p> +<h2>处理方法<span class="hx:absolute hx:-mt-20" id="处理方法"></span> + <a href="#%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li> +<p>打开<code>/etc/udev/rules.d</code>目录,查看是否有一个《80-udisks2.rules》文件。如果该文件不存在,请创建。</p> +</li> +<li> +<p>打开终端,使用root权限打开编辑</p> +</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo deepin-editor /etc/udev/rules.d/80-udisk2.rules</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ol start="3"> +<li> +<p><strong>打开新的终端</strong>,输入执行 <code>blkid -f</code> ,记录LABLE、UUID两列</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230928320.png" alt="截图录屏_选择区域_20220223090208" loading="lazy" /></p> +</li> +<li> +<p>对比<code>80-udisks2.rules</code>的内容,将里面的ENV{ID_FS_UUID}后面引号中内容换成第3步中显示的新的UUID。</p> +<p>(#号后面内容即为盘符,对应3中的LABEL,双等号后面就是UUID,四行都需要替换)</p> +<p>​ <img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202202230927888.png" alt="截图录屏_选择区域_20220223090606" loading="lazy" /></p> +</li> +<li> +<p>保存文件并重启计算机,即可看到效果。</p> +</li> +<li> +<p>该方法对Deepin系统也有效。对于其他使用了dde-file-manager的应该也是有作用的。</p> +</li> +</ol> + + + + + 定制自己的Linux ISO镜像 + https://www.small09.top/posts/220104-diylinuxiso/ + Tue, 04 Jan 2022 09:17:38 +0800 + + https://www.small09.top/posts/220104-diylinuxiso/ + + + + <p>想要在原版的Linux镜像上添加自定义的软件,但是不想、不会编译,那么就可以基于Linux的iso文件进行修改,定制自己的安装试用镜像。</p> +<p>起因是因为对付信创操作系统的备份恢复时,UOS、麒麟自带的工具都超级不好用,两个系统的镜像不统一,导致实际使用的时候各种问题和不方便。所以使用clonezilla再生龙工具自己做一个PE镜像。这个镜像不仅可以备份、安装系统,还能够修改硬盘大小、应急拯救系统等多种功能。并且它是一个完整的系统可以安装使用。</p> +<h2>基本计划<span class="hx:absolute hx:-mt-20" id="基本计划"></span> + <a href="#%e5%9f%ba%e6%9c%ac%e8%ae%a1%e5%88%92" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>之前尝试过使用<a href="http://bbs.wuyou.net/forum.php?mod=viewthread&amp;tid=409980"target="_blank" rel="noopener">deepin魔改lub工具</a>制作系统的完整镜像,但是启动总是失败。<a href="https://forum.ubuntu.org.cn/viewtopic.php?t=206287"target="_blank" rel="noopener">LUB</a>全称是<em>Live Ubuntu Backup</em>,是Ubuntu的一个工具,到了同为Debian系的deepin魔改的UOS上就水土不服了。(好绕,套娃呢搁这儿</p> +<p>所以采取另一种办法,用已有的Linux iso,通过解压其系统文件filesystem.squashfs,然后替换关键文件,最后实现自定义系统。</p> +<h2>解压、修改、打包Linux文件系统<span class="hx:absolute hx:-mt-20" id="解压修改打包linux文件系统"></span> + <a href="#%e8%a7%a3%e5%8e%8b%e4%bf%ae%e6%94%b9%e6%89%93%e5%8c%85linux%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>首先把Linux iso挂载到系统中。把里面的filesystem.squashfs文件复制出来(因为iso是只读的,复制出来后也方便管理和备份)。我这里以UOS1040为例,其文件系统在<code>./live</code>目录下</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -o loop /xxx/xxx.iso /media/usrname/isofolder +</span></span><span style="display:flex;"><span>cd /media/usrname/isofolder/live/ +</span></span><span style="display:flex;"><span>cp ./filesystem.squashfs ~</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>安装<code>squashfs-tools</code>工具,用来压缩解压归档文件。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install squashfs-tools +</span></span><span style="display:flex;"><span><span style="color:#75715e">#回到主目录后,解压filesystem.squashfs文件</span> +</span></span><span style="display:flex;"><span>sudo unsquashfs filesystem.squashfs</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>解压后可以看到主目录多一个<code>squashfs-root</code>目录,里面存放的就是一个Linux的文件系统了。这时候把你需要的文件拷贝进去,比如我替换了/usr、/opt、/etc三个目录,这三个目录保存了我安装的clonezilla应用及其配置程序,以及我编写的辅助脚本和自启动脚本。</p> +<p>修改好了就可以打包了。第一个参数是刚刚解压出来修改好的文件系统,第二个参数是输出的名称。建议把之前拷贝出来的squashfs文件改名进行保存。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mksquashfs squashfs-root/ filesystem.squashfs</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>重建Linux iso<span class="hx:absolute hx:-mt-20" id="重建linux-iso"></span> + <a href="#%e9%87%8d%e5%bb%balinux-iso" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最后我们需要重建ISO文件。由于我们是基于原来的ISO修改的,所以只需要替换iso里面的filesystem.squashfs文件即可。否则还需要更换系统内核img/lz、重写引导。</p> +<p>把新生成的filesystem.squashfs文件拷贝到Windows系统里,打开UltraISO软件,直接替换Live目录下的filesystem.squashfs文件。同时我们也可以修改<code>EFI/grub.cfg</code>文件,毕竟这个系统主要是使用而不是安装。我调换了安装和试用的次序,并把等待时间增加到60s。</p> +<p>最后进行修改ISO名称等小操作,保存为ISO文件就可以了。</p> +<p>新生成的ISO文件既可以用rufus等工具烧录到U盘中制作PE盘,也可以试用ventoy工具制作成多系统启动盘,甚至可以烧录一张光盘来使用。</p> + + + + + 在Linux环境下修改用户名和主目录 + https://www.small09.top/posts/211203-linuxchangeusername/ + Fri, 03 Dec 2021 09:01:23 +0800 + + https://www.small09.top/posts/211203-linuxchangeusername/ + + + + <p>如何在Linux下完整修改当前用户名和主目录,还不影响其他文件?</p> +<p>Linux的用户名是一个用户的字符串表示,是可以修改的。只要用户的id不改变就不会被系统认作新用户,所有的权限都可以继承。下面介绍如何修改用户的名称、所属组,并更改主目录的名称与对应关系。</p> +<h2>一、进入“单用户模式”<span class="hx:absolute hx:-mt-20" id="一进入单用户模式"></span> + <a href="#%e4%b8%80%e8%bf%9b%e5%85%a5%e5%8d%95%e7%94%a8%e6%88%b7%e6%a8%a1%e5%bc%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>为了修改用户名时不影响程序运行,需要把当前用户打开的所有程序都关闭。该方法可以快速注销当前用户。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>init <span style="color:#ae81ff">1</span> +</span></span><span style="display:flex;"><span>执行后输入当前用户密码 +</span></span><span style="display:flex;"><span>进入黑屏终端后需要输入root密码,可以使用sudo passwd 提前设置</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>对于屏蔽init指令的系统,一般情况下注销用户就可以了,但是有些GUI桌面环境就是以当前用户的身份运行的,所以提供如下几个方式关闭当前用户,挨个尝试即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 1 桌面环境中点击注销</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2 杀掉当前用户所有进程</span> +</span></span><span style="display:flex;"><span>pkill -kill -u old_name +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3 杀掉进程后可能桌面重启,关闭Lightdm管理器</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># Ctrl + Alt + F2 进入第二页面,以root用户登录(可能需要提前设置root密码)</span> +</span></span><span style="display:flex;"><span>systemctl stop lightdm +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 然后再次执行第二步</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>二、修改信息<span class="hx:absolute hx:-mt-20" id="二修改信息"></span> + <a href="#%e4%ba%8c%e4%bf%ae%e6%94%b9%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>init 0</code>可以直接进入黑屏的终端,或者使用 <code>ctrl + alt + f2</code>进入(远程连接可以),以root用户登录。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 1 修改组的名称</span> +</span></span><span style="display:flex;"><span>groupmod -n new_name old_name +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2 修改用户名 -l 改名字 -d 改目录 -g 改组</span> +</span></span><span style="display:flex;"><span>usermod -l new_name -d /home/new_name -g new_name old_name +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3 修改主目录</span> +</span></span><span style="display:flex;"><span>mv /home/old_name /home/new_name</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>最后回到图形化界面,输入<code>init 5</code> ,或者重启电脑。</p> +<p>修改完主目录后记得检查是不是有其他软连接指向旧目录,需要进行更新,否则还会遇到问题。</p> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p># <a href="https://blog.csdn.net/weixin_30016961/article/details/116543982"target="_blank" rel="noopener">linux用户家目录怎么修改,Linux 更改用户名,用户组和主目录</a></p> +<p># <a href="https://www.runoob.com/linux/linux-comm-usermod.html"target="_blank" rel="noopener">菜鸟教程-usermod指令</a></p> +<p># <a href="https://blog.csdn.net/weixin_40001125/article/details/88903115"target="_blank" rel="noopener">linux组的管理:修改组名字,删除组,使用sudo权限</a></p> + + + + + DEB打包方法简介 + https://www.small09.top/posts/211019-dpkg_package_guide/ + Tue, 19 Oct 2021 10:12:23 +0800 + + https://www.small09.top/posts/211019-dpkg_package_guide/ + + + + <p>简单介绍deb打包的方法,包括目录树格式和打包注意事项。</p> +<h2>文件结构<span class="hx:absolute hx:-mt-20" id="文件结构"></span> + <a href="#%e6%96%87%e4%bb%b6%e7%bb%93%e6%9e%84" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>首先创建一个目录作为工作空间,目录名可以使用软件名,deb包所有文件都将会存在这个目录下,这个目录就是安装时的根目录<code>/</code>。一个工作空间的目录树如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code> PackageDIR + ├─DEBIAN + ├─opt + │ └─kushidou-touchscreen + │ └─desktops + ├─usr + │ ├─bin + │ └─share + └─var</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中,<code>DEBIAN</code>目录放置了安装包的信息、配置、控制文件等,其他文件都放在目录的相对位置里,安装时就会根据这些相对位置,把程序与文件直接拷贝到对应的地方。</p> +<h2>DEBIAN<span class="hx:absolute hx:-mt-20" id="debian"></span> + <a href="#debian" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>DEBIAN</code>目录下有一个control文件和至多四个脚本文件。</p> +<h3>control<span class="hx:absolute hx:-mt-20" id="control"></span> + <a href="#control" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>control</code>文件用于写入安装包的识别信息,包括包名、版本、作者、简介等。没有严格的顺序要求。表格中列出常用的关键字和作用,图片展示了一个软件包的control信息。control文件最后需要添加一个空行。</p> +<table> + <thead> + <tr> + <th>NAME</th> + <th>Description</th> + </tr> + </thead> + <tbody> + <tr> + <td>Package</td> + <td>软件名,<code>dpkg -l</code> 列出的名字</td> + </tr> + <tr> + <td>Version</td> + <td>版本号-修订号</td> + </tr> + <tr> + <td>Section</td> + <td>软件类型,比如mail, test, x11等</td> + </tr> + <tr> + <td>Priority</td> + <td>优先级/重要性,比如optional 可选的</td> + </tr> + <tr> + <td>Architecture</td> + <td>软件架构,比如x86, arm64</td> + </tr> + <tr> + <td>Depends</td> + <td>依赖环境,用 <code>,</code> 隔开,用<code>(&gt;=&lt; )</code>指定版本号</td> + </tr> + <tr> + <td>Suggests</td> + <td>推荐安装,指非必要依赖,可以提供更多功能</td> + </tr> + <tr> + <td>Maintainer</td> + <td>作者 &lt;邮箱或者其他联系方式&gt;</td> + </tr> + <tr> + <td>Descritprion</td> + <td>简介,可以换行书写</td> + </tr> + </tbody> +</table> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20211019101901.png" alt="image-20211019094048011" loading="lazy" /></p> +<h3>额外脚本<span class="hx:absolute hx:-mt-20" id="额外脚本"></span> + <a href="#%e9%a2%9d%e5%a4%96%e8%84%9a%e6%9c%ac" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>DEBIAN</code>目录下之多放置四个可执行脚本,包括<strong>安装前<code>preinst</code></strong>、<strong>安装后<code>postinst</code></strong>、<strong>卸载前<code>prerm</code></strong>、<strong>卸载前<code>postrm</code></strong>,四个脚本都需要以<code>#!/bin/bash</code>开头,内容与普通脚本无异,文件名不需要后缀。</p> +<p>这些脚本主要实现复制快捷方式、建立软连接、添加环境变量、添加额外的文件和配置等操作。这些脚本是以<code>root</code>身份运行,注意他们创建文件的访问权限。inst创建的文件无法被dpkg删除,需要使用rm脚本自行处理。</p> +<p>此外<code>postinst</code>可以用来启动已安装的程序,<code>prerm</code>常用来停止正在运行的程序。</p> +<h3>md5sums<span class="hx:absolute hx:-mt-20" id="md5sums"></span> + <a href="#md5sums" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>为了防止安装包被篡改,可以计算每个二进制文件的md5值存放在这个文件里。非必要。</p> +<h2>打包<span class="hx:absolute hx:-mt-20" id="打包"></span> + <a href="#%e6%89%93%e5%8c%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>所有文件配置完成后,进入工作空间上级目录,执行指令</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dpkg -b ./PackageDIR packagename.deb</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>deb包命名的一般规则为 <strong>软件名_版本-修订号_架构.deb</strong></p> +<h2>其他信息<span class="hx:absolute hx:-mt-20" id="其他信息"></span> + <a href="#%e5%85%b6%e4%bb%96%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>一般公开发行的安装包应该包含其他信息,如版权信息、changelog、doc、man手册等。这些文件都有各自的编撰格式,主要是为了方便系统识别和二次开发,与用户直接体验无关。</p> + + + + + Linux恢复操作系统后mdadm导致开机变慢的方法 + https://www.small09.top/posts/210914-mdadm/ + Tue, 14 Sep 2021 15:16:13 +0800 + + https://www.small09.top/posts/210914-mdadm/ + + + + <p>解决Linux系统异地恢复后开机mdadm报错导致开机时间变长。</p> +<h2>现象<span class="hx:absolute hx:-mt-20" id="现象"></span> + <a href="#%e7%8e%b0%e8%b1%a1" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用deepin-clone恢复UOS镜像后,开机会不断跳一条日志:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mdadm: no arrays found in config file or automatically</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>大概蹦个15条后,系统才真正开机。mdadm好像是一个磁盘raid管理工具,当磁盘变化后其配置也不适配了,必须等到超时才会加载默认配置。那么我们就为其生成一个新的配置即可解决问题。</p> +<h2>方案<span class="hx:absolute hx:-mt-20" id="方案"></span> + <a href="#%e6%96%b9%e6%a1%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>步骤一:删除mdadm.conf</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo rm /etc/mdadm/mdadm.conf</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>步骤二:生成新的mdadm.conf</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo update-initramfs -u</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>ps:如果是swap分区问题导致等待90s开机慢,不归这儿管。</p> +<h2>从这儿抄的:<span class="hx:absolute hx:-mt-20" id="从这儿抄的"></span> + <a href="#%e4%bb%8e%e8%bf%99%e5%84%bf%e6%8a%84%e7%9a%84" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><a href="https://blog.csdn.net/KuXiaoQuShiHuai/article/details/99686620"target="_blank" rel="noopener">W: mdadm: no arrays found in config file or automatically导致开机变慢的解决方案(deepin)</a></p> + + + + + 使用dd指令备份和恢复Linux系统(硬盘级) + https://www.small09.top/posts/210914-linuxbackupdd/ + Tue, 14 Sep 2021 13:51:16 +0800 + + https://www.small09.top/posts/210914-linuxbackupdd/ + + + + <p>害,谁不是个DD (誰でも 大好き)了呢!</p> +<p>但是这玩意儿太慢了。。。</p> +<h2>起因<span class="hx:absolute hx:-mt-20" id="起因"></span> + <a href="#%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用UOS的时候经常需要备份镜像并分享给其他部门或者办事处的人。传统的方法是进入系统自带的Recovery分区,打开备份软件制作镜像,但是这存在两个问题:A. 镜像特别巨大,系统多大镜像就占多大。B. 必须先拥有系统,才能恢复镜像。最新版的系统中已经去掉了Recovery分区,而LiveCD镜像还不是很好用,于是有了这篇博客,试图寻找一个简单的方法来进行恢复和备份。</p> +<h2>DD说明<span class="hx:absolute hx:-mt-20" id="dd说明"></span> + <a href="#dd%e8%af%b4%e6%98%8e" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>dd指令是一个简单的复制指令,它不管源和目标的编码、格式、数据结构,简单粗暴的把二进制数据从A复制到B。所以恢复的目标硬盘甚至不需要提前分区,因为dd会把分区信息也写入。</p> +<p>dd指令依然是有多少数据占多少空间,所以我们可以使用gzip进行压缩。具体代码后面贴出来。</p> +<p>实际使用中,发现dd指令除了方便,就没有其他优点了。它因为要读取硬盘所有数据(包括垃圾数据),即便用SSD,读盘速度还是会很慢,三刻钟才读了200GiB数据,平均75MB/s,而使用uos自带的deepin-clone,三个系统都备份完了。</p> +<h2>DD备份<span class="hx:absolute hx:-mt-20" id="dd备份"></span> + <a href="#dd%e5%a4%87%e4%bb%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda of<span style="color:#f92672">=</span>/dev/sdb <span style="color:#f92672">=</span>&gt; 备份整个磁盘到另外一个磁盘 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>sda.img <span style="color:#f92672">=</span>&gt; 备份整个磁盘为某个文件 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 备份并且压缩 +</span></span><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M | gzip &gt; sda.img <span style="color:#f92672">=</span>&gt; 指定块大小,备份并压缩(据说能提速)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>DD复原<span class="hx:absolute hx:-mt-20" id="dd复原"></span> + <a href="#dd%e5%a4%8d%e5%8e%9f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>/dev/sdb of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从另一个磁盘恢复回来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda <span style="color:#f92672">=</span>&gt;从压缩的文件恢复出来 +</span></span><span style="display:flex;"><span>gzip -dc sda.img | dd of<span style="color:#f92672">=</span>/dev/sda bs<span style="color:#f92672">=</span>1M <span style="color:#f92672">=</span>&gt;前面指定了块大小,这里也需要</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>本文参考(抄)了以下内容:</p> +<p><a href="http://vpsxyz.com/archives/87"target="_blank" rel="noopener">Linux 下使用 dd 和 gzip 命令来代替 Ghost 做磁盘镜像</a></p> +<p><a href="https://blog.csdn.net/weixin_33971130/article/details/85662628"target="_blank" rel="noopener">linux dd实现磁盘完整全盘镜像备份backup,恢复recover(restore)</a></p> +<p>还有一些查找过程中作用不大的页面,就不贴出来了(诶嘿(╹ڡ╹ )</p> + + + + + Linux处理多触屏的终极解决方案 + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + Tue, 24 Aug 2021 00:00:00 +0800 + + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + + + + <p>一般Linux操作系统接入多个触摸屏的最终解!</p> +<p>从<a href="https://www.small09.top/posts/210311-multitouchscreeninlinux/"target="_blank" rel="noopener">21年3月中旬开始</a>,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。</p> +<p><em>重要:</em> 本文全文基于软件<code>xinput</code></p> +<p><em>更重要:</em> 如简介所说,仍有一种方法无法对应,具体为<code>【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】</code>,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。</p> +<h2>0、核心:xinput<span class="hx:absolute hx:-mt-20" id="0核心xinput"></span> + <a href="#0%e6%a0%b8%e5%bf%83xinput" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)</p> +<blockquote> + <p>xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级</p> +<p>xinput list &ndash;id-only : 仅仅列出设备ID(序号,每次开机都可能改变)</p> +<p>xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕</p> +<p>xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。</p> + +</blockquote> +<h2>1、简约:两个完全不同的触摸显示器<span class="hx:absolute hx:-mt-20" id="1简约两个完全不同的触摸显示器"></span> + <a href="#1%e7%ae%80%e7%ba%a6%e4%b8%a4%e4%b8%aa%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c%e7%9a%84%e8%a7%a6%e6%91%b8%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>xinput map-to-output &quot;$DEVICE NAME A&quot; PORT-A</code></p> +<p>这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。</p> +<p>首先通过xinput查看所有的输入设备:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>~$ xinput +</span></span><span style="display:flex;"><span>┌ Virtual core pointer id<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">[</span>master pointer <span style="color:#f92672">(</span>3<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK TLITEK-TP id<span style="color:#f92672">=</span><span style="color:#ae81ff">9</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK Multi-Touch id<span style="color:#f92672">=</span><span style="color:#ae81ff">7</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">(</span>下略<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>可以看到<code>ILITEK ILITEK-TP</code>和<code>ILITEK Multi-Touch</code>显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:</p> +<p><code>xinput map-to-output &quot;ILITEK ILITEK-TP&quot; HDMI-0</code></p> +<p><code>xinput map-to-output &quot;ILITEK Multi-Touch&quot; HDMI-1</code></p> +<p>这种方式的要点是1.必须带英文<code>&quot;&quot;</code>,2.引号内名称须完整。<code>map-to-output</code>所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。</p> +<h2>2、复杂但通用:VID:PID不同的显示器<span class="hx:absolute hx:-mt-20" id="2复杂但通用vidpid不同的显示器"></span> + <a href="#2%e5%a4%8d%e6%9d%82%e4%bd%86%e9%80%9a%e7%94%a8vidpid%e4%b8%8d%e5%90%8c%e7%9a%84%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。</p> +<p>通过<code>xinput list-props [id]</code>可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。<code>list-props</code>的结果示例如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#f92672">(</span>后续再补<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Device Product ID</code>一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA<span style="color:#f92672">=</span>222a +</span></span><span style="display:flex;"><span>PIDA<span style="color:#f92672">=</span><span style="color:#ae81ff">0001</span> +</span></span><span style="display:flex;"><span>PORTA<span style="color:#f92672">=</span>VGA-0 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 或者这里直接填写Device Product ID里面的结果,先VID后PID。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 字母O,不是数字0,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$VIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span>PIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$PIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># =========自动处理步骤,请勿随意修改==========</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 根据设备ID取其序号</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3、屏映射,多屏重复本段</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$VIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$PIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> xinput map-to-output $I $PORTA +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span>exit <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这个脚本使用<code>xinput list --id-only</code>仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用<code>map-to-output</code>进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。</p> +<h2>3、[2]的进阶版<span class="hx:absolute hx:-mt-20" id="32的进阶版"></span> + <a href="#32%e7%9a%84%e8%bf%9b%e9%98%b6%e7%89%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。</p> +<p>在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是<code>pyudev</code>库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装<code>python3+pyudev</code>,并且使用desktop文件在开机时启动,即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/python3</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- coding: UTF-8 --</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- Author: Blues --</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pyudev +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>context <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Context() +</span></span><span style="display:flex;"><span>monitor <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Monitor<span style="color:#f92672">.</span>from_netlink(context) +</span></span><span style="display:flex;"><span>monitor<span style="color:#f92672">.</span>filter_by(subsystem<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;hid&#39;</span>) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> device <span style="color:#f92672">in</span> iter(monitor<span style="color:#f92672">.</span>poll, <span style="color:#66d9ef">None</span>): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>driver <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;hid-multitouch&#34;</span>: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;add&#34;</span> <span style="color:#f92672">or</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;change&#34;</span>: +</span></span><span style="display:flex;"><span> os<span style="color:#f92672">.</span>system(<span style="color:#e6db74">&#34;/var/my-touchscreen/remap-screen&#34;</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照<em>设备名、VID、UID、物理接口</em>四大特征,自动映射到对应的显示屏上了。</p> + + + + + Linux中获得AMD显卡的状态信息 + https://www.small09.top/posts/210719-gpuinfoinlinux/ + Mon, 19 Jul 2021 11:00:07 +0800 + + https://www.small09.top/posts/210719-gpuinfoinlinux/ + + + + <p>在Linux下获得AMD GPU的状态和温度信息。</p> +<p>在Linux下如果要查看Nvidia GPU的状态信息,可以使用Nvidia驱动自带的工具。而AMD显卡一般都是使用的开源驱动,这种情况下如何获得显卡的状态和占用呢?</p> +<p><em>文章部分图片之后再附</em></p> +<h2>1、获取GPU信息<span class="hx:absolute hx:-mt-20" id="1获取gpu信息"></span> + <a href="#1%e8%8e%b7%e5%8f%96gpu%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>如需获得GPU的信息(型号、驱动)等,如果系统提供类似*“设备管理器”*的工具可以打开查看。或者就是用<code>lspci</code>指令查看。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 获得所有PCIe设备,在其中找到AMD GPU</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>exam@ple<span style="color:#f92672">]</span>:~$ lspci +</span></span><span style="display:flex;"><span>00:02.0 VGA compatible controller: xxxxxxxx +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>exam@ple<span style="color:#f92672">]</span>:~$ lspci -v -s 00:02.0 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 这里就会显示显卡的具体信息了,包括型号、驱动等,之后补充</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>2、GPU占用率<span class="hx:absolute hx:-mt-20" id="2gpu占用率"></span> + <a href="#2gpu%e5%8d%a0%e7%94%a8%e7%8e%87" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>显卡占用率是一个能反映负载的直观指标,AMD显卡可以通过安装<code>radeontop</code>软件查看。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210719133426.png" alt="radeontop界面" loading="lazy" /></p> +<p>最上面是GPU管道使用,大概类似于总体使用率的东西;最下面是内存使用率,使用sudo权限能看到更多信息。其他项目可以到radeontop的<a href="https://github.com/clbr/radeontop"target="_blank" rel="noopener">Github页面</a>查看。</p> +<h2>3、温度<span class="hx:absolute hx:-mt-20" id="3温度"></span> + <a href="#3%e6%b8%a9%e5%ba%a6" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>特指GPU温度。查阅这篇文章所涉及的知识点的最初目的,也是确认机箱过热是否是GPU的问题。</p> +<p>GPU温度使用第三方软件查看,并使其实时更新。查看温度使用<code>sensors</code>指令,通过安装<code>lm-sensors</code>实现。执行sensors时只会显示一次,我们可以利用watch指令定时刷新温度显示:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>watch -n <span style="color:#ae81ff">2</span> sensors +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 每隔2秒执行sensors指令显示结果</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>![watch执行结果](图片占位)</p> + + + + + 免密码使用sudo + https://www.small09.top/posts/210630-sudowithoutpasswd/ + Wed, 30 Jun 2021 15:14:40 +0800 + + https://www.small09.top/posts/210630-sudowithoutpasswd/ + + + + <p>记录一种可以免密码使用sudo的方法。</p> +<p>每次使用sudo提权,常常要输密码太烦了,所以想给自己赋予免密码的特权。</p> +<ol> +<li> +<p>首先登录root用户: <code>sudo -i</code></p> +</li> +<li> +<p>这个操作的核心文件是<code>/etc/sudoers</code>,可以看到这个文件是只读的,要修改他需要先赋予写入权限。</p> +</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod <span style="color:#ae81ff">666</span> /etc/sudoers</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210705092601.png" alt="文件是只读权限" loading="lazy" /></p> +<ol start="3"> +<li>使用vim修改文件,<code>vi /etc/sudoers</code>,把当前用户和用户组加入,如图</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 表示为该用户免密码</span> +</span></span><span style="display:flex;"><span>yourname ALL<span style="color:#f92672">=(</span>ALL:ALL<span style="color:#f92672">)</span> NOPASSWD:ALL +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 表示为该用户组免密码</span> +</span></span><span style="display:flex;"><span>%yourname ALL<span style="color:#f92672">=(</span>ALL:ALL<span style="color:#f92672">)</span> NOPASSWD:ALL +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 不用严格按照次形式,与你的这个文件中root的格式对应即可。</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ol start="4"> +<li><code>:wq</code>保存,把sodoers改回只读</li> +</ol> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>chmod <span style="color:#ae81ff">440</span> /etc/sudoers</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210705092637.png" alt="文件修改的内容" loading="lazy" /></p> +<ol start="5"> +<li>完成~~~ 把终端关掉再次打开就应该能看到效果了。</li> +</ol> +<blockquote> + <p><strong>PS. 不可使用sudo chmod</strong></p> +<p>一定要直接sudo -i进入root用户,如果使用sudo chmod以普通用户的身份修改sudoers文件的话,改完会发现无法使用sudo指令把权限改回去了,提示“任何人可读写,无法找到有效的sudoers”。其实还有一种更加安全的操作,但是那个编辑器用起来没有vim顺手,</p> + +</blockquote> +<p>参考:https://blog.csdn.net/wxqee/article/details/72718869</p> + + + + + deepin和UOS,快捷方式,开机自启动 + https://www.small09.top/posts/210429-deepinautorun/ + Thu, 29 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210429-deepinautorun/ + + + + <p>修正触屏的脚本总是运行失败,于是找到在Deepin下能够在桌面启动后自动运行的方法。这样的触屏映射就稳定了。经测试在Ubuntu 1604中也适用。</p> +<h2>桌面快捷方式<span class="hx:absolute hx:-mt-20" id="桌面快捷方式"></span> + <a href="#%e6%a1%8c%e9%9d%a2%e5%bf%ab%e6%8d%b7%e6%96%b9%e5%bc%8f" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>deepin的桌面快捷方式是以<code>*.desktop</code>文件的形式实现的,所以我们需要添加自定义快捷方式的话,只要在桌面添加要给desktop格式的文本文件,编辑内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>[Desktop Entry] +Name=ddd +Exec=&#34;/usr/sbin/xx.sh&#34; +Type=Application</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Desktop Entry</code>表示声明该文件为快捷方式文件。<code>Name</code>是这个文件要显示的名字,设置了之后就可以隐藏原名称和扩展名。<code>Exec</code>是程序的启动脚本,或者启动指令。<code>Type</code>是程序类型,在开始菜单的分类中可以体现。以上四行是必须要有的参数,还有一些其他的参数不影响执行。Exec的脚本必须赋予777权限以执行。</p> +<p>把这个文件放在<code>~/Desktop</code>,就可以看到桌面上多了一个快捷方式了。</p> +<h2>开始菜单<span class="hx:absolute hx:-mt-20" id="开始菜单"></span> + <a href="#%e5%bc%80%e5%a7%8b%e8%8f%9c%e5%8d%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo cp ~/Desktop/刚刚的.desktop /usr/share/applications</code>,过一会或者注销重新登录就能看到开始菜单的响应类型下出现这个快捷方式了。</p> +<h2>开机自动执行<span class="hx:absolute hx:-mt-20" id="开机自动执行"></span> + <a href="#%e5%bc%80%e6%9c%ba%e8%87%aa%e5%8a%a8%e6%89%a7%e8%a1%8c" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><h3>UOS/Deepin和Ubuntu<span class="hx:absolute hx:-mt-20" id="uosdeepin和ubuntu"></span> + <a href="#uosdeepin%e5%92%8cubuntu" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p><code>cp ~/Desktop/刚刚的.desktop ~/.config/autostart</code></p> +<p>需要这个的原因主要是我执行触摸屏映射的指令经常会在桌面启动前(<code>lightdm.service</code>)执行,导致error从而无法进入桌面,将脚本放进<code>/etc/profile.d</code>之后第二天就失效了,所以需要一个在登录桌面后就实现自动执行的功能,多种方法对比下来还是放进桌面环境的开机自启文件夹比较靠谱。这样一般会在进入桌面后5-10秒自动执行,稳定性大幅提高。</p> +<p>经过实际查阅资料和测试,Ubuntu1604也完全适用上面的步骤,只不过可能需要在<code>~/.config/</code>下手动创建一个<code>autostart</code>目录。由于1604已经停止支持,许多人转入了更新的ubuntu系统,猜测近几年的也可以尝试一下。</p> +<h3>麒麟和其他使用X11桌面的Linux<span class="hx:absolute hx:-mt-20" id="麒麟和其他使用x11桌面的linux"></span> + <a href="#%e9%ba%92%e9%ba%9f%e5%92%8c%e5%85%b6%e4%bb%96%e4%bd%bf%e7%94%a8x11%e6%a1%8c%e9%9d%a2%e7%9a%84linux" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>本条目包含UOS和Ubuntu、deepin系统</p> +<p>自启动目录为<code>/etc/xdg/autostart</code>,同样的desktop文件放进去即可。但是麒麟中我使用上面的基础desktop报错,可能需要添加其他内容。</p> + + + + + Linux启用四个以上的串口 + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + Tue, 20 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + + + + <p>工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。</p> +<h2>先说结论<span class="hx:absolute hx:-mt-20" id="先说结论"></span> + <a href="#%e5%85%88%e8%af%b4%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e"># 1.修改开机grub文件</span> +</span></span><span style="display:flex;"><span>sudo vi /etc/default/grub +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2.在&#34;GRUB_CMDLINE_LINUX_DEFAULT=&#34;一行的结尾空格,添加内容</span> +</span></span><span style="display:flex;"><span> 8250.nr_uarts<span style="color:#f92672">=</span><span style="color:#ae81ff">8</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3.更新开机引导并重启</span> +</span></span><span style="display:flex;"><span>sudo update-grub</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>再说细节<span class="hx:absolute hx:-mt-20" id="再说细节"></span> + <a href="#%e5%86%8d%e8%af%b4%e7%bb%86%e8%8a%82" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>给带有多串口的工控机安装国产Linux操作系统UOS,发现<code>/dev/ttyS</code>只有四个,而且一个都不通,其实这是Linux 的通病,如果发行商没有提前配置,我们只能看到四个不能用的串口。但是工控机都有8-16个串口,不得不想办法让他们能够正常使用,而许多串口芯片只需要基本的驱动就能运行,也不提供专有驱动的源代码编译<font size=2 color=#ff0000>(一般安装专用驱动后,串口名称会改变,比如xr17v358芯片的驱动安装后名称变成ttyXR0)</font>,我们只有从linux本身下手了。</p> +<p>经过多番查找资料,虽然不知道为什么,但是我们应该修改<code>/etc/default/grub</code>文件,其中有一行是这样的:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>GRUB_CMDLINE_LINUX_DEFAULT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;这里面 的内容 先不管 它&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>我看了UOS(含Deepin)、Ubuntu和服务器的Centos7都有,在这一行的最后添加<code>8250.nr_uart=32</code>,其中数字&lt;=32都行,效果如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>GRUB_CMDLINE_LINUX_DEFAULT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;这里面 的内容 先不管 它 8250.nr_uarts=32&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后更新一下系统的引导</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo update-gurb +</span></span><span style="display:flex;"><span>reboot</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>重启完了你就能看到更多的串口啦!配合<a href="https://www.small09.top/?p=24"target="_blank" rel="noopener">修改udev规则</a>,你就可以用普通用户的权限使用串口通信了。但是请注意其他隐患,比如这个芯片配合基本驱动只能完成基础的收发功能,流控的RTS、CTS等还是需要专用驱动实现的!</p> +<h2>最后吐槽<span class="hx:absolute hx:-mt-20" id="最后吐槽"></span> + <a href="#%e6%9c%80%e5%90%8e%e5%90%90%e6%a7%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>为了串口问题我找了一下午的资料,最后才在外网找到了一个能用的。国内的教程都是说要修改<code>/boot/gurb/gurb2</code>,宝友,这可不兴改啊,这个改坏了系统可就没了啊!这些文件普通用户连浏览权限都没就可见其重要性。</p> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><a href="https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/"target="_blank" rel="noopener">https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/</a></p> +<p>核心步骤就参考了这个,在Linux方面大家还是多用google,多用英文去搜索吧!</p> + + + + + Linux修改mac地址 + https://www.small09.top/posts/210328-linuxchangemacip/ + Sun, 28 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210328-linuxchangemacip/ + + + + <p>当Linux的图形界面没有或失效的时候,该如何更改mac地址?</p> +<h1>Linux调整网卡MAC地址(指令)</h1><p>有的时候需要使用欺骗的方法为电脑设置新的mac,比如存在上网网卡限制,或者想要隐藏自己的真实mac地址。这里列举三种修改mac的方法,适用于Debian系的Linux,其他的应该类似。</p> +<h2>方法一:ifconfig<span class="hx:absolute hx:-mt-20" id="方法一ifconfig"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%b8%80ifconfig" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># ifconfig 查看网卡名称$NAME</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 需要sudo权限</span> +</span></span><span style="display:flex;"><span>ifconfig $NAME down +</span></span><span style="display:flex;"><span>ifconfig $NAME hw ether $NEW_MAC +</span></span><span style="display:flex;"><span>ifconfig $NAME up +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 使用ifconfig检查有没有改变</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>方法二:macchanger<span class="hx:absolute hx:-mt-20" id="方法二macchanger"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%ba%8cmacchanger" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo apt install macchanger</code>,安装过程提示是否自启,随便。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ifconfig $NAME down +</span></span><span style="display:flex;"><span>macchanger -m $NEW_MAC $NAME +</span></span><span style="display:flex;"><span>ifconfig $NAME up</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>ps:macchanger可以附加多种参数,如-m指定mac地址,-s查看状态,-r随机地址</p> +<h2>备注:<span class="hx:absolute hx:-mt-20" id="备注"></span> + <a href="#%e5%a4%87%e6%b3%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上两种方法是软件层面的修改,不会对硬件造成损伤,重启之后就能恢复如初。如果是要长期有效,可以把这些命令写入脚本开机自启。除了这两个,还有另一种软修改的方法,因为我总是提示失败就遗忘了。</p> +<p>有些方法会提到修改/etc目录下的文件实现MAC地址的修改,但是我一个文件都没找到(UOS),这个是因系统而异的,不同的发行版可以选择性的编译这些文件,就会造成有人有有人没有,因为没有实践,所以就不写在这里了。</p> +<h2>方法三:ethtool(慎用)<span class="hx:absolute hx:-mt-20" id="方法三ethtool慎用"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%b8%89ethtool%e6%85%8e%e7%94%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上方法都失效,可以直接修改网卡的物理MAC地址,修改后无法恢复,慎用</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 首先检查mac是否支持修改</span> +</span></span><span style="display:flex;"><span>ethtool -i $NAME +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果 supports-eeprom-access: no,大概率不支持修改</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 查看网卡的mac寄存器</span> +</span></span><span style="display:flex;"><span>ethtool -e $NAME +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果无法读取,大概率不支持修改</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 修改mac,按字节修改</span> +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">0</span> value 0xAA +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">1</span> value 0xBB +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">2</span> value 0xCC +</span></span><span style="display:flex;"><span>...共六行 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果修改无效,则驱动不支持修改</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>如果以上方法都无法修改,则建议申请新的ip。。。<span class="hx:absolute hx:-mt-20" id="如果以上方法都无法修改则建议申请新的ip"></span> + <a href="#%e5%a6%82%e6%9e%9c%e4%bb%a5%e4%b8%8a%e6%96%b9%e6%b3%95%e9%83%bd%e6%97%a0%e6%b3%95%e4%bf%ae%e6%94%b9%e5%88%99%e5%bb%ba%e8%ae%ae%e7%94%b3%e8%af%b7%e6%96%b0%e7%9a%84ip" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><em><font size="1">文章首发于<a href="small09.top" target="前往火柴盒" title="前往Kushidou个人博客" rel="noopener">香风家的小别墅</a>,转载请携带出处。</font></em></p> + + + + + Linux串口和udev理解 + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + + + + <p>配置串口权限,顺便看一下linux的udev规则。</p> +<h2>串口的问题<span class="hx:absolute hx:-mt-20" id="串口的问题"></span> + <a href="#%e4%b8%b2%e5%8f%a3%e7%9a%84%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在现场开发的同事反映UOS工控机没有配置串口,每次都需要sudo才能使用。(明明在家里你们都会自己配置好环境与权限,为什么到了现场反而不会了需要我提供协助呢?)</p> +<p>我之前没考虑过这个问题,认为串口就是得root权限操作才安全。但是既然提出了需求,就赶紧百度并测试一下吧。</p> +<h2>赋予普通用户串口权限的两种方法<span class="hx:absolute hx:-mt-20" id="赋予普通用户串口权限的两种方法"></span> + <a href="#%e8%b5%8b%e4%ba%88%e6%99%ae%e9%80%9a%e7%94%a8%e6%88%b7%e4%b8%b2%e5%8f%a3%e6%9d%83%e9%99%90%e7%9a%84%e4%b8%a4%e7%a7%8d%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在百度上,给普通用户串口权限有两种主流方法(可能2会了,Google上不去),一种是将用户放入特殊的用户组,从而获得权限。另一种是加入udev规则,在串口插入时自动更改权限。</p> +<p>我个人更倾向于使用第二种方案,因为公司中不同的人装系统就可能起不同的名字,之后进入生产环境可能又会起新的名字,所以采用第二种设置规则的办法,便于之后制作恢复镜像和一键部署。</p> +<h2>xxx.rules<span class="hx:absolute hx:-mt-20" id="xxxrules"></span> + <a href="#xxxrules" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo vi /etc/udev/rules.d/70-tty-usb-rules</code>,写入以下内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>KERNEL<span style="color:#f92672">==</span><span style="color:#e6db74">&#34;ttyS*&#34;</span>,MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0666&#34;</span> +</span></span><span style="display:flex;"><span>KERNEL<span style="color:#f92672">==</span><span style="color:#e6db74">&#34;ttyUSB*&#34;</span>,MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0666&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这一段的意思是匹配所有ttyS和ttyUSB,插入时都赋予0666的权限,即所有用户都可读可写。重新加载一下设备,就可以按规则配置权限进行操作了。</p> +<h2>udev规则<span class="hx:absolute hx:-mt-20" id="udev规则"></span> + <a href="#udev%e8%a7%84%e5%88%99" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>udev是什么?udev是Linux下的设备文件管理方式,运行在用户侧(devfs在内核侧管理设备文件)。有了udev,<code>/dev</code>目录下的文件就都是真实存在的已连接的设备了。</p> +<p>udev的规则都保存在<code>/etc/udev/rules.d</code>目录下,文件名的格式为<code>数字-名称.rules</code>,数字表示加载顺序,一般是从小往大加载;名称按功能自定义,最后以.rules结尾。这些规则会在设备接入时生效。</p> +<p>规则文件按行执行,每行分为两个部分:匹配和赋值,每个参数使用逗号,分割。</p> +<blockquote> + <p>(这段复制自引用2,仅供参考)。 +<strong>1、udev 规则的所有操作符</strong> +<strong>==</strong>  比较键、值,若等于,则该条件满足; +<strong>!=</strong>  比较键、值,若不等于,则该条件满足; +<strong>=</strong>   对一个键赋值; +<strong>+=</strong>  为一个表示多个条目的键赋值。 +<strong>:=</strong>  对一个键赋值,并拒绝之后所有对该键的改动,防止后面的规则文件对该键重赋值。</p> +<p><strong>2、udev 规则的匹配键</strong> +<strong>ACTION</strong>        事件 (uevent) 的行为,例如:add、remove。 +<strong>KERNEL</strong>         内核设备名称,例如:sda, cdrom。 +<strong>DEVPATH</strong>        设备的 devpath 路径。 +<strong>SUBSYSTEM</strong>       设备的子系统名称,例如:sda 的子系统为 block。 +<strong>BUS</strong>           设备在 devpath 里的总线名称,例如:usb。 +<strong>DRIVER</strong>         设备在 devpath 里的设备驱动名称,例如:ide-cdrom。 +<strong>ID</strong>            设备在 devpath 里的识别号。 +<strong>SYSFS{filename}</strong>    设备的 devpath 路径下,设备的属性文件“filename”里的内容。 +<strong>ENV{key}</strong>       环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。 +<strong>PROGRAM</strong>        调用外部命令。 +<strong>RESULT</strong>         外部命令 PROGRAM 的返回结果。</p> +<p><strong>3、udev 的重要赋值键</strong> +<strong>NAME</strong>           /dev目录下显示的文件名重命名 +<strong>SYMLINK</strong>          为 /dev下文件进行符号链接,防止覆盖系统默认产生的文件 +<strong>OWNER, GROUP, MODE</strong>  为设备设定权限。 +<strong>ENV{key}</strong>         导入一个环境变量。</p> +<p><strong>4、udev 的值和可调用的替换操作符</strong> +Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符: +<strong>$kernel, %k</strong>        设备的内核设备名称,例如:sda、cdrom。 +<strong>$number, %n</strong>        设备的内核号码,例如:sda3 的内核号码是 3。 +<strong>$devpath, %p</strong>       设备的 devpath路径。 +<strong>$id, %b</strong>          设备在 devpath里的 ID 号。 +<strong>$sysfs{file}, %s{file}</strong>   设备的 sysfs里 file 的内容。其实就是设备的属性值。 +<strong>$env{key}, %E{key}</strong>   一个环境变量的值。 +<strong>$major, %M</strong>        设备的 major 号。 +<strong>$minor %m</strong>        设备的 minor 号。 +<strong>$result, %c</strong>        PROGRAM 返回的结果。 +<strong>$parent, %P</strong>       父设备的设备文件名。 +<strong>$root, %r</strong>         udev_root的值,默认是 /dev/。 +<strong>$tempnode, %N</strong>      临时设备名。 +<strong>%%</strong>            符号 % 本身。 +<strong>$$</strong>             符号 $ 本身。</p> + +</blockquote> +<h2>参考<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://www.cnblogs.com/qixianyu/p/6869744.html +[2]https://www.cnblogs.com/cslunatic/p/3171837.html</p> + + + + + Linux多显示器触屏调试与暂行解决方案 + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + + + + <p>由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。</p> +<p><em>2021-03-22更新部分内容</em> +<em>2021-04-28再次更新内容</em></p> +<p><strong>2021-09-14 文章已有新版本,请访问《<a href="https://www.small09.top/posts/210824-linuxtouchscreenfin/"target="_blank" rel="noopener">Linux处理多触屏的终极解决方案</a>》</strong></p> +<h2>问题来源<span class="hx:absolute hx:-mt-20" id="问题来源"></span> + <a href="#%e9%97%ae%e9%a2%98%e6%9d%a5%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为</p> +<p><code>xinput map-to-output $触屏设备id $显示屏名称</code></p> +<blockquote> + <p>2021-04-28更新</p> +<p><code>xinput map-to-output</code>指令支持触摸屏名称作为参数,比如我通过<code>xinput</code>指令看到有一个触摸屏的设备名称为<code>ILITEK XXXX</code>,那么我映射的时候直接输入</p> +<blockquote> + <p><code>xinput map-to-output &quot;ILITEK XXX&quot; HDMI-1</code></p> + +</blockquote> + +</blockquote> +<h2>尝试解决<span class="hx:absolute hx:-mt-20" id="尝试解决"></span> + <a href="#%e5%b0%9d%e8%af%95%e8%a7%a3%e5%86%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ul> +<li>首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。</li> +<li><del>然后<code>xinput list-props $id</code>查看设备的VID,这个在后续自动化处理有用。</del></li> +<li><code>xrandr</code>查看<code>$显示器名称</code></li> +</ul> +<h2>自动化处理<span class="hx:absolute hx:-mt-20" id="自动化处理"></span> + <a href="#%e8%87%aa%e5%8a%a8%e5%8c%96%e5%a4%84%e7%90%86" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 2021-04-28更新:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 本部分后面的都可以不看了!</span> +</span></span><span style="display:flex;"><span>xinput map-to-output <span style="color:#e6db74">&#34;DEVICE_NAME&#34;</span> DISPLAY +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><strong>仅作归档!下面这些方法过于繁琐!</strong></p> +<p>利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。<code>bash this.sh VID1 VID2 ScreenID</code></p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。</span> +</span></span><span style="display:flex;"><span>MYID_FIRST<span style="color:#f92672">=</span>$1 +</span></span><span style="display:flex;"><span>MYID_SECOND<span style="color:#f92672">=</span>$2 +</span></span><span style="display:flex;"><span>OUT_PUT<span style="color:#f92672">=</span>$3 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#[1]</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> MY_INDEX<span style="color:#f92672">=</span>$I +</span></span><span style="display:flex;"><span> break; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>xinput map-to-output $MY_INDEX $OUT_PUT +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI2</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI1</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput | grep -iw DEVICE_NAME<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID#*id=<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID%% *<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>xinput map-to-output $TOUCH_ID SCREEN_NAME</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),<del>使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id</del>。</p> +<p>经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在<code>~/.bashrc</code>中,这样只需要打开一次终端,就可以实现屏幕重新映射。</p> +<h2>后续问题<span class="hx:absolute hx:-mt-20" id="后续问题"></span> + <a href="#%e5%90%8e%e7%bb%ad%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li>当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。</li> +<li>当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数</li> +<li>如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。</li> +</ol> +<h2>触屏校准(如果需要)<span class="hx:absolute hx:-mt-20" id="触屏校准如果需要"></span> + <a href="#%e8%a7%a6%e5%b1%8f%e6%a0%a1%e5%87%86%e5%a6%82%e6%9e%9c%e9%9c%80%e8%a6%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>网上说的利用校准来区分多触摸屏的方法是<strong>完全错误</strong>的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)</p> +<ul> +<li><code>sudo apt install xinput-calibrator</code>,需要更新源</li> +<li><code>sudo xinput_calibrator</code></li> +<li>将校准信息保存到<code>/usr/share/X11/xorg.conf.d/10-evdev.conf</code>的&quot;Driver evdev”下面一行,重启生效。</li> +</ul> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077</p> + + + + + 使用Docker安装TeamSpeak + https://www.small09.top/posts/210218-teamspeakwithdocker/ + Thu, 18 Feb 2021 00:00:00 +0800 + + https://www.small09.top/posts/210218-teamspeakwithdocker/ + + + + <p>TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。</p> +<h2>Teamspeak<span class="hx:absolute hx:-mt-20" id="teamspeak"></span> + <a href="#teamspeak" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。</p> +<p>所以,选择docker安装ts就是一个很不错的选择。</p> +<h2>Docker及其安装<span class="hx:absolute hx:-mt-20" id="docker及其安装"></span> + <a href="#docker%e5%8f%8a%e5%85%b6%e5%ae%89%e8%a3%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。</p> +<p>我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo yum install docker <span style="color:#75715e">#安装docker程序,会自动选择合适的</span> +</span></span><span style="display:flex;"><span>sudo systemctl start docker <span style="color:#75715e">#启动docker服务</span> +</span></span><span style="display:flex;"><span>sudo systemctl enable docker <span style="color:#75715e">#允许docker开机自启</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>Docker安装TS<span class="hx:absolute hx:-mt-20" id="docker安装ts"></span> + <a href="#docker%e5%ae%89%e8%a3%85ts" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak</code></p> +<p>直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。</p> +<h2>端口<span class="hx:absolute hx:-mt-20" id="端口"></span> + <a href="#%e7%ab%af%e5%8f%a3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>从指令可以看出,docker需要使用到<strong>9987,10011,30033</strong>三个端口,所以需要到服务器后台开启防火墙上对应端口。</p> +<h2>管理员密钥<span class="hx:absolute hx:-mt-20" id="管理员密钥"></span> + <a href="#%e7%ae%a1%e7%90%86%e5%91%98%e5%af%86%e9%92%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。</p> +<p><code>docker ps -a</code>查找所有容器。</p> +<p><code>docker logs -f [CONTATINER ID]</code>查看log,找到token即可。客户端设置之后再说。</p> +<p>密钥只会在第一次运行时打印,注意保存。</p> +<p><em>参考:<a href="https://woolio.cn/683/"target="_blank" rel="noopener">https://woolio.cn/683/</a></em></p> + + + + + diff --git a/public/tags/nas/index.html b/public/tags/nas/index.html new file mode 100644 index 0000000..4e9ed9d --- /dev/null +++ b/public/tags/nas/index.html @@ -0,0 +1,510 @@ + + + + + + + +NAS – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/nas/index.xml b/public/tags/nas/index.xml new file mode 100644 index 0000000..6942e59 --- /dev/null +++ b/public/tags/nas/index.xml @@ -0,0 +1,107 @@ + + + 香风家的火柴盒 – NAS + https://www.small09.top/tags/nas/ + Recent content in NAS on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 15 Nov 2021 10:02:30 +0800 + + + + + + + + + + + 记一次拯救NAS硬盘 + https://www.small09.top/posts/211115-fixnasdriver/ + Mon, 15 Nov 2021 10:02:30 +0800 + + https://www.small09.top/posts/211115-fixnasdriver/ + + + + <p>更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据?</p> +<p><em>图片稍后补充。</em></p> +<h2>v 0 起因<span class="hx:absolute hx:-mt-20" id="v-0-起因"></span> + <a href="#v-0-%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0.</p> +<p>考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。</p> +<p>为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。</p> +<h2>v-1 杯具<span class="hx:absolute hx:-mt-20" id="v-1-杯具"></span> + <a href="#v-1-%e6%9d%af%e5%85%b7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。</p> +<h2>v-2 尝试<span class="hx:absolute hx:-mt-20" id="v-2-尝试"></span> + <a href="#v-2-%e5%b0%9d%e8%af%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内<strong>写入任何内容</strong>,也<strong>没有格式化</strong>。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。</p> +<p>为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。</p> +<p>经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。</p> +<p>搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。</p> +<p>难道就这么放弃了么?</p> +<h2>v 1 曙光<span class="hx:absolute hx:-mt-20" id="v-1-曙光"></span> + <a href="#v-1-%e6%9b%99%e5%85%89" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。</p> +<p>我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?!</p> +<p>开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。</p> +<p>把小硬盘用易驱线连上树莓派,<code>lsblk</code>看到这个硬盘有三个分区,其中<code>/dev/sda3</code>下面还有一个<code>md127</code>,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。</p> +<h2>v 2 得到数据<span class="hx:absolute hx:-mt-20" id="v-2-得到数据"></span> + <a href="#v-2-%e5%be%97%e5%88%b0%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>安装<code>mdadm</code>软件包,我的树莓派不带这个,然后就能挂载raid盘了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount /dev/md127 ~/disk</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个<code>NasData</code>目录,这正是我当时保存文件的地方。cd进去,在!都还在!</p> +<h2>v 3 移动数据<span class="hx:absolute hx:-mt-20" id="v-3-移动数据"></span> + <a href="#v-3-%e7%a7%bb%e5%8a%a8%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。</p> +<p>在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rsync -vrultD --exclude <span style="color:#e6db74">&#39;文件名&#39;</span> --exclude <span style="color:#e6db74">&#39;目录名&#39;</span> ~/disk/NasData/* ~/Nasdata/临时目录 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。</p> +<h2>v 4 结论<span class="hx:absolute hx:-mt-20" id="v-4-结论"></span> + <a href="#v-4-%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。</p> +<p>这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。</p> +<p>总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢?</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202111150957877.png" alt="image-20211115095410121" loading="lazy" /></p> + + + + + diff --git a/public/tags/python/index.html b/public/tags/python/index.html new file mode 100644 index 0000000..fa5010b --- /dev/null +++ b/public/tags/python/index.html @@ -0,0 +1,510 @@ + + + + + + + +Python – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/python/index.xml b/public/tags/python/index.xml new file mode 100644 index 0000000..14db678 --- /dev/null +++ b/public/tags/python/index.xml @@ -0,0 +1,166 @@ + + + 香风家的火柴盒 – Python + https://www.small09.top/tags/python/ + Recent content in Python on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Fri, 08 Sep 2023 00:00:00 +0800 + + + + + + + + + + + Python socket 收取POST协议内容时没有payload + https://www.small09.top/posts/230908-py-post/ + Fri, 08 Sep 2023 00:00:00 +0800 + + https://www.small09.top/posts/230908-py-post/ + + + + <p>使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况</p> +<p>最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。</p> +<p>服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sock <span style="color:#f92672">=</span> socket<span style="color:#f92672">.</span>socket(socket<span style="color:#f92672">.</span>AF_INET, socket<span style="color:#f92672">.</span>SOCK_STREAM) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>bind((self<span style="color:#f92672">.</span>host, self<span style="color:#f92672">.</span>port)) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>listen(<span style="color:#ae81ff">128</span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>(client, address) <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>sock<span style="color:#f92672">.</span>accept() +</span></span><span style="display:flex;"><span>th <span style="color:#f92672">=</span> Thread(target<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>accept_request, args<span style="color:#f92672">=</span>(client, address)) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">accept_request</span>(client, address): +</span></span><span style="display:flex;"><span> data <span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) +</span></span><span style="display:flex;"><span> req <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>decode(<span style="color:#e6db74">&#34;utf-8&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 处理数据的具体方法</span> +</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> process_response(req, client_addr) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>send(response) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># clean up</span> +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>shutdown(<span style="color:#ae81ff">1</span>) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>close()</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。</p> +<p><strong>=====2024.01.04更新=======</strong></p> +<p>下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。</p> +<p>其实用flask接收和处理数据会更加方便快捷:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> flask <span style="color:#f92672">import</span> Flask, request +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 实例化flask</span> +</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> Flask(__name__) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 接收和处理post的方法</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.route</span>(<span style="color:#e6db74">&#39;/test&#39;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#39;POST&#39;</span>]) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv_file_www</span>(): +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post的传入参数</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>args<span style="color:#f92672">.</span>to_dict()) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post所承载的数据</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>data) +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;OK&#39;</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;__main__&#39;</span>: +</span></span><span style="display:flex;"><span> app<span style="color:#f92672">.</span>run(port<span style="color:#f92672">=</span><span style="color:#ae81ff">31408</span>, host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0.0.0.0&#34;</span>,debug<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。</p> +<p><strong>======以下是原来的内容==========</strong></p> +<p>经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,<strong>必须不断的recv才能完整接收数据</strong>。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。</p> +<p>但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:</p> +<p>POST的报文头会包含一个<code>Content-length</code>的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。</p> +<p>那么将上面代码的<code>client_sock.recv()</code>部分改写成下面的方法:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv</span>(self, client_sock): +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> <span style="color:#ae81ff">8192</span> <span style="color:#75715e"># 记录每次接收的长度</span> +</span></span><span style="display:flex;"><span> lrecv_all<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> <span style="color:#75715e"># 记录数据总长度 </span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">=</span> bytes() <span style="color:#75715e"># 接收的所有数据存入这里</span> +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 解析的payload长度</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 用于记录是否接收到了payload,以及实时记录接收到payload的长度</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span> : +</span></span><span style="display:flex;"><span> part_body<span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) <span style="color:#75715e"># 循环接收数据</span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">+=</span> part_body <span style="color:#75715e"># 将接收的数据写入data_body</span> +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> len(part_body) <span style="color:#75715e"># 获取本次接收长度</span> +</span></span><span style="display:flex;"><span> lrecv_all <span style="color:#f92672">+=</span> lrecved <span style="color:#75715e"># 计算总长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>: <span style="color:#75715e"># 没有接收到payload部分时,才进入下面的判断流程</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">3</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;GET&#39;</span>: <span style="color:#75715e"># 由于我get方法没出现过断开的问题,因此直接返回即可。</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;POST&#39;</span>: +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> count_payload_len(data_body) <span style="color:#75715e"># 解析头部,计算payload部分的长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> lrecved <span style="color:#f92672">&lt;</span> payload_len: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 偶尔会出现能接收到payload的情况,所以多加了一个判断。</span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 能接收payload肯定总长度大于payload_len的</span> +</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;no payloads, keep recving...&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 计算post报文的总长度,借用payload_flag变量</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> lrecved <span style="color:#f92672">+</span> payload_len +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 当接收到的总长度 &gt;= POST的计算长度,表示所有数据接收完成,退出</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">&lt;=</span> lrecv_all: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> data_body</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>里面解析headers的代码如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">count_payload_len</span>(data): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> d <span style="color:#f92672">in</span> data<span style="color:#f92672">.</span>split(<span style="color:#e6db74">b</span><span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">&#39;</span>): <span style="color:#75715e"># 通过\r\n来分隔报文头</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;LENGTH&#34;</span> <span style="color:#f92672">in</span> d<span style="color:#f92672">.</span>upper()<span style="color:#f92672">.</span>decode(): <span style="color:#75715e"># 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> int(d<span style="color:#f92672">.</span>decode()<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;:&#34;</span>)[<span style="color:#ae81ff">1</span>]) <span style="color:#75715e"># 分隔出payload长度并返回</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。</p> +<p>对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。</p> + + + + + diff --git a/public/tags/server/index.html b/public/tags/server/index.html new file mode 100644 index 0000000..af18d70 --- /dev/null +++ b/public/tags/server/index.html @@ -0,0 +1,510 @@ + + + + + + + +Server – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/server/index.xml b/public/tags/server/index.xml new file mode 100644 index 0000000..8651c7e --- /dev/null +++ b/public/tags/server/index.xml @@ -0,0 +1,166 @@ + + + 香风家的火柴盒 – Server + https://www.small09.top/tags/server/ + Recent content in Server on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Fri, 08 Sep 2023 00:00:00 +0800 + + + + + + + + + + + Python socket 收取POST协议内容时没有payload + https://www.small09.top/posts/230908-py-post/ + Fri, 08 Sep 2023 00:00:00 +0800 + + https://www.small09.top/posts/230908-py-post/ + + + + <p>使用Python搭建简单服务器的时候,只能收到headers,收不到POST内容的情况</p> +<p>最近做了一个简单的http服务器,用于接收IoT端上传的json格式的日志。</p> +<p>服务端使用socket中的方法接收原始数据,再对原始数据单独进行处理,根据报文头部的GET或者POST等方法进行具体的操作。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sock <span style="color:#f92672">=</span> socket<span style="color:#f92672">.</span>socket(socket<span style="color:#f92672">.</span>AF_INET, socket<span style="color:#f92672">.</span>SOCK_STREAM) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>bind((self<span style="color:#f92672">.</span>host, self<span style="color:#f92672">.</span>port)) +</span></span><span style="display:flex;"><span>sock<span style="color:#f92672">.</span>listen(<span style="color:#ae81ff">128</span>) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>(client, address) <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>sock<span style="color:#f92672">.</span>accept() +</span></span><span style="display:flex;"><span>th <span style="color:#f92672">=</span> Thread(target<span style="color:#f92672">=</span>self<span style="color:#f92672">.</span>accept_request, args<span style="color:#f92672">=</span>(client, address)) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">accept_request</span>(client, address): +</span></span><span style="display:flex;"><span> data <span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) +</span></span><span style="display:flex;"><span> req <span style="color:#f92672">=</span> data<span style="color:#f92672">.</span>decode(<span style="color:#e6db74">&#34;utf-8&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 处理数据的具体方法</span> +</span></span><span style="display:flex;"><span> response <span style="color:#f92672">=</span> process_response(req, client_addr) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>send(response) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># clean up</span> +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>shutdown(<span style="color:#ae81ff">1</span>) +</span></span><span style="display:flex;"><span> client_sock<span style="color:#f92672">.</span>close()</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码是大概的处理流程。在这个流程中,我发现处理POST的数据的时候,经常只能收到headers部分,payload部分的数据不管发几次都是空的。而且接收缓冲区已经扩大到了8192字节,headers部分的长度只有200多字节,IoT端上传的payload也不过上百字节,不应该是超出缓冲区的问题。</p> +<p><strong>=====2024.01.04更新=======</strong></p> +<p>下面的方法突然某一天就失效了,我不清楚是不是python的环境变化导致的。</p> +<p>其实用flask接收和处理数据会更加方便快捷:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> flask <span style="color:#f92672">import</span> Flask, request +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 实例化flask</span> +</span></span><span style="display:flex;"><span>app <span style="color:#f92672">=</span> Flask(__name__) +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 接收和处理post的方法</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app.route</span>(<span style="color:#e6db74">&#39;/test&#39;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#39;POST&#39;</span>]) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv_file_www</span>(): +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post的传入参数</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>args<span style="color:#f92672">.</span>to_dict()) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># post所承载的数据</span> +</span></span><span style="display:flex;"><span> print(request<span style="color:#f92672">.</span>data) +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;OK&#39;</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;__main__&#39;</span>: +</span></span><span style="display:flex;"><span> app<span style="color:#f92672">.</span>run(port<span style="color:#f92672">=</span><span style="color:#ae81ff">31408</span>, host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0.0.0.0&#34;</span>,debug<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面就是一个完整的post接收和处理程序了,不需要调试socket,也不需要自己处理ResponseBuilder,最重要的是能保证获取到post数据。</p> +<p><strong>======以下是原来的内容==========</strong></p> +<p>经过网上搜索与自己尝试,发现socket.recv返回的时候是分段返回的,貌似会按照实际内容切割返回,<strong>必须不断的recv才能完整接收数据</strong>。有一种解决方案是POST传送之前先确定报文的完整擦换个年度,然后利用while循环,接收到对应长度才结束。</p> +<p>但是POST发送的一个不定长的数据,如果每次都通过http报文先传送post完整长度的话,有些浪费资源。于是我想到了这么一个方案:</p> +<p>POST的报文头会包含一个<code>Content-length</code>的数据,记录的是payload的长度。那我在接收阶段就对headers部分进行解码和处理,获取到content-length后,在加上本次获取headers的长度就是POST的完整长度了。</p> +<p>那么将上面代码的<code>client_sock.recv()</code>部分改写成下面的方法:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">recv</span>(self, client_sock): +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> <span style="color:#ae81ff">8192</span> <span style="color:#75715e"># 记录每次接收的长度</span> +</span></span><span style="display:flex;"><span> lrecv_all<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span> <span style="color:#75715e"># 记录数据总长度 </span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">=</span> bytes() <span style="color:#75715e"># 接收的所有数据存入这里</span> +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 解析的payload长度</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span> <span style="color:#75715e"># 用于记录是否接收到了payload,以及实时记录接收到payload的长度</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span> : +</span></span><span style="display:flex;"><span> part_body<span style="color:#f92672">=</span> client_sock<span style="color:#f92672">.</span>recv(<span style="color:#ae81ff">8192</span>) <span style="color:#75715e"># 循环接收数据</span> +</span></span><span style="display:flex;"><span> data_body <span style="color:#f92672">+=</span> part_body <span style="color:#75715e"># 将接收的数据写入data_body</span> +</span></span><span style="display:flex;"><span> lrecved <span style="color:#f92672">=</span> len(part_body) <span style="color:#75715e"># 获取本次接收长度</span> +</span></span><span style="display:flex;"><span> lrecv_all <span style="color:#f92672">+=</span> lrecved <span style="color:#75715e"># 计算总长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>: <span style="color:#75715e"># 没有接收到payload部分时,才进入下面的判断流程</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">3</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;GET&#39;</span>: <span style="color:#75715e"># 由于我get方法没出现过断开的问题,因此直接返回即可。</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> data_body[<span style="color:#ae81ff">0</span>:<span style="color:#ae81ff">4</span>]<span style="color:#f92672">.</span>decode() <span style="color:#f92672">==</span> <span style="color:#e6db74">&#39;POST&#39;</span>: +</span></span><span style="display:flex;"><span> payload_len <span style="color:#f92672">=</span> count_payload_len(data_body) <span style="color:#75715e"># 解析头部,计算payload部分的长度</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> lrecved <span style="color:#f92672">&lt;</span> payload_len: +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 偶尔会出现能接收到payload的情况,所以多加了一个判断。</span> +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 能接收payload肯定总长度大于payload_len的</span> +</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;no payloads, keep recving...&#34;</span>) +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 计算post报文的总长度,借用payload_flag变量</span> +</span></span><span style="display:flex;"><span> payload_flag <span style="color:#f92672">=</span> lrecved <span style="color:#f92672">+</span> payload_len +</span></span><span style="display:flex;"><span> <span style="color:#75715e"># 当接收到的总长度 &gt;= POST的计算长度,表示所有数据接收完成,退出</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> payload_flag <span style="color:#f92672">&lt;=</span> lrecv_all: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> data_body</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>里面解析headers的代码如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">count_payload_len</span>(data): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> d <span style="color:#f92672">in</span> data<span style="color:#f92672">.</span>split(<span style="color:#e6db74">b</span><span style="color:#e6db74">&#39;</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">&#39;</span>): <span style="color:#75715e"># 通过\r\n来分隔报文头</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#e6db74">&#34;LENGTH&#34;</span> <span style="color:#f92672">in</span> d<span style="color:#f92672">.</span>upper()<span style="color:#f92672">.</span>decode(): <span style="color:#75715e"># 如果报文头部有length关键词,当然可以写完整,我的heasers没有其他的length就简化了</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> int(d<span style="color:#f92672">.</span>decode()<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;:&#34;</span>)[<span style="color:#ae81ff">1</span>]) <span style="color:#75715e"># 分隔出payload长度并返回</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面的代码其实写的很复杂,因为我解析POST整体报文的功能已经写完并且通过第三方http报文调试工具验证过了,上IoT设备实验的时候才发现了问题,本着少改一点是一点的原则,就把这一部分复杂化了,但确实解决了我的问题,就OK了。</p> +<p>对了,这篇博客就是利用这个这个方案改造的新服务端进行上传同步的,之前用GitHub Actions进行同步博客,每次运营商都会发一条高危访问的短信和邮件通知我,太烦了。</p> + + + + + diff --git a/public/tags/teamspeak/index.html b/public/tags/teamspeak/index.html new file mode 100644 index 0000000..d6026c4 --- /dev/null +++ b/public/tags/teamspeak/index.html @@ -0,0 +1,510 @@ + + + + + + + +Teamspeak – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/teamspeak/index.xml b/public/tags/teamspeak/index.xml new file mode 100644 index 0000000..924e08e --- /dev/null +++ b/public/tags/teamspeak/index.xml @@ -0,0 +1,65 @@ + + + 香风家的火柴盒 – Teamspeak + https://www.small09.top/tags/teamspeak/ + Recent content in Teamspeak on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Thu, 18 Feb 2021 00:00:00 +0800 + + + + + + + + + + + 使用Docker安装TeamSpeak + https://www.small09.top/posts/210218-teamspeakwithdocker/ + Thu, 18 Feb 2021 00:00:00 +0800 + + https://www.small09.top/posts/210218-teamspeakwithdocker/ + + + + <p>TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。</p> +<h2>Teamspeak<span class="hx:absolute hx:-mt-20" id="teamspeak"></span> + <a href="#teamspeak" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。</p> +<p>所以,选择docker安装ts就是一个很不错的选择。</p> +<h2>Docker及其安装<span class="hx:absolute hx:-mt-20" id="docker及其安装"></span> + <a href="#docker%e5%8f%8a%e5%85%b6%e5%ae%89%e8%a3%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。</p> +<p>我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo yum install docker <span style="color:#75715e">#安装docker程序,会自动选择合适的</span> +</span></span><span style="display:flex;"><span>sudo systemctl start docker <span style="color:#75715e">#启动docker服务</span> +</span></span><span style="display:flex;"><span>sudo systemctl enable docker <span style="color:#75715e">#允许docker开机自启</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>Docker安装TS<span class="hx:absolute hx:-mt-20" id="docker安装ts"></span> + <a href="#docker%e5%ae%89%e8%a3%85ts" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak</code></p> +<p>直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。</p> +<h2>端口<span class="hx:absolute hx:-mt-20" id="端口"></span> + <a href="#%e7%ab%af%e5%8f%a3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>从指令可以看出,docker需要使用到<strong>9987,10011,30033</strong>三个端口,所以需要到服务器后台开启防火墙上对应端口。</p> +<h2>管理员密钥<span class="hx:absolute hx:-mt-20" id="管理员密钥"></span> + <a href="#%e7%ae%a1%e7%90%86%e5%91%98%e5%af%86%e9%92%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。</p> +<p><code>docker ps -a</code>查找所有容器。</p> +<p><code>docker logs -f [CONTATINER ID]</code>查看log,找到token即可。客户端设置之后再说。</p> +<p>密钥只会在第一次运行时打印,注意保存。</p> +<p><em>参考:<a href="https://woolio.cn/683/"target="_blank" rel="noopener">https://woolio.cn/683/</a></em></p> + + + + + diff --git a/public/tags/tools/index.html b/public/tags/tools/index.html new file mode 100644 index 0000000..df6c705 --- /dev/null +++ b/public/tags/tools/index.html @@ -0,0 +1,510 @@ + + + + + + + +Tools – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/tools/index.xml b/public/tags/tools/index.xml new file mode 100644 index 0000000..58b1394 --- /dev/null +++ b/public/tags/tools/index.xml @@ -0,0 +1,65 @@ + + + 香风家的火柴盒 – Tools + https://www.small09.top/tags/tools/ + Recent content in Tools on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Thu, 18 Feb 2021 00:00:00 +0800 + + + + + + + + + + + 使用Docker安装TeamSpeak + https://www.small09.top/posts/210218-teamspeakwithdocker/ + Thu, 18 Feb 2021 00:00:00 +0800 + + https://www.small09.top/posts/210218-teamspeakwithdocker/ + + + + <p>TeamSpeak的官网总是有些问题,无法下载其客户端,所以考虑使用Docker来部署一个ts服务器环境。</p> +<h2>Teamspeak<span class="hx:absolute hx:-mt-20" id="teamspeak"></span> + <a href="#teamspeak" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TeamSpeak(简称ts)是一款实时语音聊天软件,是替换yy语音的一个很好的选择。ts唯一的缺点是其服务器在国外,不论下载客户端还是服务端都是一个很麻烦的事情,且下载服务端后需要诸多配置和环境依赖,如果哪天不用了卸载也是一个麻烦事儿。</p> +<p>所以,选择docker安装ts就是一个很不错的选择。</p> +<h2>Docker及其安装<span class="hx:absolute hx:-mt-20" id="docker及其安装"></span> + <a href="#docker%e5%8f%8a%e5%85%b6%e5%ae%89%e8%a3%85" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker是一个容器,可以打包一个程序及其依赖,然后移植到任意系统运行而不报错。</p> +<p>我的服务器的腾讯云轻量-Wordpress应用,没有自带docker,所以第一步先安装docker:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo yum install docker <span style="color:#75715e">#安装docker程序,会自动选择合适的</span> +</span></span><span style="display:flex;"><span>sudo systemctl start docker <span style="color:#75715e">#启动docker服务</span> +</span></span><span style="display:flex;"><span>sudo systemctl enable docker <span style="color:#75715e">#允许docker开机自启</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>Docker安装TS<span class="hx:absolute hx:-mt-20" id="docker安装ts"></span> + <a href="#docker%e5%ae%89%e8%a3%85ts" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>docker run -dit -p 9987:9987/udp -p 10011:10011 -p 30033:30033 -e TS3SERVER_LICENSE=accept teamspeak</code></p> +<p>直接运行这条指令,docker就会自动寻找下载ts的容器并启动。如果下载太慢则自行查找修改docker仓库源的方式。</p> +<h2>端口<span class="hx:absolute hx:-mt-20" id="端口"></span> + <a href="#%e7%ab%af%e5%8f%a3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>从指令可以看出,docker需要使用到<strong>9987,10011,30033</strong>三个端口,所以需要到服务器后台开启防火墙上对应端口。</p> +<h2>管理员密钥<span class="hx:absolute hx:-mt-20" id="管理员密钥"></span> + <a href="#%e7%ae%a1%e7%90%86%e5%91%98%e5%af%86%e9%92%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>TS允许用户成为管理员,在首次运行的时候会在log中打印。不过docker运行时添加了-dit参数表示后台运行,所以查看参数需要额外的操作。</p> +<p><code>docker ps -a</code>查找所有容器。</p> +<p><code>docker logs -f [CONTATINER ID]</code>查看log,找到token即可。客户端设置之后再说。</p> +<p>密钥只会在第一次运行时打印,注意保存。</p> +<p><em>参考:<a href="https://woolio.cn/683/"target="_blank" rel="noopener">https://woolio.cn/683/</a></em></p> + + + + + diff --git a/public/tags/串口/index.html b/public/tags/串口/index.html new file mode 100644 index 0000000..92c3fae --- /dev/null +++ b/public/tags/串口/index.html @@ -0,0 +1,522 @@ + + + + + + + +串口 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/串口/index.xml b/public/tags/串口/index.xml new file mode 100644 index 0000000..1ffc18d --- /dev/null +++ b/public/tags/串口/index.xml @@ -0,0 +1,193 @@ + + + 香风家的火柴盒 – 串口 + https://www.small09.top/tags/%E4%B8%B2%E5%8F%A3/ + Recent content in 串口 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Tue, 20 Apr 2021 00:00:00 +0800 + + + + + + + + + + + Linux启用四个以上的串口 + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + Tue, 20 Apr 2021 00:00:00 +0800 + + https://www.small09.top/posts/210420-linuxmorethan4serialport/ + + + + <p>工控板安装Linux常常最多只能看到四个串口,通过修改引导文件可以显示所有的串口。</p> +<h2>先说结论<span class="hx:absolute hx:-mt-20" id="先说结论"></span> + <a href="#%e5%85%88%e8%af%b4%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e"># 1.修改开机grub文件</span> +</span></span><span style="display:flex;"><span>sudo vi /etc/default/grub +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2.在&#34;GRUB_CMDLINE_LINUX_DEFAULT=&#34;一行的结尾空格,添加内容</span> +</span></span><span style="display:flex;"><span> 8250.nr_uarts<span style="color:#f92672">=</span><span style="color:#ae81ff">8</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3.更新开机引导并重启</span> +</span></span><span style="display:flex;"><span>sudo update-grub</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>再说细节<span class="hx:absolute hx:-mt-20" id="再说细节"></span> + <a href="#%e5%86%8d%e8%af%b4%e7%bb%86%e8%8a%82" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>给带有多串口的工控机安装国产Linux操作系统UOS,发现<code>/dev/ttyS</code>只有四个,而且一个都不通,其实这是Linux 的通病,如果发行商没有提前配置,我们只能看到四个不能用的串口。但是工控机都有8-16个串口,不得不想办法让他们能够正常使用,而许多串口芯片只需要基本的驱动就能运行,也不提供专有驱动的源代码编译<font size=2 color=#ff0000>(一般安装专用驱动后,串口名称会改变,比如xr17v358芯片的驱动安装后名称变成ttyXR0)</font>,我们只有从linux本身下手了。</p> +<p>经过多番查找资料,虽然不知道为什么,但是我们应该修改<code>/etc/default/grub</code>文件,其中有一行是这样的:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>GRUB_CMDLINE_LINUX_DEFAULT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;这里面 的内容 先不管 它&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>我看了UOS(含Deepin)、Ubuntu和服务器的Centos7都有,在这一行的最后添加<code>8250.nr_uart=32</code>,其中数字&lt;=32都行,效果如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>GRUB_CMDLINE_LINUX_DEFAULT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;这里面 的内容 先不管 它 8250.nr_uarts=32&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后更新一下系统的引导</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>sudo update-gurb +</span></span><span style="display:flex;"><span>reboot</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>重启完了你就能看到更多的串口啦!配合<a href="https://www.small09.top/?p=24"target="_blank" rel="noopener">修改udev规则</a>,你就可以用普通用户的权限使用串口通信了。但是请注意其他隐患,比如这个芯片配合基本驱动只能完成基础的收发功能,流控的RTS、CTS等还是需要专用驱动实现的!</p> +<h2>最后吐槽<span class="hx:absolute hx:-mt-20" id="最后吐槽"></span> + <a href="#%e6%9c%80%e5%90%8e%e5%90%90%e6%a7%bd" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>为了串口问题我找了一下午的资料,最后才在外网找到了一个能用的。国内的教程都是说要修改<code>/boot/gurb/gurb2</code>,宝友,这可不兴改啊,这个改坏了系统可就没了啊!这些文件普通用户连浏览权限都没就可见其重要性。</p> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><a href="https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/"target="_blank" rel="noopener">https://blog.wjacobsen.dk/2019/05/03/enable-more-than-4-serial-ports-linux-unix/</a></p> +<p>核心步骤就参考了这个,在Linux方面大家还是多用google,多用英文去搜索吧!</p> + + + + + Linux串口和udev理解 + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-serialmodeandudevinlinux/ + + + + <p>配置串口权限,顺便看一下linux的udev规则。</p> +<h2>串口的问题<span class="hx:absolute hx:-mt-20" id="串口的问题"></span> + <a href="#%e4%b8%b2%e5%8f%a3%e7%9a%84%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在现场开发的同事反映UOS工控机没有配置串口,每次都需要sudo才能使用。(明明在家里你们都会自己配置好环境与权限,为什么到了现场反而不会了需要我提供协助呢?)</p> +<p>我之前没考虑过这个问题,认为串口就是得root权限操作才安全。但是既然提出了需求,就赶紧百度并测试一下吧。</p> +<h2>赋予普通用户串口权限的两种方法<span class="hx:absolute hx:-mt-20" id="赋予普通用户串口权限的两种方法"></span> + <a href="#%e8%b5%8b%e4%ba%88%e6%99%ae%e9%80%9a%e7%94%a8%e6%88%b7%e4%b8%b2%e5%8f%a3%e6%9d%83%e9%99%90%e7%9a%84%e4%b8%a4%e7%a7%8d%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>在百度上,给普通用户串口权限有两种主流方法(可能2会了,Google上不去),一种是将用户放入特殊的用户组,从而获得权限。另一种是加入udev规则,在串口插入时自动更改权限。</p> +<p>我个人更倾向于使用第二种方案,因为公司中不同的人装系统就可能起不同的名字,之后进入生产环境可能又会起新的名字,所以采用第二种设置规则的办法,便于之后制作恢复镜像和一键部署。</p> +<h2>xxx.rules<span class="hx:absolute hx:-mt-20" id="xxxrules"></span> + <a href="#xxxrules" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo vi /etc/udev/rules.d/70-tty-usb-rules</code>,写入以下内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>KERNEL<span style="color:#f92672">==</span><span style="color:#e6db74">&#34;ttyS*&#34;</span>,MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0666&#34;</span> +</span></span><span style="display:flex;"><span>KERNEL<span style="color:#f92672">==</span><span style="color:#e6db74">&#34;ttyUSB*&#34;</span>,MODE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;0666&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这一段的意思是匹配所有ttyS和ttyUSB,插入时都赋予0666的权限,即所有用户都可读可写。重新加载一下设备,就可以按规则配置权限进行操作了。</p> +<h2>udev规则<span class="hx:absolute hx:-mt-20" id="udev规则"></span> + <a href="#udev%e8%a7%84%e5%88%99" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>udev是什么?udev是Linux下的设备文件管理方式,运行在用户侧(devfs在内核侧管理设备文件)。有了udev,<code>/dev</code>目录下的文件就都是真实存在的已连接的设备了。</p> +<p>udev的规则都保存在<code>/etc/udev/rules.d</code>目录下,文件名的格式为<code>数字-名称.rules</code>,数字表示加载顺序,一般是从小往大加载;名称按功能自定义,最后以.rules结尾。这些规则会在设备接入时生效。</p> +<p>规则文件按行执行,每行分为两个部分:匹配和赋值,每个参数使用逗号,分割。</p> +<blockquote> + <p>(这段复制自引用2,仅供参考)。 +<strong>1、udev 规则的所有操作符</strong> +<strong>==</strong>  比较键、值,若等于,则该条件满足; +<strong>!=</strong>  比较键、值,若不等于,则该条件满足; +<strong>=</strong>   对一个键赋值; +<strong>+=</strong>  为一个表示多个条目的键赋值。 +<strong>:=</strong>  对一个键赋值,并拒绝之后所有对该键的改动,防止后面的规则文件对该键重赋值。</p> +<p><strong>2、udev 规则的匹配键</strong> +<strong>ACTION</strong>        事件 (uevent) 的行为,例如:add、remove。 +<strong>KERNEL</strong>         内核设备名称,例如:sda, cdrom。 +<strong>DEVPATH</strong>        设备的 devpath 路径。 +<strong>SUBSYSTEM</strong>       设备的子系统名称,例如:sda 的子系统为 block。 +<strong>BUS</strong>           设备在 devpath 里的总线名称,例如:usb。 +<strong>DRIVER</strong>         设备在 devpath 里的设备驱动名称,例如:ide-cdrom。 +<strong>ID</strong>            设备在 devpath 里的识别号。 +<strong>SYSFS{filename}</strong>    设备的 devpath 路径下,设备的属性文件“filename”里的内容。 +<strong>ENV{key}</strong>       环境变量。在一条规则中,可以设定最多五条环境变量的 匹配键。 +<strong>PROGRAM</strong>        调用外部命令。 +<strong>RESULT</strong>         外部命令 PROGRAM 的返回结果。</p> +<p><strong>3、udev 的重要赋值键</strong> +<strong>NAME</strong>           /dev目录下显示的文件名重命名 +<strong>SYMLINK</strong>          为 /dev下文件进行符号链接,防止覆盖系统默认产生的文件 +<strong>OWNER, GROUP, MODE</strong>  为设备设定权限。 +<strong>ENV{key}</strong>         导入一个环境变量。</p> +<p><strong>4、udev 的值和可调用的替换操作符</strong> +Linux 用户可以随意地定制 udev 规则文件的值。例如:my_root_disk, my_printer。同时也可以引用下面的替换操作符: +<strong>$kernel, %k</strong>        设备的内核设备名称,例如:sda、cdrom。 +<strong>$number, %n</strong>        设备的内核号码,例如:sda3 的内核号码是 3。 +<strong>$devpath, %p</strong>       设备的 devpath路径。 +<strong>$id, %b</strong>          设备在 devpath里的 ID 号。 +<strong>$sysfs{file}, %s{file}</strong>   设备的 sysfs里 file 的内容。其实就是设备的属性值。 +<strong>$env{key}, %E{key}</strong>   一个环境变量的值。 +<strong>$major, %M</strong>        设备的 major 号。 +<strong>$minor %m</strong>        设备的 minor 号。 +<strong>$result, %c</strong>        PROGRAM 返回的结果。 +<strong>$parent, %P</strong>       父设备的设备文件名。 +<strong>$root, %r</strong>         udev_root的值,默认是 /dev/。 +<strong>$tempnode, %N</strong>      临时设备名。 +<strong>%%</strong>            符号 % 本身。 +<strong>$$</strong>             符号 $ 本身。</p> + +</blockquote> +<h2>参考<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://www.cnblogs.com/qixianyu/p/6869744.html +[2]https://www.cnblogs.com/cslunatic/p/3171837.html</p> + + + + + diff --git a/public/tags/容器和虚拟化/index.html b/public/tags/容器和虚拟化/index.html new file mode 100644 index 0000000..68db370 --- /dev/null +++ b/public/tags/容器和虚拟化/index.html @@ -0,0 +1,510 @@ + + + + + + + +容器和虚拟化 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/容器和虚拟化/index.xml b/public/tags/容器和虚拟化/index.xml new file mode 100644 index 0000000..080e2a5 --- /dev/null +++ b/public/tags/容器和虚拟化/index.xml @@ -0,0 +1,200 @@ + + + 香风家的火柴盒 – 容器和虚拟化 + https://www.small09.top/tags/%E5%AE%B9%E5%99%A8%E5%92%8C%E8%99%9A%E6%8B%9F%E5%8C%96/ + Recent content in 容器和虚拟化 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 25 Dec 2023 09:06:25 +0800 + + + + + + + + + + + docker简单笔记 + https://www.small09.top/posts/231225-dockernote/ + Mon, 25 Dec 2023 09:06:25 +0800 + + https://www.small09.top/posts/231225-dockernote/ + + + + <p>学习docker,使用docker,编写docker</p> +<h2>前言<span class="hx:absolute hx:-mt-20" id="前言"></span> + <a href="#%e5%89%8d%e8%a8%80" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>祝自己圣诞节快乐,去年的愿望是“明年不要一个人过圣诞”,今年的愿望也是“明年不要一个人过圣诞”。</p> +<p>不扯了,docker,又称容器,是一种高效的虚拟化技术,能够在不启动虚拟机的情况下,创建一套虚拟环境,方便调试程序,配置环境,乃至直接抄别人的配置。docker还有一个进阶的docker compose,支持多个容器之间组合互联组成一套完整环境。</p> +<p>由于docker比较复杂,又没有官方支持的gui界面~~(在官网看好像有了?但是就当他没有吧。)~~;中文环境的文档、教程都冗长复杂不想看,因此docker一直是我学习路上的一个绕路点。今天打算突破它,记录最简单的docker指令和方法。</p> +<h2>安装docker和docker compose<span class="hx:absolute hx:-mt-20" id="安装docker和docker-compose"></span> + <a href="#%e5%ae%89%e8%a3%85docker%e5%92%8cdocker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>虽然阿里ECS之类的云主机大多预装了docker,但是海外主机、自己装的linux系统大多还是没有docker的,我们从安装开始。以ubuntu为例。</p> +<p>apt仓库提供的docker-io等软件包是社区构建的非官方版本,要安装官方版,需要多几个步骤。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 添加docker官方的gpg</span> +</span></span><span style="display:flex;"><span>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 在/etc/apt/source.list.d 里面添加docker的源地址,借用官网的一行指令直接添加</span> +</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;deb [arch=</span><span style="color:#66d9ef">$(</span>dpkg --print-architecture<span style="color:#66d9ef">)</span><span style="color:#e6db74"> signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu </span><span style="color:#66d9ef">$(</span>. /etc/os-release <span style="color:#f92672">&amp;&amp;</span> echo <span style="color:#e6db74">&#34;</span>$VERSION_CODENAME<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74"> stable&#34;</span> | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 我这边添加的结果是</span> +</span></span><span style="display:flex;"><span>deb <span style="color:#f92672">[</span>arch<span style="color:#f92672">=</span>amd64 signed-by<span style="color:#f92672">=</span>/usr/share/keyrings/docker-archive-keyring.gpg<span style="color:#f92672">]</span> https://download.docker.com/linux/ubuntu jammy stable</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>然后可以通过apt源安装docker了</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update +</span></span><span style="display:flex;"><span>sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>*<em>注意,上面的指令同时安装了docker-compose</em>。如果不安装docker,直接安装docker-compose也是可以的。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>wget https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-linux-x86_64 +</span></span><span style="display:flex;"><span>sudo cp ./docker-compose-linux-x86_64 /usr/bin/docker-compose</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>更加具体的步骤和细节可以到官网查看:<a href="https://docs.docker.com/engine/install/ubuntu/"target="_blank" rel="noopener">docker安装</a>、<a href="https://docs.docker.com/compose/install/standalone/"target="_blank" rel="noopener">docker-compose安装</a></p> +<h2>docker 基本使用方法<span class="hx:absolute hx:-mt-20" id="docker-基本使用方法"></span> + <a href="#docker-%e5%9f%ba%e6%9c%ac%e4%bd%bf%e7%94%a8%e6%96%b9%e6%b3%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>使用一个docker镜像的步骤是【拉取镜像、创建容器、运行容器、停止容器】,一般创建和运行通过同一条指令实现。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 拉取一个镜像</span> +</span></span><span style="display:flex;"><span>docker pull xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 创建容器并运行</span> +</span></span><span style="display:flex;"><span>docker run --name aaa xxx/yyy +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 停止容器</span> +</span></span><span style="display:flex;"><span>docker stop docker-id</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>关于运行容器,有很多常用的参数:</p> +<blockquote> + <p>-d 容器转入后台运行</p> +<p>-e 设置容器的环境变量</p> +<p>&ndash;name 容器名称</p> +<p>-p 80:80 将暴露的端口映射到容器内端口,外部:内部</p> +<p>-v f:c 将一个文件(卷)映射到容器内部的文件,支持文件和文件夹。外:内</p> +<p>-e、-p、-v 参数都可以多次使用,达到添加多个变量的目的。</p> + +</blockquote> +<p>停止容器支持多种类型的参数,包括容器ID和容器名称。容器ID可以只写出部分,能够区分容器即可。</p> +<p>查询容器id,可以使用指令<code>docker ps</code>。docker还有其他几个查询和控制指令</p> +<blockquote> + <p><code>docker exec 容器 指令</code> 允许在外部执行docker内的指令,添加 -d 参数转入后台运行。</p> +<p><code>docker ps</code> 列出正在运行的docker容器信息。添加 -a 参数查看所有容器,包括已经停止的。</p> +<p><code>docker images</code> 列出已经下载的镜像列表。与 <code>docker image</code> 指令不同。</p> +<p><code>docker kill 容器名/id</code> 杀死一个无法stop的容器</p> +<p><code>docker rm 容器</code> 删除一个<strong>容器</strong></p> +<p><code>docker rmi 镜像名</code> 删除一个<strong>镜像</strong></p> +<p><code>docker restart 容器ID</code> 重启一个容器,毕竟我们不需要每次都创建新容器。</p> + +</blockquote> +<p>关于docker运行时需要哪些端口,可以通过镜像的docker hub页面进行判断。</p> +<h2>更换docker镜像源<span class="hx:absolute hx:-mt-20" id="更换docker镜像源"></span> + <a href="#%e6%9b%b4%e6%8d%a2docker%e9%95%9c%e5%83%8f%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>和apt源、github一样,docker镜像源服务器在国外,国内访问可能很慢,遇到这个情况可以更换成国内的镜像源。</p> +<p>修改<code>/etc/docker/daemon.json</code>文件,添加下面的内容:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{ +</span></span><span style="display:flex;"><span> <span style="color:#f92672">&#34;registry-mirrors&#34;</span>: [ +</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;https://docker.mirrors.ustc.edu.cn&#34;</span> +</span></span><span style="display:flex;"><span> ] +</span></span><span style="display:flex;"><span>}</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>编辑完成后需要重启docker服务。</p> +<p>具体的源地址可以进行搜索和尝试,我访问速度还行就没有更换。</p> +<h2>docker-compose<span class="hx:absolute hx:-mt-20" id="docker-compose"></span> + <a href="#docker-compose" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>docker compose是通过ymal配置文件来启动容器的,它支持将多个容器组合启动,并自动处理依赖关系。在docker compose中我们不需要拉取镜像,直接在配置文件中指定镜像名,在启动后会自动拉取。</p> +<p>首先创建一个工作目录,并创建一个<code>docker-compose.yml</code>的文件,默认情况下docker compose指令只认这个文件名。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><pre><code>version: &#39;3&#39; +services: + [NAME1]: + image: &#34;image/name&#34; + ports: + - 81:80 + - 8080:8080 + environment: + HOSTNAME: &#34;cc&#34; + PORT: 8181 + volumes: + - nginx-data:/etc/nginx + [NAME2]: + image: &#39;xxx&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>docker compose的ymal文件可以实现十分复杂的功能,我暂时也没搞懂,就只放一个基础结构的文件在这里。与docker run指令相同,<code>ports</code>字段指定了内外端口的映射,即&rsquo;外部:内部&rsquo;,卷映射同理。<code>environment</code>不需要 - 开头。</p> +<p>编写完ymal文件,就可以启动容器组了。首先切换到文件所在的目录,即工作目录,然后执行指令直接启动</p> +<blockquote> + <p><code>docker compose up</code> 启动容器,查找当前目录下的ymal文件。-d 参数可以转入后台运行。 -f参数可以指定ymal文件。</p> +<p><code>docker compose down</code> 停止容器,并删除对应的容器和网络</p> +<p><code>docker compose exec NAME command</code> 在ymal文件内其中一个容器里运行指令</p> + +</blockquote> +<p>docker compose指令必须是要有ymal文件才能运行的,它通过这个文件来寻找有关的网络和容器。</p> +<p>目前浅学了一下,只有学一点更新一点吧。</p> + + + + + diff --git a/public/tags/数据恢复/index.html b/public/tags/数据恢复/index.html new file mode 100644 index 0000000..71d447f --- /dev/null +++ b/public/tags/数据恢复/index.html @@ -0,0 +1,510 @@ + + + + + + + +数据恢复 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/数据恢复/index.xml b/public/tags/数据恢复/index.xml new file mode 100644 index 0000000..328807c --- /dev/null +++ b/public/tags/数据恢复/index.xml @@ -0,0 +1,107 @@ + + + 香风家的火柴盒 – 数据恢复 + https://www.small09.top/tags/%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D/ + Recent content in 数据恢复 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 15 Nov 2021 10:02:30 +0800 + + + + + + + + + + + 记一次拯救NAS硬盘 + https://www.small09.top/posts/211115-fixnasdriver/ + Mon, 15 Nov 2021 10:02:30 +0800 + + https://www.small09.top/posts/211115-fixnasdriver/ + + + + <p>更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据?</p> +<p><em>图片稍后补充。</em></p> +<h2>v 0 起因<span class="hx:absolute hx:-mt-20" id="v-0-起因"></span> + <a href="#v-0-%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0.</p> +<p>考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。</p> +<p>为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。</p> +<h2>v-1 杯具<span class="hx:absolute hx:-mt-20" id="v-1-杯具"></span> + <a href="#v-1-%e6%9d%af%e5%85%b7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。</p> +<h2>v-2 尝试<span class="hx:absolute hx:-mt-20" id="v-2-尝试"></span> + <a href="#v-2-%e5%b0%9d%e8%af%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内<strong>写入任何内容</strong>,也<strong>没有格式化</strong>。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。</p> +<p>为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。</p> +<p>经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。</p> +<p>搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。</p> +<p>难道就这么放弃了么?</p> +<h2>v 1 曙光<span class="hx:absolute hx:-mt-20" id="v-1-曙光"></span> + <a href="#v-1-%e6%9b%99%e5%85%89" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。</p> +<p>我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?!</p> +<p>开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。</p> +<p>把小硬盘用易驱线连上树莓派,<code>lsblk</code>看到这个硬盘有三个分区,其中<code>/dev/sda3</code>下面还有一个<code>md127</code>,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。</p> +<h2>v 2 得到数据<span class="hx:absolute hx:-mt-20" id="v-2-得到数据"></span> + <a href="#v-2-%e5%be%97%e5%88%b0%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>安装<code>mdadm</code>软件包,我的树莓派不带这个,然后就能挂载raid盘了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount /dev/md127 ~/disk</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个<code>NasData</code>目录,这正是我当时保存文件的地方。cd进去,在!都还在!</p> +<h2>v 3 移动数据<span class="hx:absolute hx:-mt-20" id="v-3-移动数据"></span> + <a href="#v-3-%e7%a7%bb%e5%8a%a8%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。</p> +<p>在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rsync -vrultD --exclude <span style="color:#e6db74">&#39;文件名&#39;</span> --exclude <span style="color:#e6db74">&#39;目录名&#39;</span> ~/disk/NasData/* ~/Nasdata/临时目录 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。</p> +<h2>v 4 结论<span class="hx:absolute hx:-mt-20" id="v-4-结论"></span> + <a href="#v-4-%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。</p> +<p>这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。</p> +<p>总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢?</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202111150957877.png" alt="image-20211115095410121" loading="lazy" /></p> + + + + + diff --git a/public/tags/显卡/index.html b/public/tags/显卡/index.html new file mode 100644 index 0000000..1805374 --- /dev/null +++ b/public/tags/显卡/index.html @@ -0,0 +1,510 @@ + + + + + + + +显卡 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/显卡/index.xml b/public/tags/显卡/index.xml new file mode 100644 index 0000000..c24f2b3 --- /dev/null +++ b/public/tags/显卡/index.xml @@ -0,0 +1,78 @@ + + + 香风家的火柴盒 – 显卡 + https://www.small09.top/tags/%E6%98%BE%E5%8D%A1/ + Recent content in 显卡 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 19 Jul 2021 11:00:07 +0800 + + + + + + + + + + + Linux中获得AMD显卡的状态信息 + https://www.small09.top/posts/210719-gpuinfoinlinux/ + Mon, 19 Jul 2021 11:00:07 +0800 + + https://www.small09.top/posts/210719-gpuinfoinlinux/ + + + + <p>在Linux下获得AMD GPU的状态和温度信息。</p> +<p>在Linux下如果要查看Nvidia GPU的状态信息,可以使用Nvidia驱动自带的工具。而AMD显卡一般都是使用的开源驱动,这种情况下如何获得显卡的状态和占用呢?</p> +<p><em>文章部分图片之后再附</em></p> +<h2>1、获取GPU信息<span class="hx:absolute hx:-mt-20" id="1获取gpu信息"></span> + <a href="#1%e8%8e%b7%e5%8f%96gpu%e4%bf%a1%e6%81%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>如需获得GPU的信息(型号、驱动)等,如果系统提供类似*“设备管理器”*的工具可以打开查看。或者就是用<code>lspci</code>指令查看。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 获得所有PCIe设备,在其中找到AMD GPU</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>exam@ple<span style="color:#f92672">]</span>:~$ lspci +</span></span><span style="display:flex;"><span>00:02.0 VGA compatible controller: xxxxxxxx +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">[</span>exam@ple<span style="color:#f92672">]</span>:~$ lspci -v -s 00:02.0 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 这里就会显示显卡的具体信息了,包括型号、驱动等,之后补充</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>2、GPU占用率<span class="hx:absolute hx:-mt-20" id="2gpu占用率"></span> + <a href="#2gpu%e5%8d%a0%e7%94%a8%e7%8e%87" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>显卡占用率是一个能反映负载的直观指标,AMD显卡可以通过安装<code>radeontop</code>软件查看。</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary/img/20210719133426.png" alt="radeontop界面" loading="lazy" /></p> +<p>最上面是GPU管道使用,大概类似于总体使用率的东西;最下面是内存使用率,使用sudo权限能看到更多信息。其他项目可以到radeontop的<a href="https://github.com/clbr/radeontop"target="_blank" rel="noopener">Github页面</a>查看。</p> +<h2>3、温度<span class="hx:absolute hx:-mt-20" id="3温度"></span> + <a href="#3%e6%b8%a9%e5%ba%a6" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>特指GPU温度。查阅这篇文章所涉及的知识点的最初目的,也是确认机箱过热是否是GPU的问题。</p> +<p>GPU温度使用第三方软件查看,并使其实时更新。查看温度使用<code>sensors</code>指令,通过安装<code>lm-sensors</code>实现。执行sensors时只会显示一次,我们可以利用watch指令定时刷新温度显示:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>watch -n <span style="color:#ae81ff">2</span> sensors +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 每隔2秒执行sensors指令显示结果</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>![watch执行结果](图片占位)</p> + + + + + diff --git a/public/tags/网络/index.html b/public/tags/网络/index.html new file mode 100644 index 0000000..0a05131 --- /dev/null +++ b/public/tags/网络/index.html @@ -0,0 +1,510 @@ + + + + + + + +网络 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/网络/index.xml b/public/tags/网络/index.xml new file mode 100644 index 0000000..f2db62b --- /dev/null +++ b/public/tags/网络/index.xml @@ -0,0 +1,106 @@ + + + 香风家的火柴盒 – 网络 + https://www.small09.top/tags/%E7%BD%91%E7%BB%9C/ + Recent content in 网络 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Sun, 28 Mar 2021 00:00:00 +0800 + + + + + + + + + + + Linux修改mac地址 + https://www.small09.top/posts/210328-linuxchangemacip/ + Sun, 28 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210328-linuxchangemacip/ + + + + <p>当Linux的图形界面没有或失效的时候,该如何更改mac地址?</p> +<h1>Linux调整网卡MAC地址(指令)</h1><p>有的时候需要使用欺骗的方法为电脑设置新的mac,比如存在上网网卡限制,或者想要隐藏自己的真实mac地址。这里列举三种修改mac的方法,适用于Debian系的Linux,其他的应该类似。</p> +<h2>方法一:ifconfig<span class="hx:absolute hx:-mt-20" id="方法一ifconfig"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%b8%80ifconfig" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># ifconfig 查看网卡名称$NAME</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 需要sudo权限</span> +</span></span><span style="display:flex;"><span>ifconfig $NAME down +</span></span><span style="display:flex;"><span>ifconfig $NAME hw ether $NEW_MAC +</span></span><span style="display:flex;"><span>ifconfig $NAME up +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 使用ifconfig检查有没有改变</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>方法二:macchanger<span class="hx:absolute hx:-mt-20" id="方法二macchanger"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%ba%8cmacchanger" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>sudo apt install macchanger</code>,安装过程提示是否自启,随便。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ifconfig $NAME down +</span></span><span style="display:flex;"><span>macchanger -m $NEW_MAC $NAME +</span></span><span style="display:flex;"><span>ifconfig $NAME up</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>ps:macchanger可以附加多种参数,如-m指定mac地址,-s查看状态,-r随机地址</p> +<h2>备注:<span class="hx:absolute hx:-mt-20" id="备注"></span> + <a href="#%e5%a4%87%e6%b3%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上两种方法是软件层面的修改,不会对硬件造成损伤,重启之后就能恢复如初。如果是要长期有效,可以把这些命令写入脚本开机自启。除了这两个,还有另一种软修改的方法,因为我总是提示失败就遗忘了。</p> +<p>有些方法会提到修改/etc目录下的文件实现MAC地址的修改,但是我一个文件都没找到(UOS),这个是因系统而异的,不同的发行版可以选择性的编译这些文件,就会造成有人有有人没有,因为没有实践,所以就不写在这里了。</p> +<h2>方法三:ethtool(慎用)<span class="hx:absolute hx:-mt-20" id="方法三ethtool慎用"></span> + <a href="#%e6%96%b9%e6%b3%95%e4%b8%89ethtool%e6%85%8e%e7%94%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>以上方法都失效,可以直接修改网卡的物理MAC地址,修改后无法恢复,慎用</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 首先检查mac是否支持修改</span> +</span></span><span style="display:flex;"><span>ethtool -i $NAME +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果 supports-eeprom-access: no,大概率不支持修改</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 查看网卡的mac寄存器</span> +</span></span><span style="display:flex;"><span>ethtool -e $NAME +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果无法读取,大概率不支持修改</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 修改mac,按字节修改</span> +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">0</span> value 0xAA +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">1</span> value 0xBB +</span></span><span style="display:flex;"><span>ethtool -E $NAME offset <span style="color:#ae81ff">2</span> value 0xCC +</span></span><span style="display:flex;"><span>...共六行 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 如果修改无效,则驱动不支持修改</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h2>如果以上方法都无法修改,则建议申请新的ip。。。<span class="hx:absolute hx:-mt-20" id="如果以上方法都无法修改则建议申请新的ip"></span> + <a href="#%e5%a6%82%e6%9e%9c%e4%bb%a5%e4%b8%8a%e6%96%b9%e6%b3%95%e9%83%bd%e6%97%a0%e6%b3%95%e4%bf%ae%e6%94%b9%e5%88%99%e5%bb%ba%e8%ae%ae%e7%94%b3%e8%af%b7%e6%96%b0%e7%9a%84ip" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><em><font size="1">文章首发于<a href="small09.top" target="前往火柴盒" title="前往Kushidou个人博客" rel="noopener">香风家的小别墅</a>,转载请携带出处。</font></em></p> + + + + + diff --git a/public/tags/群晖/index.html b/public/tags/群晖/index.html new file mode 100644 index 0000000..a260abc --- /dev/null +++ b/public/tags/群晖/index.html @@ -0,0 +1,510 @@ + + + + + + + +群晖 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/群晖/index.xml b/public/tags/群晖/index.xml new file mode 100644 index 0000000..36b55d4 --- /dev/null +++ b/public/tags/群晖/index.xml @@ -0,0 +1,107 @@ + + + 香风家的火柴盒 – 群晖 + https://www.small09.top/tags/%E7%BE%A4%E6%99%96/ + Recent content in 群晖 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 15 Nov 2021 10:02:30 +0800 + + + + + + + + + + + 记一次拯救NAS硬盘 + https://www.small09.top/posts/211115-fixnasdriver/ + Mon, 15 Nov 2021 10:02:30 +0800 + + https://www.small09.top/posts/211115-fixnasdriver/ + + + + <p>更新家里的NAS,手滑把原来NAS硬盘的分区信息删掉了。如何拯救盘里的数据?</p> +<p><em>图片稍后补充。</em></p> +<h2>v 0 起因<span class="hx:absolute hx:-mt-20" id="v-0-起因"></span> + <a href="#v-0-%e8%b5%b7%e5%9b%a0" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>双十一买了两块新的4T紫盘,打算升级一下家里的黑群晖。原来黑群晖是用一台N手工控机+1TB希捷笔记本硬盘(看缓存和速度肯定是SMR盘了),数据安全几乎为0.</p> +<p>考虑到小工控机的电源带不动那么多硬盘,机箱也没有空间,就把家里闲置的i3台式机整了过来,拆掉原来的硬盘装上紫盘组新nas了。顺手拆了一个捡来的监控机上的1TB西数紫盘。</p> +<p>为了节约迁移系统和数据的时间,我打算拿两个sata易驱线,用DiskGenuies进行克隆。后来网上冲浪发现群晖系统是认硬盘SN的,也就是说我克隆的数据没有一丁点用,加上手头上没有2.5转3.5的转接件,就考虑把笔记本盘放回老nas,然后直接拷贝或者备份到新nas。</p> +<h2>v-1 杯具<span class="hx:absolute hx:-mt-20" id="v-1-杯具"></span> + <a href="#v-1-%e6%9d%af%e5%85%b7" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>当我按下取消的时候,惨剧发生了。其实整盘克隆的时候,DG会删除所有分区,克隆完了再重建分区。我取消克隆,然后退出DG。它提示我是否保存分区更改,我以为是1T紫盘的分区,顺手点了个否。当我重启DG的时候,发现希捷笔记本盘只剩一条灰色,未分区的区域。</p> +<h2>v-2 尝试<span class="hx:absolute hx:-mt-20" id="v-2-尝试"></span> + <a href="#v-2-%e5%b0%9d%e8%af%95" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>DG有个功能是搜索重建分区表,我第一时间进行搜索。此时还没有网盘内<strong>写入任何内容</strong>,也<strong>没有格式化</strong>。数据应该是都在的,只要按照原来的样子重新分区应该就能重新读写数据了。</p> +<p>为什么不手动分区?因为群晖会在硬盘上先建立一个系统分区,然后建立swap分区,空一段空间后再建立数据分区。每个分区开始和结束的位置都是未知的,网络上也没有可以参考的内容。</p> +<p>经过一个多小时的搜索,除了第一个2.4GB的系统分区被找到以外,一无所获。我怀疑是不是我期间制作黑裙启动盘影响了,又搜索了一遍,好家伙还是没有。</p> +<p>搜索期间网上冲浪找前人的肩膀,结果他们要么是分区没被删直接装Windows下btrfs驱动(gitee开源)读取(群晖坏了),要么是DG轻松搜出全部分区(和群晖无关)。我还尝试查找丢失文件,依然一无所获。</p> +<p>难道就这么放弃了么?</p> +<h2>v 1 曙光<span class="hx:absolute hx:-mt-20" id="v-1-曙光"></span> + <a href="#v-1-%e6%9b%99%e5%85%89" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>群晖使用的BTRFS格式不是什么群晖专用格式,而是SUN(Oracle收购)开发的新一代文件系统,针对ext3/4进行优化升级。所以比较新的Linux系统都支持使用这个格式;装个驱动也能轻易读取。</p> +<p>我本来笔记本是有Deepin系统的,上次win10损毁了我就格掉装了win11。后来想起来我还有块树莓派正跑着linux系统。但是没有分区的硬盘上了Linux也啥都读不到。恰好这时候新nas的系统已经起来了,1T紫盘上的独立分区也建立好了。两款空间一样的盘,同样版本的群晖系统,建立一样的存储空间后,那不就是连分区也一模一样了么?!</p> +<p>开搞,ssh连上群晖,用sudo blkid看到硬盘实际的分区信息,它正好是显示的扇区起止、大小。打开DG,手动分区,按照群晖的磨样给小硬盘手动分区。DG不支持btrfs分区,那我随便挂一个ext4吧。分区过程和保存的时候,千万不要格式化了。</p> +<p>把小硬盘用易驱线连上树莓派,<code>lsblk</code>看到这个硬盘有三个分区,其中<code>/dev/sda3</code>下面还有一个<code>md127</code>,不知道是啥,查看发现是raid。群晖的数据区,哪怕是单盘basic也是以raid方式存在的,方便以后升级raid模式吧大概。也就是说,虽然我分区表是瞎分的,Linux还是能够正确读取到原来的分区和文件系统。</p> +<h2>v 2 得到数据<span class="hx:absolute hx:-mt-20" id="v-2-得到数据"></span> + <a href="#v-2-%e5%be%97%e5%88%b0%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>安装<code>mdadm</code>软件包,我的树莓派不带这个,然后就能挂载raid盘了。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount /dev/md127 ~/disk</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>disk目录提前建立好了,把机械盘挂载上去,cd到目录里,然后我就发现了新世界的大门。盘里有个<code>NasData</code>目录,这正是我当时保存文件的地方。cd进去,在!都还在!</p> +<h2>v 3 移动数据<span class="hx:absolute hx:-mt-20" id="v-3-移动数据"></span> + <a href="#v-3-%e7%a7%bb%e5%8a%a8%e6%95%b0%e6%8d%ae" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>我打算直接把就数据拷贝到新NAS里去了,烤到移动硬盘再复制到nas大费周章的。</p> +<p>在NAS里的主共享文件夹启动NFS服务,映射为root,允许所有来源的读写,然后到树莓派中挂载nfs</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo mount -t nfs 10.x.x.x:/volume2/NasData ~/Nasdata</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>主目录下提前建立Nasdata目录,挂载完成后新Nas的内容也都出现了。不想一个个复制文件,CP指令不支持排除不想要的文件(毕竟还有部分文件依然保存在移动硬盘和笔记本里,树莓派只支持2.4GHz的wifi太慢),最终我选择了rsync指令进行“本地复制”。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rsync -vrultD --exclude <span style="color:#e6db74">&#39;文件名&#39;</span> --exclude <span style="color:#e6db74">&#39;目录名&#39;</span> ~/disk/NasData/* ~/Nasdata/临时目录 +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -v 详细输出 -r 递归拷贝(保留目录结构) -u 仅更新,防止中断再开后重新拷贝 -l 保留软连接 -t 保留时间信息 -D 保持设备文件信息</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 不用-a 是因为对拷过去的文件赋权限时疯狂报错失败</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>经历树莓派三次死机两次中断后,我终于抢救了500G数据中的50G(另外450包括百度云同步、移动硬盘依然保存着的动漫视频等)。</p> +<h2>v 4 结论<span class="hx:absolute hx:-mt-20" id="v-4-结论"></span> + <a href="#v-4-%e7%bb%93%e8%ae%ba" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>现在数据和小姐姐们都住在一个raid1备份的空间中,应该轻易不会发生无家可归的情况了。</p> +<p>这次事件完全是我自己作死,群晖其实提供了备份功能,可以完整的把数据、部分设置乃至我的Docker容器都完整备份到新NAS里(只要两台都在线),而且树莓派的wifi只有5MiB/s,比起家里千兆的内网,速度慢了数十倍,时间也花了我整整一下午。</p> +<p>总之硬盘出了事故不要慌,只要不是物理损坏,都有机会修复。除了事故后第一时间就要停止读写,更不要格式化,先尝试重建分区表,失败了换个系统试试。说不定我不重建,放到Linux上也能看到正常的三个分区呢?</p> +<p><img src="https://cdn.jsdelivr.net/gh/kushidou/PicLibrary//img/202111150957877.png" alt="image-20211115095410121" loading="lazy" /></p> + + + + + diff --git a/public/tags/触摸屏/index.html b/public/tags/触摸屏/index.html new file mode 100644 index 0000000..38d3953 --- /dev/null +++ b/public/tags/触摸屏/index.html @@ -0,0 +1,522 @@ + + + + + + + +触摸屏 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + +
    + + + + + +
    + + + + + + + + diff --git a/public/tags/触摸屏/index.xml b/public/tags/触摸屏/index.xml new file mode 100644 index 0000000..f64c062 --- /dev/null +++ b/public/tags/触摸屏/index.xml @@ -0,0 +1,290 @@ + + + 香风家的火柴盒 – 触摸屏 + https://www.small09.top/tags/%E8%A7%A6%E6%91%B8%E5%B1%8F/ + Recent content in 触摸屏 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Tue, 24 Aug 2021 00:00:00 +0800 + + + + + + + + + + + Linux处理多触屏的终极解决方案 + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + Tue, 24 Aug 2021 00:00:00 +0800 + + https://www.small09.top/posts/210824-linuxtouchscreenfin/ + + + + <p>一般Linux操作系统接入多个触摸屏的最终解!</p> +<p>从<a href="https://www.small09.top/posts/210311-multitouchscreeninlinux/"target="_blank" rel="noopener">21年3月中旬开始</a>,我就在不断探索Linux操作系统下接入多个触摸屏且为拓展显示的解决方案,随着我们遇到的问题不断变得复杂,那篇博客也越来越乱越来越难读。直到今天我把里面最复杂的方案尝试成功后,我感觉我已经获得了Linux触摸屏的终极奥义。</p> +<p><em>重要:</em> 本文全文基于软件<code>xinput</code></p> +<p><em>更重要:</em> 如简介所说,仍有一种方法无法对应,具体为<code>【两个触摸屏的 VID:PID 与 设备描述 完全一致时,即两个完全相同的显示器】</code>,本文内容对这种情况仍然是无解的,个人认为只能通过硬件端口(/sys/class)来进行识别与调整,本文不涉及该方法(还没吃透)。</p> +<h2>0、核心:xinput<span class="hx:absolute hx:-mt-20" id="0核心xinput"></span> + <a href="#0%e6%a0%b8%e5%bf%83xinput" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>xinput是Linux下的一款配置测试X输入设备的神器,具有以下功能(我们将会用到的)</p> +<blockquote> + <p>xinput (list) : 直接键入xinput或者跟上list参数,就会列出本机所有输入设备,包括名称、ID、层级</p> +<p>xinput list &ndash;id-only : 仅仅列出设备ID(序号,每次开机都可能改变)</p> +<p>xinput map-to-output : 将输入设备映射到输出,比如将触屏映射到某个屏幕</p> +<p>xinput list-props : 列出某个设备的详细信息,后接参数为设备ID。</p> + +</blockquote> +<h2>1、简约:两个完全不同的触摸显示器<span class="hx:absolute hx:-mt-20" id="1简约两个完全不同的触摸显示器"></span> + <a href="#1%e7%ae%80%e7%ba%a6%e4%b8%a4%e4%b8%aa%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c%e7%9a%84%e8%a7%a6%e6%91%b8%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p><code>xinput map-to-output &quot;$DEVICE NAME A&quot; PORT-A</code></p> +<p>这里对应了两种情况,一是VID:PDI和设备描述符都不同,即毫无关系的两款;二是具有相同的VID:PID,但是设备描述符,这一般是同款芯片配了不同的固件。</p> +<p>首先通过xinput查看所有的输入设备:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>~$ xinput +</span></span><span style="display:flex;"><span>┌ Virtual core pointer id<span style="color:#f92672">=</span><span style="color:#ae81ff">2</span> <span style="color:#f92672">[</span>master pointer <span style="color:#f92672">(</span>3<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK TLITEK-TP id<span style="color:#f92672">=</span><span style="color:#ae81ff">9</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span>| ILITEK Multi-Touch id<span style="color:#f92672">=</span><span style="color:#ae81ff">7</span> <span style="color:#f92672">[</span>slave pointer <span style="color:#f92672">(</span>2<span style="color:#f92672">)]</span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">(</span>下略<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>可以看到<code>ILITEK ILITEK-TP</code>和<code>ILITEK Multi-Touch</code>显示为两个不同名称的设备,那么不论其VID:PID具体如何,我们都可以按如下方式进行映射:</p> +<p><code>xinput map-to-output &quot;ILITEK ILITEK-TP&quot; HDMI-0</code></p> +<p><code>xinput map-to-output &quot;ILITEK Multi-Touch&quot; HDMI-1</code></p> +<p>这种方式的要点是1.必须带英文<code>&quot;&quot;</code>,2.引号内名称须完整。<code>map-to-output</code>所携带的第一个参数为输入设备,不一定要设备id,使用设备名也是能够实现。</p> +<h2>2、复杂但通用:VID:PID不同的显示器<span class="hx:absolute hx:-mt-20" id="2复杂但通用vidpid不同的显示器"></span> + <a href="#2%e5%a4%8d%e6%9d%82%e4%bd%86%e9%80%9a%e7%94%a8vidpid%e4%b8%8d%e5%90%8c%e7%9a%84%e6%98%be%e7%a4%ba%e5%99%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>不论usb设备还是PCI设备,他们都有一个唯一识别符VID:PID,这相当于设备的身份证,同功能的设备,不同的厂商、不同的型号,就会有不同的ID。我们可以通过指定设备的VID:PID,筛选出输入设备的编号id,进行映射。</p> +<p>通过<code>xinput list-props [id]</code>可以得到某个设备的详细信息,其中就有十进制的VID,PID,如果这两个参数可以和指定的对应上,那么我们可以提取id映射到屏幕。<code>list-props</code>的结果示例如下:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#f92672">(</span>后续再补<span style="color:#f92672">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>其中<code>Device Product ID</code>一项有两个值,但不像是16进制的VID:PID,经过换算,他们其实是十进制的。利用grep+awk指令筛选出这两个值,和预先设定的比较,确定设备的id。写出来就是这样子:</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 先通过lsusb/xinput/xrandr 等命令确定如下硬件信息:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1、屏幕VID:PID(16进制)和显示接口,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA<span style="color:#f92672">=</span>222a +</span></span><span style="display:flex;"><span>PIDA<span style="color:#f92672">=</span><span style="color:#ae81ff">0001</span> +</span></span><span style="display:flex;"><span>PORTA<span style="color:#f92672">=</span>VGA-0 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2、先修算出10进制。如果xinput list-props [n] 的“Device Product ID”为16进制的,注释这2行</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 或者这里直接填写Device Product ID里面的结果,先VID后PID。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 字母O,不是数字0,多屏重复本段</span> +</span></span><span style="display:flex;"><span>VIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$VIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span>PIDA_O<span style="color:#f92672">=</span><span style="color:#66d9ef">$((</span><span style="color:#ae81ff">16#</span>$PIDA<span style="color:#66d9ef">))</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># =========自动处理步骤,请勿随意修改==========</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 根据设备ID取其序号</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3、屏映射,多屏重复本段</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$VIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$PIDA_O<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> xinput map-to-output $I $PORTA +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span>exit <span style="color:#ae81ff">0</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>这个脚本使用<code>xinput list --id-only</code>仅列出了设备的id序号,然后轮询所有的设备找出符合要求的编号,最后仍然使用<code>map-to-output</code>进行映射。这个脚本易于扩展,如果多个触摸屏只需要将3个有序号的注释后面的一块内容重复、修改名字就可以了。</p> +<h2>3、[2]的进阶版<span class="hx:absolute hx:-mt-20" id="32的进阶版"></span> + <a href="#32%e7%9a%84%e8%bf%9b%e9%98%b6%e7%89%88" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>前面都是通过shell/bash脚本的方式实现的重映射,如果要自动映射还需要添加自启动。如果设备进行了插拔,这个设备的映射就失效了,需要手动重新跑一跑脚本。我尝试了编写udev-rules文件,当触摸的设备发生变化时就延时启动映射脚本,但无一例外都是失败的,我觉得是udev生效的必xinput更早,无论延时多久都是这样。</p> +<p>在银河麒麟操作系统中我发现了它提供的触摸映射方法,使用的是<code>pyudev</code>库,采用第三方的方法实现udev-rules的效果,而且在这里延时不会影响系统的加载顺序,只需要系统额外安装<code>python3+pyudev</code>,并且使用desktop文件在开机时启动,即可。</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/python3</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- coding: UTF-8 --</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># -- Author: Blues --</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pyudev +</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>context <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Context() +</span></span><span style="display:flex;"><span>monitor <span style="color:#f92672">=</span> pyudev<span style="color:#f92672">.</span>Monitor<span style="color:#f92672">.</span>from_netlink(context) +</span></span><span style="display:flex;"><span>monitor<span style="color:#f92672">.</span>filter_by(subsystem<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;hid&#39;</span>) +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> device <span style="color:#f92672">in</span> iter(monitor<span style="color:#f92672">.</span>poll, <span style="color:#66d9ef">None</span>): +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>driver <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;hid-multitouch&#34;</span>: +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;add&#34;</span> <span style="color:#f92672">or</span> device<span style="color:#f92672">.</span>action <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;change&#34;</span>: +</span></span><span style="display:flex;"><span> os<span style="color:#f92672">.</span>system(<span style="color:#e6db74">&#34;/var/my-touchscreen/remap-screen&#34;</span>)</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>上面直接贴了麒麟自带的代码,就修改了一下脚本的路径,经过多次验证,在uos下可以正常使用。借此机会我也参照麒麟的代码修改了一下,用户不用看命令行那些多余的东西,只需要点击几个GUI窗口,程序就可以按照<em>设备名、VID、UID、物理接口</em>四大特征,自动映射到对应的显示屏上了。</p> + + + + + Linux多显示器触屏调试与暂行解决方案 + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + Thu, 11 Mar 2021 00:00:00 +0800 + + https://www.small09.top/posts/210311-multitouchscreeninlinux/ + + + + <p>由于需要使用新创平台配合多个显示器,触摸屏经常出现不准的情况。经过多次的查阅资料、测试和整合,总结了一个解决触摸的方法。一些测试过程中的弯路也保留了,作为记录。</p> +<p><em>2021-03-22更新部分内容</em> +<em>2021-04-28再次更新内容</em></p> +<p><strong>2021-09-14 文章已有新版本,请访问《<a href="https://www.small09.top/posts/210824-linuxtouchscreenfin/"target="_blank" rel="noopener">Linux处理多触屏的终极解决方案</a>》</strong></p> +<h2>问题来源<span class="hx:absolute hx:-mt-20" id="问题来源"></span> + <a href="#%e9%97%ae%e9%a2%98%e6%9d%a5%e6%ba%90" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>最近客户要求使用信创平台,即国产CPU+国产操作系统平台。现在这方面主要使用的操作系统为麒麟和通信uos。这两个的本质都是debian系的,所以很多地方的调试与使用都可以按乌班图的经验来操作。一款产品要使用三块显示器,其中两块是触屏。屏幕通过R5 420显卡,都转换成VGA连接。 +接入设备后,发现触摸屏默认是映射到全屏幕,在任何显示器上操作都会按排列顺序在对应的区域触发。所以需要给每个显示器分别映射触摸。映射的基本指令为</p> +<p><code>xinput map-to-output $触屏设备id $显示屏名称</code></p> +<blockquote> + <p>2021-04-28更新</p> +<p><code>xinput map-to-output</code>指令支持触摸屏名称作为参数,比如我通过<code>xinput</code>指令看到有一个触摸屏的设备名称为<code>ILITEK XXXX</code>,那么我映射的时候直接输入</p> +<blockquote> + <p><code>xinput map-to-output &quot;ILITEK XXX&quot; HDMI-1</code></p> + +</blockquote> + +</blockquote> +<h2>尝试解决<span class="hx:absolute hx:-mt-20" id="尝试解决"></span> + <a href="#%e5%b0%9d%e8%af%95%e8%a7%a3%e5%86%b3" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ul> +<li>首先使用xinput查看触摸设备及其序号$id。这里可以看到多个设备,可以通过单独插拔的方式找到每个设备。</li> +<li><del>然后<code>xinput list-props $id</code>查看设备的VID,这个在后续自动化处理有用。</del></li> +<li><code>xrandr</code>查看<code>$显示器名称</code></li> +</ul> +<h2>自动化处理<span class="hx:absolute hx:-mt-20" id="自动化处理"></span> + <a href="#%e8%87%aa%e5%8a%a8%e5%8c%96%e5%a4%84%e7%90%86" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># 2021-04-28更新:</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 本部分后面的都可以不看了!</span> +</span></span><span style="display:flex;"><span>xinput map-to-output <span style="color:#e6db74">&#34;DEVICE_NAME&#34;</span> DISPLAY +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 把这一段写入可以自动执行的脚本里面,比如/etc/rc.local, ~/.bashrc, /etc/profile, </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 以及制作成service等。有几个触摸屏就写几行指令,比下面的简洁了太多~</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p><strong>仅作归档!下面这些方法过于繁琐!</strong></p> +<p>利用VID和显示器名称作为参数,运行下面的脚本,或者直接手动执行重映射,即可解决触屏作用范围问题。<code>bash this.sh VID1 VID2 ScreenID</code></p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 这段适合触屏硬件id不同的情况,如果遇到id不同需要其他方法。</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:在前几天的实操中,发现字符串比较部分出现了问题,这个方案的可行性有待进一步探索。现在采用另一种方案。</span> +</span></span><span style="display:flex;"><span>MYID_FIRST<span style="color:#f92672">=</span>$1 +</span></span><span style="display:flex;"><span>MYID_SECOND<span style="color:#f92672">=</span>$2 +</span></span><span style="display:flex;"><span>OUT_PUT<span style="color:#f92672">=</span>$3 +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> I in <span style="color:#66d9ef">$(</span>xinput list --id-only<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">do</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#[1]</span> +</span></span><span style="display:flex;"><span> CUR_FIRST<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $1}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> CUR_SECOND<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput list-props $I|grep <span style="color:#e6db74">&#34;Device Product ID&#34;</span>|awk -F : <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk -F , <span style="color:#e6db74">&#39;{print $2}&#39;</span>|awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_FIRST<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$MYID_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;</span>$CUR_SECOND<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">then</span> +</span></span><span style="display:flex;"><span> MY_INDEX<span style="color:#f92672">=</span>$I +</span></span><span style="display:flex;"><span> break; +</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">fi</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span>xinput map-to-output $MY_INDEX $OUT_PUT +</span></span><span style="display:flex;"><span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI2</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e">#./this.sh VID VID HDMI1</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash +</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># 2021-03-22更新:这种方案只要四行就能实现一块的映射,需要时只要复制这个代码块即可,修改DEVICE_NAME 和 SCREEN_NAME 。如果名称中带有重复的部分,可以再加一个grep -v xxxx来排除关键词。但是如果遇到屏幕完全重名的情况,就不可解了。</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>xinput | grep -iw DEVICE_NAME<span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID#*id=<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>TOUCH_ID<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">${</span>TOUCH_ID%% *<span style="color:#e6db74">}</span><span style="color:#66d9ef">)</span> +</span></span><span style="display:flex;"><span>xinput map-to-output $TOUCH_ID SCREEN_NAME</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<p>此方法可以在直到触屏VID后,(一般多显示器使用的是不同的型号),<del>使用自启动rc.local开机执行重映射指令,保证每次开机都能准确找到屏幕,且适用于系列产品不用单独找id</del>。</p> +<p>经测试rc.local由于只在开机执行,如果分辨率在开机的时候改变了(比如用了转接头分辨率会重新调整),映射关系还是会出错。我决定用一个取巧的办法,把执行脚本的命令放在<code>~/.bashrc</code>中,这样只需要打开一次终端,就可以实现屏幕重新映射。</p> +<h2>后续问题<span class="hx:absolute hx:-mt-20" id="后续问题"></span> + <a href="#%e5%90%8e%e7%bb%ad%e9%97%ae%e9%a2%98" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><ol> +<li>当屏幕参数改变,如调整分辨率、调整顺序、插拔屏幕、换屏幕等,都会引发映射的失效,需要再次执行脚本。</li> +<li>当屏幕发生改变,需要调整脚本中固化的参数,或调整执行脚本的输入参数</li> +<li>如果屏幕使用了相同的驱动ic,会看到同样的VID,这样子会难以区分。这时候把[1]处的判断条件改成按名称或者其他不同的参数,也能够自动适配。但两块完全一样的屏幕,现阶段还没想到解决方法,只能自己手动映射试试了。</li> +</ol> +<h2>触屏校准(如果需要)<span class="hx:absolute hx:-mt-20" id="触屏校准如果需要"></span> + <a href="#%e8%a7%a6%e5%b1%8f%e6%a0%a1%e5%87%86%e5%a6%82%e6%9e%9c%e9%9c%80%e8%a6%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>网上说的利用校准来区分多触摸屏的方法是<strong>完全错误</strong>的,仅适用于他们的产品而不是通用解决方案,很多人就是抄来抄去而已。但是如果遇到触摸偏移,还是有需要进行校准的,校准方法为(最后一步需要甄别,有些系统没有这个目录)</p> +<ul> +<li><code>sudo apt install xinput-calibrator</code>,需要更新源</li> +<li><code>sudo xinput_calibrator</code></li> +<li>将校准信息保存到<code>/usr/share/X11/xorg.conf.d/10-evdev.conf</code>的&quot;Driver evdev”下面一行,重启生效。</li> +</ul> +<h2>参考:<span class="hx:absolute hx:-mt-20" id="参考"></span> + <a href="#%e5%8f%82%e8%80%83" class="subheading-anchor" aria-label="此章节的永久链接"></a></h2><p>[1]https://blog.csdn.net/yuan_da_xian/article/details/72864008 +[2]https://blog.csdn.net/qq_33406883/article/details/106649087 +[3]https://segmentfault.com/a/1190000020334077</p> + + + + + diff --git a/public/tags/软件/index.html b/public/tags/软件/index.html new file mode 100644 index 0000000..bd8deb3 --- /dev/null +++ b/public/tags/软件/index.html @@ -0,0 +1,510 @@ + + + + + + + +软件 – 香风家的火柴盒 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 跳至内容 + + + + + + + + + + + diff --git a/public/tags/软件/index.xml b/public/tags/软件/index.xml new file mode 100644 index 0000000..4a2571a --- /dev/null +++ b/public/tags/软件/index.xml @@ -0,0 +1,97 @@ + + + 香风家的火柴盒 – 软件 + https://www.small09.top/tags/%E8%BD%AF%E4%BB%B6/ + Recent content in 软件 on 香风家的火柴盒 + Hugo -- gohugo.io + zh-cn + Mon, 27 Nov 2023 09:33:32 +0800 + + + + + + + + + + + Linux安装VNC服务端 + https://www.small09.top/posts/231127-vncserver/ + Mon, 27 Nov 2023 09:33:32 +0800 + + https://www.small09.top/posts/231127-vncserver/ + + + + <p><em>(笔记)在Linux系统里安装远程桌面VNC客户端并连接。</em></p> +<h3>1. 安装NVC服务端<span class="hx:absolute hx:-mt-20" id="1-安装nvc服务端"></span> + <a href="#1-%e5%ae%89%e8%a3%85nvc%e6%9c%8d%e5%8a%a1%e7%ab%af" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt install x11vnc</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<h3>2. 设置远程连接密码<span class="hx:absolute hx:-mt-20" id="2-设置远程连接密码"></span> + <a href="#2-%e8%ae%be%e7%bd%ae%e8%bf%9c%e7%a8%8b%e8%bf%9e%e6%8e%a5%e5%af%86%e7%a0%81" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>x11vnc --storepasswd +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 输入两次连接密码以设定</span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 询问 Write password to /home/xxx/.vnc/passwd </span> +</span></span><span style="display:flex;"><span><span style="color:#75715e"># 时,输入y并回车保存密码。</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ul> +<li>该步骤不需要使用sudo!</li> +</ul> +<h3>3. 使VNC服务开机自启动<span class="hx:absolute hx:-mt-20" id="3-使vnc服务开机自启动"></span> + <a href="#3-%e4%bd%bfvnc%e6%9c%8d%e5%8a%a1%e5%bc%80%e6%9c%ba%e8%87%aa%e5%90%af%e5%8a%a8" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>使用service的方法,将vnc作为一个服务开机启动</p> +<p>创建<code>/lib/systemd/system/x11vnc.service</code>文件并填入下面的内容</p> +<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"> + +<div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#66d9ef">[Unit]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Description</span><span style="color:#f92672">=</span><span style="color:#e6db74">Start x11vnc at startup.</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">After</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Service]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Type</span><span style="color:#f92672">=</span><span style="color:#e6db74">simple</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ExecStart</span><span style="color:#f92672">=</span><span style="color:#e6db74">/usr/bin/x11vnc -auth guess -rfbauth /home/xxx/.vnc/passwd</span> +</span></span><span style="display:flex;"><span><span style="color:#66d9ef">[Install]</span> +</span></span><span style="display:flex;"><span><span style="color:#a6e22e">WantedBy</span><span style="color:#f92672">=</span><span style="color:#e6db74">multi-user.target</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"> + <button + class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50" + title="复制代码" + aria-label="复制代码" + data-copied-label="已复制!" + > + <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div> +<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div> + </button> +</div> +</div> +<ul> +<li>注意,/home/xxx 需要替换成实际的用户名!</li> +</ul> +<h3>使用vnc客户端连接<span class="hx:absolute hx:-mt-20" id="使用vnc客户端连接"></span> + <a href="#%e4%bd%bf%e7%94%a8vnc%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5" class="subheading-anchor" aria-label="此章节的永久链接"></a></h3><p>我使用Windows上的VncViewer,官网<a href="https://www.realvnc.com/en/"target="_blank" rel="noopener">RealVNC</a>,这个软件假冒的很多,下载需要注意甄别。</p> + + + + + diff --git a/resources/blog_main.jpg b/resources/blog_main.jpg new file mode 100644 index 0000000..a55da1e Binary files /dev/null and b/resources/blog_main.jpg differ diff --git a/resources/daily_main.png b/resources/daily_main.png new file mode 100644 index 0000000..71ea96c Binary files /dev/null and b/resources/daily_main.png differ diff --git a/resources/photograph_main.jpg b/resources/photograph_main.jpg new file mode 100644 index 0000000..d8efb67 Binary files /dev/null and b/resources/photograph_main.jpg differ diff --git a/static/resources/blog_main.jpg b/static/resources/blog_main.jpg new file mode 100644 index 0000000..a55da1e Binary files /dev/null and b/static/resources/blog_main.jpg differ diff --git a/static/resources/daily_main.png b/static/resources/daily_main.png new file mode 100644 index 0000000..71ea96c Binary files /dev/null and b/static/resources/daily_main.png differ diff --git a/static/resources/icon_180x180.png b/static/resources/icon_180x180.png new file mode 100644 index 0000000..8d2aa84 Binary files /dev/null and b/static/resources/icon_180x180.png differ diff --git a/static/resources/icon_32x32.png b/static/resources/icon_32x32.png new file mode 100644 index 0000000..4f7f5ee Binary files /dev/null and b/static/resources/icon_32x32.png differ diff --git a/static/resources/photograph_main.jpg b/static/resources/photograph_main.jpg new file mode 100644 index 0000000..d8efb67 Binary files /dev/null and b/static/resources/photograph_main.jpg differ diff --git a/static/resources/头像.jpg b/static/resources/头像.jpg new file mode 100644 index 0000000..9de17bd Binary files /dev/null and b/static/resources/头像.jpg differ