├── 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 | ![](screenshots/EnterRoom.jpeg) 109 | ![](screenshots/ChatView.jpeg) 110 | ![](screenshots/DarkMode.jpeg) 111 | 112 | --- 113 | 114 | [![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](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 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
121 |
122 |
123 | Mini 124 | Chat 125 |
126 | 127 |
128 |
129 |
130 |
131 | 132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | 143 | 146 | 147 |
148 |
149 | {[ formatDate(new Date()) ]} 150 |
151 |
152 | 155 | 158 | 161 |
162 | 163 |
164 |
165 |
166 |
167 | 168 |
169 |
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 | 233 | 236 |
237 |
238 | {[ formatDate(new Date()) ]} 239 |
240 | 241 |
246 |
247 |

248 |
249 |
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 | --------------------------------------------------------------------------------