├── config.yaml
├── screenshots
├── ChatView.jpeg
├── DarkMode.jpeg
└── EnterRoom.jpeg
├── go.mod
├── docker-compose.yml
├── util
├── str.go
└── socket.go
├── constant
└── constant.go
├── server
├── server.go
└── connection.go
├── Dockerfile
├── .gitignore
├── go.sum
├── config
└── config.go
├── main.go
├── LICENSE
├── conversation
├── client.go
└── manager.go
├── .github
└── workflows
│ └── docker-image.yml
├── README.md
├── templates
├── ddiu.html
└── bulma.html
└── static
├── index.6369ed8c.css
└── index.a243aca3.css
/config.yaml:
--------------------------------------------------------------------------------
1 | port: 8080
2 | server_url: "" # 此处为html页面去请求服务器的地址,例子:8.8.8.8:8080 或 abc.com 或 abc.com:8080
3 |
--------------------------------------------------------------------------------
/screenshots/ChatView.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byebyehair/minichat/HEAD/screenshots/ChatView.jpeg
--------------------------------------------------------------------------------
/screenshots/DarkMode.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byebyehair/minichat/HEAD/screenshots/DarkMode.jpeg
--------------------------------------------------------------------------------
/screenshots/EnterRoom.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/byebyehair/minichat/HEAD/screenshots/EnterRoom.jpeg
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module minichat
2 |
3 | go 1.22
4 |
5 | require (
6 | github.com/gorilla/websocket v1.5.1
7 | gopkg.in/yaml.v3 v3.0.1
8 | )
9 |
10 | require golang.org/x/net v0.26.0 // indirect
11 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | server:
5 | image: okhanyu/minichat:latest
6 | container_name: minichat
7 | restart: always
8 | ports:
9 | - "8080:8080"
10 | volumes:
11 | - "./config.yaml:/app/config.yaml"
12 | environment:
13 | - TEMPLATE_NAME=bulma
14 |
--------------------------------------------------------------------------------
/util/str.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 | )
7 |
8 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
9 |
10 | func RandomString(length int) string {
11 | //rand.Seed(time.Now().UnixNano())
12 | rand.New(rand.NewSource(time.Now().UnixNano()))
13 | b := make([]byte, length)
14 | for i := range b {
15 | b[i] = charset[rand.Intn(len(charset))]
16 | }
17 | return string(b)
18 | }
19 |
--------------------------------------------------------------------------------
/constant/constant.go:
--------------------------------------------------------------------------------
1 | package constant
2 |
3 | const (
4 | CmdChat = "chat"
5 | CmdJoin = "join"
6 | CmdExit = "exit"
7 | CmdJoinPasswordFail = "join-pwd-fail"
8 | JoinSuccess = "Join success 🌟! "
9 | ExitSuccess = "Exit success 🌟! "
10 | JoinFailByRoomEmpty = "Joined fail 😭, room can't is empty ! "
11 | JoinFailByPassword = "Joined fail 😭, password is error ! "
12 | Online = "Current online user list: "
13 | )
14 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | type ResponseData struct {
4 | Code int `json:"code"`
5 | Info string `json:"info"`
6 | Data any `json:"data"`
7 | }
8 |
9 | const (
10 | SuccessCode = 10000
11 | ErrorCodePassword = 20001
12 | ErrorCodeOnceToken = 20002
13 | ErrorCodeUsernameRepeat = 20003
14 | )
15 |
16 | type PreCheckParam struct {
17 | RoomNumber string `json:"room_number"`
18 | UserName string `json:"username"`
19 | Password string `json:"password"`
20 | }
21 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.20.4-alpine3.18 AS builder
2 |
3 | COPY . /src
4 | WORKDIR /src
5 |
6 | #国内服务器可以取消以下注释
7 | #RUN go env -w GO111MODULE=on && \
8 | # go env -w GOPROXY=https://goproxy.cn,direct
9 |
10 | RUN go build -ldflags "-s -w" -o ./bin/ .
11 |
12 | FROM alpine
13 |
14 | COPY --from=builder /src/bin /app
15 | COPY --from=builder /src/config.yaml /app/config.yaml
16 |
17 | WORKDIR /app
18 |
19 | EXPOSE 8080
20 |
21 | # 设置时区
22 | RUN apk add --no-cache tzdata
23 | ENV TZ=Asia/Shanghai
24 |
25 | ENTRYPOINT ["./minichat"]
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 | go.work.sum
23 |
24 |
25 | .idea
26 | .DS_Store
--------------------------------------------------------------------------------
/util/socket.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "github.com/gorilla/websocket"
5 | "log"
6 | )
7 |
8 | func SocketSend(conn *websocket.Conn, byteData []byte) error {
9 | err := conn.WriteMessage(websocket.TextMessage, byteData)
10 | if err != nil {
11 | log.Printf("websocket send error, error is %+v", err)
12 | return err
13 | }
14 | return nil
15 | }
16 |
17 | func SocketReceive(conn *websocket.Conn) ([]byte, error) {
18 | _, message, err := conn.ReadMessage()
19 | //log.Printf("receive message, error is %+v, message type is %d", err, messageType)
20 | if err != nil {
21 | return nil, err
22 | //err := c.conn.WriteMessage(websocket.CloseMessage, []byte{})
23 | }
24 | return message, nil
25 | }
26 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
2 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
3 | github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
4 | github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
5 | golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
6 | golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "gopkg.in/yaml.v3"
5 | "log"
6 | "os"
7 | "path/filepath"
8 | )
9 |
10 | type Config struct {
11 | Port int `yaml:"port"`
12 | ServerUrl string `yaml:"server_url"`
13 | }
14 |
15 | var GlobalConfig *Config
16 |
17 | func ParseConfig(filename string) *Config {
18 |
19 | // 获取当前可执行文件的完整路径
20 | executable, err := os.Executable()
21 | if err != nil {
22 | log.Fatalf("\n\nUnable to get executable path: %+v\n\n", err)
23 | }
24 |
25 | // 如使用IDE调试,请改为本地路径
26 | dir := filepath.Dir(executable)
27 | configPath := filepath.Join(dir, filename)
28 |
29 | data, err := os.ReadFile(configPath)
30 | if err != nil {
31 | log.Fatalf("\n\nNot found config file, %+v\n\n", err)
32 | }
33 |
34 | var cfg *Config
35 | err = yaml.Unmarshal(data, &cfg)
36 | if err != nil {
37 | log.Fatalf("\n\nUnable to parse config file, %+v\n\n", err)
38 | }
39 | GlobalConfig = cfg
40 | return cfg
41 | }
42 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "embed"
5 | "fmt"
6 | "log"
7 | "minichat/config"
8 | "minichat/conversation"
9 | "minichat/server"
10 | "net/http"
11 | )
12 |
13 | //go:embed static
14 | var DirStatic embed.FS
15 |
16 | //go:embed templates/*
17 | var DirTemplate embed.FS
18 |
19 | func main() {
20 |
21 | http.HandleFunc("/precheck", server.PreCheck)
22 | http.HandleFunc("/ws", server.HandleWs)
23 | http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
24 | server.HandleFiles(writer, request, DirTemplate)
25 | })
26 | fs := http.FileServer(http.FS(DirStatic))
27 | http.Handle("/static/", fs)
28 |
29 | go conversation.Manager.Start()
30 |
31 | configVal := config.ParseConfig("config.yaml")
32 |
33 | log.Printf("\n\n********************************\nChat server is running at %d !\n********************************\n\n", configVal.Port)
34 | err := http.ListenAndServe(fmt.Sprintf(":%d", configVal.Port), nil)
35 | if err != nil {
36 | fmt.Printf("Server start fail, error is: [ %+v ]", err)
37 | return
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 okhanyu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/conversation/client.go:
--------------------------------------------------------------------------------
1 | package conversation
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "minichat/constant"
7 | "minichat/util"
8 | )
9 |
10 | func (c *Client) Read() {
11 | defer func() {
12 | Manager.unregister <- c
13 | }()
14 |
15 | for {
16 | message, err := util.SocketReceive(c.Conn)
17 | if err != nil {
18 | return
19 | }
20 | Manager.broadcast <- Message{
21 | UserName: c.UserName,
22 | RoomNumber: c.RoomNumber,
23 | Payload: string(message),
24 | Cmd: constant.CmdChat,
25 | }
26 | }
27 | }
28 |
29 | func (c *Client) Write() {
30 | for {
31 | select {
32 | case message, isOpen := <-c.Send:
33 | if !isOpen {
34 | log.Printf("chan is closed")
35 | return
36 | }
37 |
38 | byteData, err := json.Marshal(message)
39 | if err != nil {
40 | log.Printf("json marshal error, error is %+v", err)
41 | } else {
42 | err = util.SocketSend(c.Conn, byteData)
43 | if err != nil {
44 | log.Printf("ocket send error, error is %+v", err)
45 | return
46 | }
47 | }
48 | case makeStop := <-c.Stop:
49 | if makeStop {
50 | break
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/.github/workflows/docker-image.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v4
15 |
16 | - name: Set up QEMU
17 | uses: docker/setup-qemu-action@v3
18 |
19 | - name: Extract build args
20 | # Example: branch name `release/1.0.0` sets up env.VERSION=1.0.0
21 | run: |
22 | echo "VERSION=${GITHUB_REF_NAME#release/}" >> $GITHUB_ENV
23 |
24 | - name: Set up Docker Buildx
25 | uses: docker/setup-buildx-action@v3
26 |
27 | - name: Login to Docker Hub
28 | uses: docker/login-action@v3
29 | with:
30 | username: ${{ secrets.DOCKER_HUB_USER }}
31 | password: ${{ secrets.DOCKER_HUB_PWD }}
32 |
33 | - name: Login to GitHub Container Registry
34 | if: github.event_name != 'pull_request'
35 | uses: docker/login-action@v3
36 | with:
37 | registry: ghcr.io
38 | username: ${{ github.repository_owner }}
39 | password: ${{ secrets.GITHUB_TOKEN }}
40 | # ${{ github.actor }}
41 | # ${{ github.token }}
42 |
43 | - name: Docker meta
44 | id: meta
45 | uses: docker/metadata-action@v5
46 | with:
47 | images: |
48 | ${{ github.repository_owner }}/minichat
49 | ghcr.io/${{ github.repository_owner }}/minichat
50 | # ${{ env.REGISTRY }}/${{ github.repository }}
51 | tags: |
52 | type=ref,event=branch
53 | type=ref,event=pr
54 | type=semver,pattern={{version}}
55 | type=semver,pattern={{major}}.{{minor}}
56 |
57 | - name: Build and push Docker image
58 | uses: docker/build-push-action@v5
59 | with:
60 | context: .
61 | file: ./Dockerfile
62 | platforms: linux/amd64,linux/arm64
63 | push: ${{ github.event_name != 'pull_request' }}
64 | tags: ${{ steps.meta.outputs.tags }}
65 | labels: ${{ steps.meta.outputs.labels }}
66 | # tags: ghcr.io/${{ github.repository_owner }}/minichat:latest
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MiniChat
2 |
3 | [click to demo](https://talks.im)
4 |
5 | MiniChat 是一款Go语言实现的极简、极轻、无痕匿名聊天工具,开发此程序的本意是用于自己平日与好友临时讨论敏感话题时使用,现开源共享,代码简陋,请多包涵。
6 |
7 | **如有兴致,还请赏个 github 的 Star .**
8 |
9 | ## 特性
10 |
11 | * 无数据库、无多余组件、打包后镜像仅 16M
12 | * 支持自定义服务端口、支持单独配置后端服务 Server API
13 | * 良好的匿名特性、安全隐私特性,数据只在服务器内存中毫秒级短暂中转不留痕
14 | * 支持房间密码、后进房间不能观看之前记录等特性,非常安全
15 | * 只需随意填写用户昵称,无任何真实信息,非常安全
16 | * 所有人离开房间即销毁,非常安全
17 | * 简洁但用心的界面布局
18 | * 适配手机端、Web端
19 |
20 | ## 使用说明
21 |
22 | > 🌟 其实只需要两步:
23 | > 你输入地址进入,输入昵称,开始聊天
24 | > 复制给别人地址,别人进入,输入昵称,开始聊天
25 |
26 | 1. 进入页面,若地址栏未携带 room=xxx 的参数,将随机产生一个新房间
27 | 2. 输入自己的昵称(随意),点击进入房间即可
28 | 3. 将房间地址复制发送给其他小伙伴,小伙伴输入他自己的昵称(随意)后,将进入你的房间
29 | 4. 开始愉快且无痕且私密的聊天
30 | 5. 当所有房间内的人都离开后,房间会立刻销毁
31 |
32 | ## 部署方式
33 |
34 | ### 创建目录和配置文件
35 |
36 | 1. 创建 minichat 目录并 `cd` 到该目录下
37 | ```
38 | mkdir minichat
39 | cd minichat
40 | ```
41 |
42 | 2. 创建 config.yaml 配置文件
43 | ```
44 | cat << EOF > ./minichat/config.yaml
45 | port: 8080
46 | server_url: ""
47 | EOF
48 | ```
49 |
50 | 以下方式默认在 minichat 目录下操作
51 |
52 | ### Docker compose 方式(推荐)
53 |
54 | 1. 环境要求: Docker、Docker-Compose
55 | 2. 创建目录并下载 docker-compose.yaml:
56 | ```
57 | wget https://raw.githubusercontent.com/okhanyu/minichat/master/docker-compose.yml
58 | ```
59 | 3. 修改 docker-compose.yml 文件,按需修改端口号(只需修改 docker-compose.yml 文件中 - "8080:8080" 中前面的8080即可,后面的 8080 代表容器内的端口要和 config.yaml 中的端口一致,后面默认 8080 即可无需修改)
60 | 4. 如有特殊需要,修改 config.yaml 文件,页面请求服务器的接口地址 server_url,如页面和服务使用同域名同端口,server_url 留空无需修改即可
61 | 5. 进入刚 clone 的 minichat 文件夹,执行
62 | ```
63 | docker-compose up -d
64 | ```
65 | 6. 部署成功后,通过ip+端口号访问
66 |
67 | ### Docker run 方式
68 |
69 | 1. 环境要求: Docker
70 | 2. 如有特殊需要,修改 config.yaml 文件,修改页面请求服务器的接口地址 server_url,如页面和服务使用同域名同端口,server_url 留空无需修改即可
71 | 3. 执行(只需修改 -p 8080:8080 中前面的 8080 即可,后面的 8080 代表容器内的端口,要和 config.yaml 中的端口一致,后面默认 8080 即可无需修改)
72 | ```
73 | docker pull okhanyu/minichat:latest
74 | docker run -d --name minichattest --restart always \
75 | -p 8080:8080 \
76 | -v ./config.yaml:/app/config.yaml \
77 | -e TEMPLATE_NAME="bulma" \
78 | okhanyu/minichat:latest
79 | ```
80 | 4. 部署成功后,通过ip+端口号访问
81 |
82 | ### 二进制直接运行 方式(无需 Docker)
83 |
84 | 1. 环境要求: 无
85 | 2. minichat 文件夹内创建文件 config.yaml
86 | 3. 修改 config.yaml 文件,按需修改端口号和页面请求服务器的接口地址 server_url,如页面和服务使用同域名同端口,server_url 留空即可
87 | ```
88 | port: 8080
89 | server_url: ""
90 | ```
91 | 4. minichat 文件夹内,放置下载并解压好的[可执行文件](https://github.com/okhanyu/minichat/releases/),保证 config.yaml 和 可执行文件在同目录下,双击打开可执行文件即可
92 | 5. 成功后,通过ip+端口号访问
93 |
94 | ### 环境变量
95 |
96 | * `TEMPLATE_NAME` 页面模板,支持 `bulma | ddiu`,默认使用 `bulma`
97 |
98 | ## 其他
99 | * 如果你需要的是一套支持自部署的完整、强大聊天室系统,请使用 [mattermost.com](https://mattermost.com/)
100 | * 如果你需要的是一款支持自部署的复古、匿名聊天室系统,请使用 [hack.chat](https://hack.chat/)
101 |
102 | ## 模板作者
103 |
104 | * ddiu by [ddiu8081](https://ddiu.io)
105 | * bulma by [@alanoy](https://ideapart.com)
106 | ---
107 |
108 | 
109 | 
110 | 
111 |
112 | ---
113 |
114 | [](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
115 |
--------------------------------------------------------------------------------
/conversation/manager.go:
--------------------------------------------------------------------------------
1 | package conversation
2 |
3 | import (
4 | "github.com/gorilla/websocket"
5 | "log"
6 | "minichat/constant"
7 | "strings"
8 | "sync"
9 | )
10 |
11 | type ConversationManager struct {
12 | Rooms map[string]*Room
13 | Register chan *Client
14 | unregister chan *Client
15 | broadcast chan Message
16 | registerLock *sync.RWMutex
17 | unregisterLock *sync.RWMutex
18 | broadcastLock *sync.RWMutex
19 | }
20 |
21 | type Message struct {
22 | RoomNumber string `json:"room_number"`
23 | UserName string `json:"username"`
24 | Cmd string `json:"cmd"`
25 | Payload string `json:"payload"`
26 | }
27 |
28 | type Client struct {
29 | //cmd string
30 | RoomNumber string
31 | UserName string
32 | Password string
33 | Send chan Message
34 | Conn *websocket.Conn
35 | Stop chan bool
36 | }
37 |
38 | type Room struct {
39 | Clients map[*Client]*Client
40 | RoomName string
41 | Password string
42 | }
43 |
44 | var Manager = ConversationManager{
45 | broadcast: make(chan Message),
46 | Register: make(chan *Client),
47 | unregister: make(chan *Client),
48 | Rooms: make(map[string]*Room),
49 | registerLock: new(sync.RWMutex),
50 | unregisterLock: new(sync.RWMutex),
51 | broadcastLock: new(sync.RWMutex),
52 | }
53 |
54 | func (manager *ConversationManager) Start() {
55 | for {
56 | select {
57 | case client := <-manager.Register:
58 | // 新客户端链接
59 | manager.registerLock.Lock()
60 | if _, ok := manager.Rooms[client.RoomNumber]; !ok {
61 | manager.Rooms[client.RoomNumber] = &Room{
62 | Clients: make(map[*Client]*Client),
63 | Password: client.Password,
64 | }
65 | }
66 | // 塞入房间初次数据
67 | manager.Rooms[client.RoomNumber].Clients[client] = client
68 | go func() {
69 | names := ""
70 | for key, _ := range manager.Rooms[client.RoomNumber].Clients {
71 | names += "[ " + key.UserName + " ], "
72 | }
73 | names = "" + strings.TrimSuffix(names, ", ") + ""
74 | manager.broadcast <- Message{
75 | UserName: client.UserName,
76 | Payload: constant.JoinSuccess + constant.Online + names,
77 | RoomNumber: client.RoomNumber,
78 | Cmd: constant.CmdJoin,
79 | }
80 | }()
81 | manager.registerLock.Unlock()
82 |
83 | case client := <-manager.unregister:
84 | // 客户端断开链接
85 | manager.unregisterLock.Lock()
86 | err := client.Conn.Close()
87 | if err != nil {
88 | return
89 | }
90 | if _, ok := manager.Rooms[client.RoomNumber]; ok {
91 | delete(manager.Rooms[client.RoomNumber].Clients, client)
92 | if len(manager.Rooms[client.RoomNumber].Clients) == 0 {
93 | delete(manager.Rooms, client.RoomNumber)
94 | }
95 | //client.stop <- true
96 | safeClose(client.Send)
97 |
98 | if manager.Rooms != nil && len(manager.Rooms) != 0 && manager.Rooms[client.RoomNumber] != nil && client.RoomNumber != "" {
99 | for c, _ := range manager.Rooms[client.RoomNumber].Clients {
100 | names := ""
101 | for key, _ := range manager.Rooms[client.RoomNumber].Clients {
102 | names += "[ " + key.UserName + " ], "
103 | }
104 | names = strings.TrimSuffix(names, ", ")
105 | names = "" + strings.TrimSuffix(names, ", ") + ""
106 | c.Send <- Message{
107 | UserName: client.UserName,
108 | Payload: constant.ExitSuccess + constant.Online + names,
109 | RoomNumber: client.RoomNumber,
110 | Cmd: constant.CmdExit,
111 | }
112 | }
113 | }
114 | }
115 | manager.unregisterLock.Unlock()
116 |
117 | case message := <-manager.broadcast:
118 | // 广播消息
119 | manager.broadcastLock.RLock()
120 | if manager.Rooms != nil && len(manager.Rooms) != 0 && manager.Rooms[message.RoomNumber] != nil && message.RoomNumber != "" {
121 | for c, _ := range manager.Rooms[message.RoomNumber].Clients {
122 | if c != nil && c.Conn != nil && c.Send != nil {
123 | c.Send <- message
124 | }
125 | }
126 | }
127 | manager.broadcastLock.RUnlock()
128 | }
129 |
130 | }
131 | }
132 |
133 | func safeClose(ch chan Message) {
134 | defer func() {
135 | if recover() != nil {
136 | log.Println("ch is closed")
137 | }
138 | }()
139 | close(ch)
140 | log.Println("ch closed successfully")
141 | }
142 |
--------------------------------------------------------------------------------
/server/connection.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/gorilla/websocket"
7 | "html/template"
8 | "io"
9 | "io/fs"
10 | "io/ioutil"
11 | "log"
12 | "minichat/config"
13 | "minichat/conversation"
14 | "minichat/util"
15 | "net/http"
16 | "time"
17 | "os"
18 | )
19 |
20 | var (
21 | upgrader = websocket.Upgrader{
22 | ReadBufferSize: 1024,
23 | WriteBufferSize: 1024,
24 | CheckOrigin: func(r *http.Request) bool {
25 | return true
26 | },
27 | //CheckOrigin: func(r *http.Request) bool {
28 | // return r.Header.Get("Origin") == ""
29 | //},
30 | }
31 | )
32 |
33 | var OnceTokenMap = make(map[string]map[string]string)
34 |
35 | func PreCheck(w http.ResponseWriter, r *http.Request) {
36 | //query := r.URL.Query()
37 | //roomNumber := query.Get("room_number")
38 | //userName := query.Get("username")
39 | //password := query.Get("password")
40 |
41 | // 确保请求方法是POST
42 | if r.Method != http.MethodPost {
43 | http.Error(w, "Invalid request method", http.StatusBadRequest)
44 | return
45 | }
46 |
47 | // 读取请求体
48 | body, err := ioutil.ReadAll(r.Body)
49 | if err != nil {
50 | http.Error(w, "Error reading request body", http.StatusInternalServerError)
51 | return
52 | }
53 | defer func(Body io.ReadCloser) {
54 | err := Body.Close()
55 | if err != nil {
56 | log.Printf("body close error: %+v", err)
57 | }
58 | }(r.Body)
59 |
60 | // 解析JSON数据到结构体中
61 | var requestBody PreCheckParam
62 | err = json.Unmarshal(body, &requestBody)
63 | if err != nil {
64 | http.Error(w, "Error parsing JSON", http.StatusBadRequest)
65 | return
66 | }
67 |
68 | roomNumber := requestBody.RoomNumber
69 | userName := requestBody.UserName
70 | password := requestBody.Password
71 |
72 | log.Printf("PreCheck Info: RoomNumber is %s, UserName is %s, Password is %s\n", roomNumber, userName, password)
73 |
74 | if roomNumber == "" || roomNumber == "null" || roomNumber == "undefined" {
75 | log.Println("roomNumber is invalid")
76 | http.Error(w, "roomNumber is invalid", http.StatusBadRequest)
77 | return
78 | }
79 |
80 | if room, ok := conversation.Manager.Rooms[roomNumber]; ok {
81 | // check password
82 | if room.Password != password { // password error
83 | log.Println("password is invalid")
84 | res, err := json.Marshal(ResponseData{
85 | Code: ErrorCodePassword,
86 | Info: "password is invalid",
87 | })
88 | if err != nil {
89 | log.Printf("json marshal error is: %+v ", err)
90 | return
91 | }
92 | _, err = w.Write(res)
93 | if err != nil {
94 | log.Printf("response write error is: %+v ", err)
95 | return
96 | }
97 | return
98 | }
99 |
100 | // check name repeat
101 | for client, _ := range conversation.Manager.Rooms[roomNumber].Clients {
102 | if client.UserName == userName {
103 | //randomStr := util.RandomString(10)
104 | //userName = userName + randomStr
105 | log.Println("username repeat")
106 | res, err := json.Marshal(ResponseData{
107 | Code: ErrorCodeUsernameRepeat,
108 | Info: "username repeat",
109 | })
110 | if err != nil {
111 | log.Printf("json marshal error is: %+v ", err)
112 | return
113 | }
114 | _, err = w.Write(res)
115 | if err != nil {
116 | log.Printf("response write error is: %+v ", err)
117 | return
118 | }
119 | return
120 | }
121 | }
122 | for halfUsername, _ := range OnceTokenMap[roomNumber] {
123 | if halfUsername == userName {
124 | log.Println("username repeat")
125 | res, err := json.Marshal(ResponseData{
126 | Code: ErrorCodeUsernameRepeat,
127 | Info: "username repeat",
128 | })
129 | if err != nil {
130 | log.Printf("json marshal error is: %+v ", err)
131 | return
132 | }
133 | _, err = w.Write(res)
134 | if err != nil {
135 | log.Printf("response write error is: %+v ", err)
136 | return
137 | }
138 | return
139 | }
140 | }
141 | }
142 |
143 | if _, ok := OnceTokenMap[roomNumber]; !ok {
144 | OnceTokenMap[roomNumber] = make(map[string]string)
145 | }
146 | onceToken := util.RandomString(6)
147 | OnceTokenMap[roomNumber][userName] = onceToken
148 | res, err := json.Marshal(ResponseData{
149 | Code: SuccessCode,
150 | Info: "success",
151 | Data: onceToken,
152 | })
153 | if err != nil {
154 | log.Printf("json marshal error is: %+v ", err)
155 | return
156 | }
157 | _, err = w.Write(res)
158 | if err != nil {
159 | log.Printf("response write error is: %+v ", err)
160 | return
161 | }
162 |
163 | }
164 |
165 | func HandleWs(w http.ResponseWriter, r *http.Request) {
166 |
167 | // params := mux.Vars(r)
168 | // roomNumber := params["room_number"]
169 | // userName := params["username"]
170 | // password := params["password"]
171 | // cmd := params["cmd"]
172 |
173 | query := r.URL.Query()
174 | roomNumber := query.Get("room_number")
175 | userName := query.Get("username")
176 | onceToken := query.Get("once_token")
177 | password := query.Get("password")
178 | cmd := query.Get("cmd")
179 |
180 | log.Printf("Connection Info: RoomNumber is %s, Cmd is %s, UserName is %s, OnceToken is %s\n", roomNumber, cmd, userName, onceToken)
181 |
182 | if roomNumber == "" || roomNumber == "null" || roomNumber == "undefined" {
183 | log.Println("roomNumber is invalid")
184 | http.Error(w, "roomNumber is invalid", http.StatusBadRequest)
185 | return
186 | }
187 |
188 | if _, ok := conversation.Manager.Rooms[roomNumber]; ok {
189 | if cacheOnceToken, onceOk := OnceTokenMap[roomNumber][userName]; !onceOk || cacheOnceToken != onceToken { // password error
190 | log.Println("once token is invalid")
191 | res, err := json.Marshal(ResponseData{
192 | Code: ErrorCodeOnceToken,
193 | Info: "once token is invalid",
194 | })
195 | if err != nil {
196 | log.Printf("json marshal error is: %+v ", err)
197 | return
198 | }
199 | _, err = w.Write(res)
200 | if err != nil {
201 | log.Printf("response write error is: %+v ", err)
202 | return
203 | }
204 | return
205 | }
206 | }
207 |
208 | ws, err := upgrader.Upgrade(w, r, nil)
209 | if err != nil {
210 | http.Error(w, "Unauthorized", http.StatusUnauthorized)
211 | return
212 | }
213 |
214 | delete(OnceTokenMap[roomNumber], userName)
215 |
216 | // timeout
217 | timeoutDuration := 1440 * time.Minute
218 | err = ws.SetReadDeadline(time.Now().Add(timeoutDuration))
219 | if err != nil {
220 | log.Printf("SetReadDeadline error: %+v\n", err)
221 | return
222 | }
223 |
224 | // heart
225 | go func() {
226 | for {
227 | time.Sleep(30 * time.Second)
228 | if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second)); err != nil {
229 | //if err := ws.WriteMessage(websocket.PingMessage, nil); err != nil {
230 | return
231 | }
232 | log.Println("websocket heartbeat!")
233 | }
234 | }()
235 |
236 | // register new client
237 | client := &conversation.Client{RoomNumber: roomNumber, UserName: userName, Password: password, Conn: ws, Send: make(chan conversation.Message)}
238 |
239 | go client.Write()
240 | go client.Read()
241 |
242 | conversation.Manager.Register <- client
243 |
244 | }
245 |
246 | func HandleFiles(w http.ResponseWriter, _ *http.Request, dirTemplate fs.FS) {
247 | data := struct {
248 | Url string
249 | }{
250 | Url: config.GlobalConfig.ServerUrl,
251 | }
252 |
253 | tmplName := os.Getenv("TEMPLATE_NAME")
254 |
255 | if tmplName == "" {
256 | tmplName = "bulma"
257 | }
258 |
259 | tmpl, err := template.ParseFS(dirTemplate, fmt.Sprintf("templates/%s.html", tmplName))
260 | if err != nil {
261 | fmt.Printf("failed to parse the template: %s", err)
262 | w.WriteHeader(http.StatusInternalServerError)
263 | return
264 | }
265 |
266 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
267 |
268 | err = tmpl.ExecuteTemplate(w, fmt.Sprintf("%s.html", tmplName), data)
269 | if err != nil {
270 | fmt.Printf("failed to execute the template: %s", err)
271 | w.WriteHeader(http.StatusInternalServerError)
272 | return
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/templates/ddiu.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | MiniChat
12 |
13 |
14 |
15 |
16 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
91 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | ( SYSTEM )
142 |
143 |
144 | {[ msg.username === username ? '( ME ) ':'' ]} {[ msg.username ]}
145 |
146 |
147 |
148 |
149 | {[ formatDate(new Date()) ]}
150 |
151 |
152 |
153 | {[ msg.payload ]}
154 |
155 |
156 | {[ msg.payload ]}
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
172 |
185 |
186 |
187 |
377 |
378 |
421 |
422 |
423 |
424 |
445 |
446 |
447 |
448 |
449 |
--------------------------------------------------------------------------------
/static/index.6369ed8c.css:
--------------------------------------------------------------------------------
1 | *,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }.i-carbon-arrow-right{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.1em' height='1.1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m18 6l-1.43 1.393L24.15 15H4v2h20.15l-7.58 7.573L18 26l10-10L18 6z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.1em;height:1.1em}.i-ph-arrow-line-down-bold,[i-ph-arrow-line-down-bold=""]{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.1em' height='1.1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M47.51 112.49a12 12 0 0 1 17-17L116 147V32a12 12 0 0 1 24 0v115l51.51-51.52a12 12 0 0 1 17 17l-72 72a12 12 0 0 1-17 0ZM216 204H40a12 12 0 0 0 0 24h176a12 12 0 0 0 0-24Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.1em;height:1.1em}:is([prose=""],.prose) :where(h1,h2,h3,h4,h5,h6):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-headings);font-weight:600;line-height:1.25}:is([prose=""],.prose) :where(a):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500}:is([prose=""],.prose) :where(a code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-links)}:is([prose=""],.prose) :where(p,ul,ol,pre):not(:where(.not-prose,.not-prose *)){margin:1em 0;line-height:1.75}:is([prose=""],.prose) :where(blockquote):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding-left:1em;font-style:italic;border-left:.25em solid var(--un-prose-borders)}:is([prose=""],.prose) :where(h1):not(:where(.not-prose,.not-prose *)){margin:1rem 0;font-size:2.25em}:is([prose=""],.prose) :where(h2):not(:where(.not-prose,.not-prose *)){margin:1.75em 0 .5em;font-size:1.75em}:is([prose=""],.prose) :where(h3):not(:where(.not-prose,.not-prose *)){margin:1.5em 0 .5em;font-size:1.375em}:is([prose=""],.prose) :where(h4):not(:where(.not-prose,.not-prose *)){margin:1em 0;font-size:1.125em}:is([prose=""],.prose) :where(img,video):not(:where(.not-prose,.not-prose *)){max-width:100%}:is([prose=""],.prose) :where(figure,picture):not(:where(.not-prose,.not-prose *)){margin:1em 0}:is([prose=""],.prose) :where(figcaption):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-captions);font-size:.875em}:is([prose=""],.prose) :where(code):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);font-size:.875em;font-weight:600;font-family:var(--un-prose-font-mono)}:is([prose=""],.prose) :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):before,:is([prose=""],.prose) :where(:not(pre)>code):not(:where(.not-prose,.not-prose *)):after{content:"`"}:is([prose=""],.prose) :where(pre):not(:where(.not-prose,.not-prose *)){padding:1.25rem 1.5rem;overflow-x:auto;border-radius:.375rem}:is([prose=""],.prose) :where(pre,code):not(:where(.not-prose,.not-prose *)){white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;hyphens:none;background:transparent}:is([prose=""],.prose) :where(pre code):not(:where(.not-prose,.not-prose *)){font-weight:inherit}:is([prose=""],.prose) :where(ol,ul):not(:where(.not-prose,.not-prose *)){padding-left:1.25em}:is([prose=""],.prose) :where(ol):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}:is([prose=""],.prose) :where(ol[type=A]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}:is([prose=""],.prose) :where(ol[type=a]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}:is([prose=""],.prose) :where(ol[type=A s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-alpha}:is([prose=""],.prose) :where(ol[type=a s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-alpha}:is([prose=""],.prose) :where(ol[type=I]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}:is([prose=""],.prose) :where(ol[type=i]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}:is([prose=""],.prose) :where(ol[type=I s]):not(:where(.not-prose,.not-prose *)){list-style-type:upper-roman}:is([prose=""],.prose) :where(ol[type=i s]):not(:where(.not-prose,.not-prose *)){list-style-type:lower-roman}:is([prose=""],.prose) :where(ol[type="1"]):not(:where(.not-prose,.not-prose *)){list-style-type:decimal}:is([prose=""],.prose) :where(ul):not(:where(.not-prose,.not-prose *)){list-style-type:disc}:is([prose=""],.prose) :where(ol>li):not(:where(.not-prose,.not-prose *))::marker,:is([prose=""],.prose) :where(ul>li):not(:where(.not-prose,.not-prose *))::marker,:is([prose=""],.prose) :where(summary):not(:where(.not-prose,.not-prose *))::marker{color:var(--un-prose-lists)}:is([prose=""],.prose) :where(hr):not(:where(.not-prose,.not-prose *)){margin:2em 0;border:1px solid var(--un-prose-hr)}:is([prose=""],.prose) :where(table):not(:where(.not-prose,.not-prose *)){display:block;margin:1em 0;border-collapse:collapse;overflow-x:auto}:is([prose=""],.prose) :where(tr):not(:where(.not-prose,.not-prose *)):nth-child(2n){background:var(--un-prose-bg-soft)}:is([prose=""],.prose) :where(td,th):not(:where(.not-prose,.not-prose *)){border:1px solid var(--un-prose-borders);padding:.625em 1em}:is([prose=""],.prose) :where(abbr):not(:where(.not-prose,.not-prose *)){cursor:help}:is([prose=""],.prose) :where(kbd):not(:where(.not-prose,.not-prose *)){color:var(--un-prose-code);border:1px solid;padding:.25rem .5rem;font-size:.875em;border-radius:.25rem}:is([prose=""],.prose) :where(details):not(:where(.not-prose,.not-prose *)){margin:1em 0;padding:1.25rem 1.5rem;background:var(--un-prose-bg-soft)}:is([prose=""],.prose) :where(summary):not(:where(.not-prose,.not-prose *)){cursor:pointer;font-weight:600}:is([prose=""],.prose) :where(ul,ol):not(:where(.not-prose,.not-prose *)){padding-left:2.25em;position:relative}.prose,[prose=""]{color:var(--un-prose-body);max-width:65ch}.gpt-copy-btn{position:absolute;top:12px;right:12px;z-index:3;width:2rem;height:2rem;display:flex;cursor:pointer;align-items:center;justify-content:center;border-width:1px;border-color:transparent;--un-bg-opacity:1;background-color:rgba(248,249,250,var(--un-bg-opacity));padding:.5rem;opacity:.9}.gpt-copy-tips{position:absolute;top:-2rem;z-index:1;box-sizing:border-box;height:1.75rem;display:flex;align-items:center;justify-content:center;white-space:nowrap;border-radius:.25rem;--un-bg-opacity:1;background-color:rgba(0,0,0,var(--un-bg-opacity));padding:.25rem .625rem;font-size:.75rem;line-height:1rem;--un-text-opacity:1;color:rgba(255,255,255,var(--un-text-opacity));opacity:0;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-duration:.6s}.gen-cb-wrapper,[gen-cb-wrapper=""]{margin-top:1rem;margin-bottom:1rem;height:3rem;display:flex;align-items:center;justify-content:center;grid-gap:1rem;gap:1rem;border-radius:.125rem;--un-bg-opacity:1;background-color:rgba(148,163,184,var(--un-bg-opacity));--un-bg-opacity:.15}.gen-text-wrapper{margin-top:1rem;margin-bottom:1rem;display:flex;justify-content:center;grid-gap:.5rem;gap:.5rem;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.gpt-title{margin-right:.25rem;font-size:1.5rem;line-height:2rem;font-weight:800}[gen-slate-btn=""]{height:3rem;border-radius:.125rem;--un-bg-opacity:1;background-color:rgba(148,163,184,var(--un-bg-opacity));--un-bg-opacity:.15;padding:.5rem 1rem}.gen-textarea,[gen-textarea=""]{width:100%;min-height:3rem;max-height:9rem;resize:none;scroll-padding:8px;border-radius:.125rem;--un-bg-opacity:1;background-color:rgba(148,163,184,var(--un-bg-opacity));--un-bg-opacity:.15;padding:.75rem}.gpt-password-input{height:3rem;border-radius:.125rem;--un-bg-opacity:1;background-color:rgba(148,163,184,var(--un-bg-opacity));--un-bg-opacity:.15;padding:.75rem 1rem}.gpt-password-submit{height:3rem;width:3rem;display:flex;cursor:pointer;align-items:center;justify-content:center;--un-bg-opacity:1;background-color:rgba(148,163,184,var(--un-bg-opacity));--un-bg-opacity:.2}.col-fcc{display:flex;flex-direction:column;align-items:center;justify-content:center}.fb,[fb=""]{display:flex;justify-content:space-between}.fi,[fi=""]{display:flex;align-items:center}.fie,[fie=""]{display:flex;align-items:center;justify-content:flex-end}.gpt-retry-btn,[gpt-retry-btn=""]{display:flex;cursor:pointer;align-items:center;grid-gap:.25rem;gap:.25rem;border-width:1px;--un-border-opacity:1;border-color:rgba(148,163,184,var(--un-border-opacity));border-radius:.375rem;padding:.125rem .5rem;font-size:.875rem;line-height:1.25rem;opacity:.7}.sys-edit-btn,[sys-edit-btn=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;grid-gap:.25rem;gap:.25rem;border-radius:.375rem;background-color:#94a3b833;padding:.25rem .5rem;font-size:.875rem;line-height:1.25rem;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.gen-cb-stop,[gen-cb-stop=""]{cursor:pointer;border-width:1px;--un-border-opacity:1;border-color:rgba(148,163,184,var(--un-border-opacity));border-radius:.375rem;padding:.125rem .5rem;font-size:.875rem;line-height:1.25rem;opacity:.7}.b-slate-link,[b-slate-link=""]{border-bottom-width:1px;--un-border-opacity:1;border-color:rgba(148,163,184,var(--un-border-opacity));border-style:none}.b-slate-link:hover,[b-slate-link=""]:hover{border-style:dashed}.gen-cb-stop:hover,.gpt-retry-btn:hover,[gen-cb-stop=""]:hover,[gpt-retry-btn=""]:hover{background-color:#94a3b81a}.dark .gpt-copy-btn{--un-bg-opacity:1;background-color:rgba(45,45,45,var(--un-bg-opacity))}.stick-btn-on{background-color:var(--c-fg)!important;color:var(--c-bg)}.sys-edit-btn:hover,[sys-edit-btn=""]:hover{background-color:#94a3b880}[gen-slate-btn=""]:hover{--un-bg-opacity:.2}.gen-textarea:focus,.gpt-password-input:focus,[gen-textarea=""]:focus{--un-bg-opacity:.2;outline:2px solid transparent;outline-offset:2px;--un-ring-width:0;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width) + var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.gpt-password-submit:hover{--un-bg-opacity:.5}.gpt-subtitle{--un-gradient-from-position:0%;--un-gradient-from:rgba(56,189,248,var(--un-from-opacity, 1)) var(--un-gradient-from-position);--un-gradient-to:rgba(56,189,248,0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from), var(--un-gradient-to);--un-gradient-to-position:100%;--un-gradient-to:rgba(5,150,105,var(--un-to-opacity, 1)) var(--un-gradient-to-position);--un-gradient-shape:to right;--un-gradient:var(--un-gradient-shape), var(--un-gradient-stops);background-image:linear-gradient(var(--un-gradient));-webkit-background-clip:text;background-clip:text;font-size:1.5rem;line-height:2rem;font-weight:800;color:transparent}.dark .gen-textarea::placeholder,.dark [gen-textarea=""]::placeholder{opacity:.3}.gen-textarea::placeholder,[gen-textarea=""]::placeholder{opacity:.5}.stick-btn-on:hover{opacity:.8}.fixed{position:fixed}[relative=""]{position:relative}.bottom-5{bottom:1.25rem}.left-5{left:1.25rem}.-mx-4,[-mx-4=""]{margin-left:-1rem;margin-right:-1rem}.my-2,[my-2=""]{margin-top:.5rem;margin-bottom:.5rem}.my-4,[my-4=""]{margin-top:1rem;margin-bottom:1rem}[my-6=""]{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1,[mb-1=""]{margin-bottom:.25rem}.mb-2,[mb-2=""]{margin-bottom:.5rem}.ml-2,[ml-2=""]{margin-left:.5rem}.mt-1,[mt-1=""]{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-8,[mt-8=""]{margin-top:2rem}.h-10{height:2.5rem}.h-7{height:1.75rem}.h-fit{height:fit-content}.h-screen{height:100vh}.w-10{width:2.5rem}.w-7{width:1.75rem}.w-fit{width:fit-content}.w-full,[w-full=""]{width:100%}.flex{display:flex}.shrink-0{flex-shrink:0}.active\:scale-90:active{--un-scale-x:.9;--un-scale-y:.9;transform:translate(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotate(var(--un-rotate-z)) skew(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.transform{transform:translate(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotate(var(--un-rotate-z)) skew(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.items-center,[items-center=""]{align-items:center}.justify-center{justify-content:center}.gap-1,[gap-1=""]{grid-gap:.25rem;gap:.25rem}.gap-3{grid-gap:.75rem;gap:.75rem}.overflow-hidden,[overflow-hidden=""]{overflow:hidden}.break-words,[break-words=""]{overflow-wrap:break-word}.border,[border=""]{border-width:1px}.border-red\/50{border-color:#a1e3c480}[border-red=""]{--un-border-opacity:1;border-color:rgba(248,113,113,var(--un-border-opacity))}.rd-50\%,[rd-50\%=""]{border-radius:50%}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.bg-red\/10{background-color:#b6ecbb1a}.hover\:bg-slate\/10:hover{background-color:#94a3b81a}[bg-red=""]{--un-bg-opacity:1;background-color:rgba(248,113,113,var(--un-bg-opacity))}.from-gray-300{--un-gradient-from-position:0%;--un-gradient-from:rgba(209,213,219,var(--un-from-opacity, 1)) var(--un-gradient-from-position);--un-gradient-to:rgba(209,213,219,0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from), var(--un-gradient-to)}.from-purple-400{--un-gradient-from-position:0%;--un-gradient-from:rgba(192,132,252,var(--un-from-opacity, 1)) var(--un-gradient-from-position);--un-gradient-to:rgba(192,132,252,0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from), var(--un-gradient-to)}.from-yellow-200{--un-gradient-from-position:0%;--un-gradient-from:rgba(254,240,138,var(--un-from-opacity, 1)) var(--un-gradient-from-position);--un-gradient-to:rgba(254,240,138,0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from), var(--un-gradient-to)}.via-gray-200{--un-gradient-via-position:50%;--un-gradient-to:rgba(229,231,235,0);--un-gradient-stops:var(--un-gradient-from), rgba(229,231,235,var(--un-via-opacity, 1)) var(--un-gradient-via-position), var(--un-gradient-to)}.via-green-200{--un-gradient-via-position:50%;--un-gradient-to:rgba(187,247,208,0);--un-gradient-stops:var(--un-gradient-from), rgba(187,247,208,var(--un-via-opacity, 1)) var(--un-gradient-via-position), var(--un-gradient-to)}.to-gray-300{--un-gradient-to-position:100%;--un-gradient-to:rgba(209,213,219,var(--un-to-opacity, 1)) var(--un-gradient-to-position)}.to-green-300{--un-gradient-to-position:100%;--un-gradient-to:rgba(134,239,172,var(--un-to-opacity, 1)) var(--un-gradient-to-position)}.to-yellow-400{--un-gradient-to-position:100%;--un-gradient-to:rgba(250,204,21,var(--un-to-opacity, 1)) var(--un-gradient-to-position)}.bg-gradient-to-r{--un-gradient-shape:to right;--un-gradient:var(--un-gradient-shape), var(--un-gradient-stops);background-image:linear-gradient(var(--un-gradient))}[stroke-width~="2"]{stroke-width:2px}.p-1,[p-1=""]{padding:.25rem}.p-2\.5,[p-2\.5=""]{padding:.625rem}.px,.px-4,[px-4=""]{padding-left:1rem;padding-right:1rem}.px-3,[px-3=""]{padding-left:.75rem;padding-right:.75rem}.py-2,[py-2=""]{padding-top:.5rem;padding-bottom:.5rem}.py-3,[py-3=""]{padding-top:.75rem;padding-bottom:.75rem}[px-1=""]{padding-left:.25rem;padding-right:.25rem}[pr-1=""]{padding-right:.25rem}.text-base,[text-base=""]{font-size:1rem;line-height:1.5rem}.text-sm,[text-sm=""]{font-size:.875rem;line-height:1.25rem}.text-xs,[text-xs=""]{font-size:.75rem;line-height:1rem}.leading-normal,[leading-normal=""]{line-height:1.5}[color~="#858585"]{--un-text-opacity:1;color:rgba(133,133,133,var(--un-text-opacity))}[color~="#FFFFFF"]{--un-text-opacity:1;color:rgba(255,255,255,var(--un-text-opacity))}.text-red,[text-red=""]{--un-text-opacity:1;color:rgba(94,134,98,var(--un-text-opacity))}.dark .dark\:op-60,.dark [dark\:op-60=""],.op-60\>Based,[op-60=""]{opacity:.6}.group:hover .group-hover\:op-100{opacity:1}.op-25,[op-25=""]{opacity:.25}.op-30,.op-30\>,[op-30=""]{opacity:.3}.op-50,[op-50=""]{opacity:.5}.op-70,[op-70=""]{opacity:.7}.op-75{opacity:.75}.op-80{opacity:.8}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors,[transition-colors=""]{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@media (min-width: 768px){.md\:hover\:bg-slate\/3:hover{background-color:#94a3b808}[md\:hover\:bg-slate=""]:hover{--un-bg-opacity:1;background-color:rgba(148,163,184,var(--un-bg-opacity))}}:root{--c-bg: #fbfbfb;--c-fg: #444444;--c-scroll: #d9d9d9;--c-scroll-hover: #bbbbbb;scrollbar-color:var(--c-scrollbar) var(--c-bg)}html{font-family:system-ui,sans-serif;background-color:var(--c-bg);color:var(--c-fg)}html.dark{--c-bg: #212129;--c-fg: #ddddf0;--c-scroll: #333333;--c-scroll-hover: #555555}main{max-width:70ch;margin:0 auto;padding:4rem 2rem 4rem}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-thumb{background-color:var(--c-scroll);border-radius:4px}::-webkit-scrollbar-thumb:hover{background-color:var(--c-scroll-hover)}::-webkit-scrollbar-track{background-color:var(--c-bg)}
2 |
--------------------------------------------------------------------------------
/templates/bulma.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | MiniChat
12 |
13 |
14 |
15 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
219 |
220 |
221 |
222 |
223 |
224 |
229 |
230 |
231 | ( SYSTEM )
232 |
233 |
234 | {[ msg.username === username ? '( ME ) ':'' ]} {[ msg.username ]}
235 |
236 |
237 |
238 | {[ formatDate(new Date()) ]}
239 |
240 |
241 |
250 |
251 |
252 |
253 |
254 |
255 |
285 |
286 |
287 |
314 |
315 |
497 |
498 |
542 |
543 |
564 |
565 |
566 |
567 |
--------------------------------------------------------------------------------
/static/index.a243aca3.css:
--------------------------------------------------------------------------------
1 | #themeToggle:where(.astro-KXYEDVG6){border:0;cursor:pointer}.theme_toggle_circle1:where(.astro-KXYEDVG6){transition:cx .5s,cy .5s;cx:100%;cy:0%}.theme_toggle_circle2:where(.astro-KXYEDVG6){transition:r .3s}.theme_toggle_svg:where(.astro-KXYEDVG6){transition:transform .5s cubic-bezier(.68,-.55,.27,1.55);transform:rotate(90deg)}.theme_toggle_g:where(.astro-KXYEDVG6){transition:opacity .5s;opacity:1}html.dark #themeToggle:where(.astro-KXYEDVG6) .theme_toggle_circle1:where(.astro-KXYEDVG6){cx:50%;cy:23%}html.dark #themeToggle:where(.astro-KXYEDVG6) .theme_toggle_svg:where(.astro-KXYEDVG6){transform:rotate(40deg)}html.dark #themeToggle:where(.astro-KXYEDVG6) .theme_toggle_g:where(.astro-KXYEDVG6){opacity:0}[data-scope=slider][data-part=root]{width:100%;display:flex;flex-direction:column}[data-scope=slider][data-part=root][data-orientation=vertical]{height:15rem}[data-scope=slider][data-part=control]{--slider-thumb-size: 14px;--slider-track-height: 4px;position:relative;display:flex;align-items:center;justify-content:center;cursor:pointer}[data-scope=slider][data-part=control][data-orientation=horizontal]{height:var(--slider-thumb-size)}[data-scope=slider][data-part=control][data-orientation=vertical]{width:var(--slider-thumb-size)}[data-scope=slider][data-part=control]:hover [data-part=range]{--un-bg-opacity:1;background-color:rgba(156,163,175,var(--un-bg-opacity))}.dark [data-scope=slider][data-part=control]:hover [data-part=range]{--un-bg-opacity:1;background-color:rgba(75,85,99,var(--un-bg-opacity))}[data-scope=slider][data-part=control]:hover [data-part=thumb]{--un-bg-opacity:1;background-color:rgba(209,213,219,var(--un-bg-opacity))}.dark [data-scope=slider][data-part=control]:hover [data-part=thumb]{--un-bg-opacity:1;background-color:rgba(156,163,175,var(--un-bg-opacity))}[data-scope=slider][data-part=thumb]{all:unset;width:var(--slider-thumb-size);height:var(--slider-thumb-size);border-width:2px;--un-border-opacity:1;border-color:rgba(197,197,210,var(--un-border-opacity));border-radius:9999px;--un-bg-opacity:1;background-color:rgba(229,231,235,var(--un-bg-opacity))}.dark [data-scope=slider][data-part=thumb]{--un-bg-opacity:1;background-color:rgba(107,114,128,var(--un-bg-opacity))}[data-scope=slider][data-part=thumb][data-disabled]{width:0}[data-scope=slider] .control-area{margin-top:12px;display:flex}.slider [data-orientation=horizontal] .control-area{flex-direction:column;width:100%}.slider [data-orientation=vertical] .control-area{flex-direction:row;height:100%}[data-scope=slider][data-part=track]{border-radius:9999px;--un-bg-opacity:1;background-color:rgba(229,231,235,var(--un-bg-opacity))}.dark [data-scope=slider][data-part=track]{--un-bg-opacity:1;background-color:rgba(64,64,64,var(--un-bg-opacity))}[data-scope=slider][data-part=track][data-orientation=horizontal]{height:var(--slider-track-height);width:100%}[data-scope=slider][data-part=track][data-orientation=vertical]{height:100%;width:var(--slider-track-height)}[data-scope=slider][data-part=range]{--un-bg-opacity:1;background-color:rgba(212,212,212,var(--un-bg-opacity))}.dark [data-scope=slider][data-part=range]{--un-bg-opacity:1;background-color:rgba(55,65,81,var(--un-bg-opacity))}[data-scope=slider][data-part=range][data-disabled]{--un-bg-opacity:1;background-color:rgba(212,212,212,var(--un-bg-opacity))}.dark [data-scope=slider][data-part=range][data-disabled]{--un-bg-opacity:1;background-color:rgba(75,85,99,var(--un-bg-opacity))}[data-scope=slider][data-part=range][data-orientation=horizontal]{height:100%}[data-scope=slider][data-part=range][data-orientation=vertical]{width:100%}[data-scope=slider][data-part=output]{margin-inline-start:12px}[data-scope=slider][data-part=marker]{color:#d3d3d3}.message pre{background-color:#64748b10;font-size:.8rem;padding:.4rem 1rem}.message .hljs{background-color:transparent}.message table{font-size:.8em}.message table thead tr{background-color:#64748b40;text-align:left}.message table th,.message table td{padding:.6rem 1rem}.message table tbody tr:last-of-type{border-bottom:2px solid #64748b40}@font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_AMS-Regular.0cdd387c.woff2) format("woff2"),url(/_astro/KaTeX_AMS-Regular.30da91e8.woff) format("woff"),url(/_astro/KaTeX_AMS-Regular.68534840.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(/_astro/KaTeX_Caligraphic-Bold.de7701e4.woff2) format("woff2"),url(/_astro/KaTeX_Caligraphic-Bold.1ae6bd74.woff) format("woff"),url(/_astro/KaTeX_Caligraphic-Bold.07d8e303.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Caligraphic-Regular.5d53e70a.woff2) format("woff2"),url(/_astro/KaTeX_Caligraphic-Regular.3398dd02.woff) format("woff"),url(/_astro/KaTeX_Caligraphic-Regular.ed0b7437.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(/_astro/KaTeX_Fraktur-Bold.74444efd.woff2) format("woff2"),url(/_astro/KaTeX_Fraktur-Bold.9be7ceb8.woff) format("woff"),url(/_astro/KaTeX_Fraktur-Bold.9163df9c.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Fraktur-Regular.51814d27.woff2) format("woff2"),url(/_astro/KaTeX_Fraktur-Regular.5e28753b.woff) format("woff"),url(/_astro/KaTeX_Fraktur-Regular.1e6f9579.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(/_astro/KaTeX_Main-Bold.0f60d1b8.woff2) format("woff2"),url(/_astro/KaTeX_Main-Bold.c76c5d69.woff) format("woff"),url(/_astro/KaTeX_Main-Bold.138ac28d.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(/_astro/KaTeX_Main-BoldItalic.99cd42a3.woff2) format("woff2"),url(/_astro/KaTeX_Main-BoldItalic.a6f7ec0d.woff) format("woff"),url(/_astro/KaTeX_Main-BoldItalic.70ee1f64.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(/_astro/KaTeX_Main-Italic.97479ca6.woff2) format("woff2"),url(/_astro/KaTeX_Main-Italic.f1d6ef86.woff) format("woff"),url(/_astro/KaTeX_Main-Italic.0d85ae7c.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Main-Regular.c2342cd8.woff2) format("woff2"),url(/_astro/KaTeX_Main-Regular.c6368d87.woff) format("woff"),url(/_astro/KaTeX_Main-Regular.d0332f52.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(/_astro/KaTeX_Math-BoldItalic.dc47344d.woff2) format("woff2"),url(/_astro/KaTeX_Math-BoldItalic.850c0af5.woff) format("woff"),url(/_astro/KaTeX_Math-BoldItalic.f9377ab0.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(/_astro/KaTeX_Math-Italic.7af58c5e.woff2) format("woff2"),url(/_astro/KaTeX_Math-Italic.8a8d2445.woff) format("woff"),url(/_astro/KaTeX_Math-Italic.08ce98e5.ttf) format("truetype")}@font-face{font-family:KaTeX_SansSerif;font-style:normal;font-weight:700;src:url(/_astro/KaTeX_SansSerif-Bold.e99ae511.woff2) format("woff2"),url(/_astro/KaTeX_SansSerif-Bold.ece03cfd.woff) format("woff"),url(/_astro/KaTeX_SansSerif-Bold.1ece03f7.ttf) format("truetype")}@font-face{font-family:KaTeX_SansSerif;font-style:italic;font-weight:400;src:url(/_astro/KaTeX_SansSerif-Italic.00b26ac8.woff2) format("woff2"),url(/_astro/KaTeX_SansSerif-Italic.91ee6750.woff) format("woff"),url(/_astro/KaTeX_SansSerif-Italic.3931dd81.ttf) format("truetype")}@font-face{font-family:KaTeX_SansSerif;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_SansSerif-Regular.68e8c73e.woff2) format("woff2"),url(/_astro/KaTeX_SansSerif-Regular.11e4dc8a.woff) format("woff"),url(/_astro/KaTeX_SansSerif-Regular.f36ea897.ttf) format("truetype")}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Script-Regular.036d4e95.woff2) format("woff2"),url(/_astro/KaTeX_Script-Regular.d96cdf2b.woff) format("woff"),url(/_astro/KaTeX_Script-Regular.1c67f068.ttf) format("truetype")}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Size1-Regular.6b47c401.woff2) format("woff2"),url(/_astro/KaTeX_Size1-Regular.c943cc98.woff) format("woff"),url(/_astro/KaTeX_Size1-Regular.95b6d2f1.ttf) format("truetype")}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Size2-Regular.d04c5421.woff2) format("woff2"),url(/_astro/KaTeX_Size2-Regular.2014c523.woff) format("woff"),url(/_astro/KaTeX_Size2-Regular.a6b2099f.ttf) format("truetype")}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(data:font/woff2;base64,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) format("woff2"),url(/_astro/KaTeX_Size3-Regular.6ab6b62e.woff) format("woff"),url(/_astro/KaTeX_Size3-Regular.500e04d5.ttf) format("truetype")}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Size4-Regular.a4af7d41.woff2) format("woff2"),url(/_astro/KaTeX_Size4-Regular.99f9c675.woff) format("woff"),url(/_astro/KaTeX_Size4-Regular.c647367d.ttf) format("truetype")}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(/_astro/KaTeX_Typewriter-Regular.71d517d6.woff2) format("woff2"),url(/_astro/KaTeX_Typewriter-Regular.e14fed02.woff) format("woff"),url(/_astro/KaTeX_Typewriter-Regular.f01f3e87.ttf) format("truetype")}.katex{text-rendering:auto;font: 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.7"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
2 |
--------------------------------------------------------------------------------