From f8ce4e3b4841622f73d03801708b7b67f5666944 Mon Sep 17 00:00:00 2001 From: atridadl Date: Wed, 17 Jan 2024 12:02:03 -0700 Subject: [PATCH] Finished --- .air.toml | 46 +++ .env.example | 2 + .gitattributes | 2 + .github/workflows/fly.yml | 15 + .gitignore | 27 +- Dockerfile | 17 ++ api/ping.go | 11 + api/sse.go | 58 ++++ api/ssedemosend.go | 37 +++ content/code-blocks.md | 13 + content/welcome.md | 7 + fly.toml | 17 ++ go.mod | 42 +++ go.sum | 90 ++++++ lib/email.go | 31 +++ lib/markdown.go | 59 ++++ lib/markdown_test.go | 57 ++++ lib/redis.go | 48 ++++ lib/redis_test.go | 27 ++ lib/sse.go | 20 ++ lib/types.go | 37 +++ main.go | 63 +++++ pages/blog.go | 75 +++++ pages/home.go | 78 ++++++ pages/post.go | 80 ++++++ pages/ssedemo.go | 25 ++ pages/templates/blog.html | 18 ++ pages/templates/home.html | 51 ++++ pages/templates/layouts/base.html | 22 ++ pages/templates/layouts/post.html | 43 +++ pages/templates/partials/cardlinks.html | 45 +++ pages/templates/partials/header.html | 20 ++ pages/templates/partials/navitems.html | 17 ++ pages/templates/post.html | 14 + pages/templates/ssedemo.html | 29 ++ public/css/styles.css | 1 + public/favicon.ico | Bin 0 -> 15406 bytes public/js/htmx.min.js | 1 + public/js/htmx.sse.js | 355 ++++++++++++++++++++++++ stylegen/base.css | 12 + stylegen/bun.lockb | Bin 0 -> 43927 bytes stylegen/package.json | 11 + stylegen/tailwind.config.js | 12 + 43 files changed, 1614 insertions(+), 21 deletions(-) create mode 100644 .air.toml create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .github/workflows/fly.yml create mode 100644 Dockerfile create mode 100644 api/ping.go create mode 100644 api/sse.go create mode 100644 api/ssedemosend.go create mode 100644 content/code-blocks.md create mode 100644 content/welcome.md create mode 100644 fly.toml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lib/email.go create mode 100644 lib/markdown.go create mode 100644 lib/markdown_test.go create mode 100644 lib/redis.go create mode 100644 lib/redis_test.go create mode 100644 lib/sse.go create mode 100644 lib/types.go create mode 100644 main.go create mode 100644 pages/blog.go create mode 100644 pages/home.go create mode 100644 pages/post.go create mode 100644 pages/ssedemo.go create mode 100644 pages/templates/blog.html create mode 100644 pages/templates/home.html create mode 100644 pages/templates/layouts/base.html create mode 100644 pages/templates/layouts/post.html create mode 100644 pages/templates/partials/cardlinks.html create mode 100644 pages/templates/partials/header.html create mode 100644 pages/templates/partials/navitems.html create mode 100644 pages/templates/post.html create mode 100644 pages/templates/ssedemo.html create mode 100644 public/css/styles.css create mode 100644 public/favicon.ico create mode 100644 public/js/htmx.min.js create mode 100644 public/js/htmx.sse.js create mode 100644 stylegen/base.css create mode 100755 stylegen/bun.lockb create mode 100644 stylegen/package.json create mode 100644 stylegen/tailwind.config.js diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..831d96f --- /dev/null +++ b/.air.toml @@ -0,0 +1,46 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "stylegen"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1928857 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +REDIS_HOST="" +REDIS_PASSWORD="" \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..755df8a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.css linguist-vendored +*.js linguist-vendored \ No newline at end of file diff --git a/.github/workflows/fly.yml b/.github/workflows/fly.yml new file mode 100644 index 0000000..c2caa68 --- /dev/null +++ b/.github/workflows/fly.yml @@ -0,0 +1,15 @@ +name: Fly Deploy +on: + push: + branches: + - main +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: superfly/flyctl-actions/setup-flyctl@master + - run: flyctl deploy --remote-only + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.gitignore b/.gitignore index 3b735ec..4bb2baf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,6 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -# Go workspace file -go.work +node_modules/ +atri.dad +.env +airbin +tmp/ +*.rdb \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3d47d83 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.21.6 as build + +WORKDIR /app + +COPY . . + +RUN go mod download +RUN CGO_ENABLED=0 go build -o /go/bin/app + +FROM gcr.io/distroless/base-debian12 + +COPY --from=build /go/bin/app / +COPY --from=build /app/content /content +COPY --from=build /app/pages /pages +COPY --from=build /app/public /public + +CMD [ "/app" ] diff --git a/api/ping.go b/api/ping.go new file mode 100644 index 0000000..1572391 --- /dev/null +++ b/api/ping.go @@ -0,0 +1,11 @@ +package api + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func Ping(c echo.Context) error { + return c.String(http.StatusOK, "Pong!") +} diff --git a/api/sse.go b/api/sse.go new file mode 100644 index 0000000..efc046b --- /dev/null +++ b/api/sse.go @@ -0,0 +1,58 @@ +package api + +import ( + "fmt" + "log" + "time" + + "github.com/labstack/echo/v4" + "goth.stack/lib" +) + +func SSEDemo(c echo.Context) error { + channel := c.QueryParam("channel") + if channel == "" { + channel = "default" + } + + // Use the request context, which is cancelled when the client disconnects + ctx := c.Request().Context() + + pubsub, _ := lib.Subscribe(lib.RedisClient, channel) + + c.Response().Header().Set(echo.HeaderContentType, "text/event-stream") + c.Response().Header().Set(echo.HeaderConnection, "keep-alive") + c.Response().Header().Set(echo.HeaderCacheControl, "no-cache") + + // Create a ticker that fires every 15 seconds + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + // If the client has disconnected, stop the loop + return nil + case <-ticker.C: + // Every 30 seconds, send a comment to keep the connection alive + if _, err := c.Response().Write([]byte(": keep-alive\n\n")); err != nil { + return err + } + c.Response().Flush() + default: + // Handle incoming messages as before + msg, err := pubsub.ReceiveMessage(ctx) + if err != nil { + log.Printf("Failed to receive message: %v", err) + continue + } + + data := fmt.Sprintf("data: %s\n\n", msg.Payload) + if _, err := c.Response().Write([]byte(data)); err != nil { + return err + } + + c.Response().Flush() + } + } +} diff --git a/api/ssedemosend.go b/api/ssedemosend.go new file mode 100644 index 0000000..2416b74 --- /dev/null +++ b/api/ssedemosend.go @@ -0,0 +1,37 @@ +package api + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "goth.stack/lib" +) + +func SSEDemoSend(c echo.Context) error { + channel := c.QueryParam("channel") + if channel == "" { + channel = "default" + } + + // Get message from query parameters, form value, or request body + message := c.QueryParam("message") + if message == "" { + message = c.FormValue("message") + if message == "" { + var body map[string]string + if err := c.Bind(&body); err != nil { + return err + } + message = body["message"] + } + } + + if message == "" { + return c.JSON(http.StatusBadRequest, map[string]string{"error": "message parameter is required"}) + } + + // Send message + lib.SendSSE("default", message) + + return c.JSON(http.StatusOK, map[string]string{"status": "message sent"}) +} diff --git a/content/code-blocks.md b/content/code-blocks.md new file mode 100644 index 0000000..7a5642d --- /dev/null +++ b/content/code-blocks.md @@ -0,0 +1,13 @@ +--- +name: "Code Blocks" +date: "April 20 1337" +tags: ["article","demo"] +--- +# Markdown is cool! + +Here is an example of a code block!: +```go +func onePlusOne() int { + return 1 + 1 +} +``` \ No newline at end of file diff --git a/content/welcome.md b/content/welcome.md new file mode 100644 index 0000000..ea59669 --- /dev/null +++ b/content/welcome.md @@ -0,0 +1,7 @@ +--- +name: "Welcome!" +date: "April 20 1337" +tags: ["meta", "demo"] +--- + +Hello and welcome to this demo of the GOTH Stack! This file is just regular markdown... feel free to edit it! **Thanks for stopping by**! diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..82e7c68 --- /dev/null +++ b/fly.toml @@ -0,0 +1,17 @@ +app = "goth-stack" +primary_region = "sea" + +[build] + +[http_service] +internal_port = 3000 +force_https = true +auto_stop_machines = true +auto_start_machines = true +min_machines_running = 1 +processes = ["app"] + +[[vm]] +cpu_kind = "shared" +cpus = 1 +memory_mb = 256 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..35ed6c2 --- /dev/null +++ b/go.mod @@ -0,0 +1,42 @@ +module goth.stack + +go 1.21.6 + +require ( + github.com/alecthomas/chroma/v2 v2.12.0 + github.com/labstack/echo/v4 v4.11.4 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/alecthomas/repr v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dlclark/regexp2 v1.10.0 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/alecthomas/assert/v2 v2.4.1 + github.com/go-redis/redismock/v9 v9.2.0 + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/joho/godotenv v1.5.1 + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/redis/go-redis/v9 v9.4.0 + github.com/resendlabs/resend-go v1.7.0 + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/yuin/goldmark v1.6.0 + github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1258c12 --- /dev/null +++ b/go.sum @@ -0,0 +1,90 @@ +github.com/alecthomas/assert/v2 v2.4.1 h1:mwPZod/d35nlaCppr6sFP0rbCL05WH9fIo7lvsf47zo= +github.com/alecthomas/assert/v2 v2.4.1/go.mod h1:fw5suVxB+wfYJ3291t0hRTqtGzFYdSwstnRQdaQx2DM= +github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= +github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw= +github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw= +github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8= +github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-redis/redismock/v9 v9.2.0 h1:ZrMYQeKPECZPjOj5u9eyOjg8Nnb0BS9lkVIZ6IpsKLw= +github.com/go-redis/redismock/v9 v9.2.0/go.mod h1:18KHfGDK4Y6c2R0H38EUGWAdc7ZQS9gfYxc94k7rWT0= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= +github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= +github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/resendlabs/resend-go v1.7.0 h1:DycOqSXtw2q7aB+Nt9DDJUDtaYcrNPGn1t5RFposas0= +github.com/resendlabs/resend-go v1.7.0/go.mod h1:yip1STH7Bqfm4fD0So5HgyNbt5taG5Cplc4xXxETyLI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= +github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= +github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/email.go b/lib/email.go new file mode 100644 index 0000000..ea9dcf0 --- /dev/null +++ b/lib/email.go @@ -0,0 +1,31 @@ +package lib + +import ( + "fmt" + "os" + + "github.com/resendlabs/resend-go" +) + +var client *resend.Client + +// init function +func init() { + client = resend.NewClient(os.Getenv("RESEND_API_KEY")) +} + +func SendEmail(to_email string, from_email string, from_name string, html string, subject string) { + params := &resend.SendEmailRequest{ + From: from_name + "<" + from_email + ">", + To: []string{to_email}, + Html: html, + Subject: subject, + } + + sent, err := client.Emails.Send(params) + if err != nil { + fmt.Println(err.Error()) + return + } + fmt.Println(sent.Id) +} diff --git a/lib/markdown.go b/lib/markdown.go new file mode 100644 index 0000000..a36d7f3 --- /dev/null +++ b/lib/markdown.go @@ -0,0 +1,59 @@ +package lib + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "os" + "strings" + + "github.com/yuin/goldmark" + "gopkg.in/yaml.v2" +) + +func ExtractFrontMatter(file os.DirEntry, dir string) (CardLink, error) { + f, err := os.Open(dir + file.Name()) + if err != nil { + return CardLink{}, fmt.Errorf("failed to open file: %w", err) + } + defer f.Close() + + scanner := bufio.NewScanner(f) + var lines []string + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + if err := scanner.Err(); err != nil { + return CardLink{}, fmt.Errorf("failed to read file: %w", err) + } + + content := strings.Join(lines, "\n") + splitContent := strings.SplitN(content, "---", 3) + if len(splitContent) < 3 { + return CardLink{}, fmt.Errorf("invalid file format: %s", file.Name()) + } + + frontMatter := CardLink{} + if err := yaml.Unmarshal([]byte(splitContent[1]), &frontMatter); err != nil { + return CardLink{}, fmt.Errorf("failed to unmarshal frontmatter: %w", err) + } + + md := goldmark.New(goldmark.WithExtensions()) + var buf bytes.Buffer + if err := md.Convert([]byte(splitContent[2]), &buf); err != nil { + return CardLink{}, fmt.Errorf("failed to convert markdown: %w", err) + } + + return frontMatter, nil +} + +func SplitFrontmatter(md []byte) (frontmatter []byte, content []byte, err error) { + parts := bytes.SplitN(md, []byte("---"), 3) + + if len(parts) < 3 { + return nil, nil, errors.New("invalid or missing frontmatter") + } + + return parts[1], parts[2], nil +} diff --git a/lib/markdown_test.go b/lib/markdown_test.go new file mode 100644 index 0000000..190ccf9 --- /dev/null +++ b/lib/markdown_test.go @@ -0,0 +1,57 @@ +package lib_test + +import ( + "io/fs" + "log" + "os" + "path/filepath" + "testing" + + "github.com/alecthomas/assert/v2" + "goth.stack/lib" +) + +func TestExtractFrontMatter(t *testing.T) { + // Create a temporary file with some front matter + tmpfile, err := os.CreateTemp("../content", "example.*.md") + println(tmpfile.Name()) + if err != nil { + log.Fatal(err) + } + defer os.Remove(tmpfile.Name()) // clean up + + text := `--- +name: "Test Title" +description: "Test Description" +--- + +# Test Content + +` + if _, err := tmpfile.Write([]byte(text)); err != nil { + log.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + log.Fatal(err) + } + + // Get the directory entry for the temporary file + dirEntry, err := os.ReadDir(filepath.Dir(tmpfile.Name())) + if err != nil { + log.Fatal(err) + } + + var tmpFileEntry fs.DirEntry + for _, entry := range dirEntry { + if entry.Name() == filepath.Base(tmpfile.Name()) { + tmpFileEntry = entry + break + } + } + + // Now we can test ExtractFrontMatter + frontMatter, err := lib.ExtractFrontMatter(tmpFileEntry, "../content/") + assert.NoError(t, err) + assert.Equal(t, "Test Title", frontMatter.Name) + assert.Equal(t, "Test Description", frontMatter.Description) +} diff --git a/lib/redis.go b/lib/redis.go new file mode 100644 index 0000000..c833b3b --- /dev/null +++ b/lib/redis.go @@ -0,0 +1,48 @@ +package lib + +import ( + "context" + "log" + "os" + + "github.com/joho/godotenv" + "github.com/redis/go-redis/v9" +) + +var ctx = context.Background() + +var RedisClient *redis.Client + +func NewClient() *redis.Client { + godotenv.Load(".env") + redis_host := os.Getenv("REDIS_HOST") + redis_password := os.Getenv("REDIS_PASSWORD") + + log.Printf("Connecting to Redis at %s", redis_host) + return redis.NewClient(&redis.Options{ + Addr: redis_host, + Password: redis_password, + DB: 0, + }) +} + +func Publish(client *redis.Client, channel string, message string) error { + if client == nil { + client = NewClient() + } + + return client.Publish(ctx, channel, message).Err() +} + +func Subscribe(client *redis.Client, channel string) (*redis.PubSub, string) { + if client == nil { + client = NewClient() + } + + pubsub := client.Subscribe(ctx, channel) + _, err := pubsub.Receive(ctx) + if err != nil { + log.Fatalf("Error receiving subscription: %v", err) + } + return pubsub, channel +} diff --git a/lib/redis_test.go b/lib/redis_test.go new file mode 100644 index 0000000..7643613 --- /dev/null +++ b/lib/redis_test.go @@ -0,0 +1,27 @@ +package lib_test + +import ( + "testing" + + "github.com/go-redis/redismock/v9" + "github.com/stretchr/testify/assert" + "goth.stack/lib" +) + +func TestPublish(t *testing.T) { + db, mock := redismock.NewClientMock() + mock.ExpectPublish("mychannel", "mymessage").SetVal(1) + + err := lib.Publish(db, "mychannel", "mymessage") + assert.NoError(t, err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +// Then you can check the channel name in your test +func TestSubscribe(t *testing.T) { + db, _ := redismock.NewClientMock() + + pubsub, channel := lib.Subscribe(db, "mychannel") + assert.NotNil(t, pubsub) + assert.Equal(t, "mychannel", channel) +} diff --git a/lib/sse.go b/lib/sse.go new file mode 100644 index 0000000..eb16ddb --- /dev/null +++ b/lib/sse.go @@ -0,0 +1,20 @@ +package lib + +func SendSSE(channel string, message string) error { + // Create a channel to receive an error from the goroutine + errCh := make(chan error, 1) + + // Use a goroutine to send the message asynchronously + go func() { + err := Publish(RedisClient, channel, message) + errCh <- err // Send the error to the channel + }() + + // Wait for the goroutine to finish and check for errors + err := <-errCh + if err != nil { + return err + } + + return nil +} diff --git a/lib/types.go b/lib/types.go new file mode 100644 index 0000000..912f1ea --- /dev/null +++ b/lib/types.go @@ -0,0 +1,37 @@ +package lib + +import ( + "html/template" +) + +type IconLink struct { + Name string + Href string + Icon template.HTML +} + +type CardLink struct { + Name string + Href string + Description string + Date string + Tags []string + Internal bool +} + +type Post struct { + Content template.HTML + Name string + Date string + Tags []string +} +type FrontMatter struct { + Name string + Date string + Tags []string +} + +type PubSubMessage struct { + Channel string `json:"channel"` + Data string `json:"data"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..55a531e --- /dev/null +++ b/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "io" + "text/template" + + "goth.stack/api" + "goth.stack/pages" + + "github.com/joho/godotenv" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +// Template Type +type Template struct { + templates *template.Template +} + +// Template Render function +func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { + return t.templates.ExecuteTemplate(w, name, data) +} + +func main() { + godotenv.Load(".env") + + // Initialize router + e := echo.New() + + // Middlewares + e.Pre(middleware.RemoveTrailingSlash()) + e.Use(middleware.Logger()) + e.Use(middleware.RequestID()) + e.Use(middleware.Secure()) + e.Use(middleware.GzipWithConfig(middleware.GzipConfig{ + Level: 5, + })) + e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(69))) + + // Template Parsing + t := &Template{ + templates: template.Must(template.ParseGlob("pages/**/*.html")), + } + + e.Renderer = t + + // // Static server + e.Static("/public", "public") + + // Page routes + e.GET("/", pages.Home) + e.GET("/blog", pages.Blog) + e.GET("/post/:post", pages.Post) + e.GET("/sse", pages.SSEDemo) + + // API Routes: + e.GET("/api/ping", api.Ping) + e.GET("/api/ssedemo", api.SSEDemo) + e.POST("/api/sendsse", api.SSEDemoSend) + + e.Logger.Fatal(e.Start(":3000")) +} diff --git a/pages/blog.go b/pages/blog.go new file mode 100644 index 0000000..ae633d3 --- /dev/null +++ b/pages/blog.go @@ -0,0 +1,75 @@ +package pages + +import ( + "html/template" + "log" + "net/http" + "os" + "sort" + "strings" + "time" + + "github.com/labstack/echo/v4" + "goth.stack/lib" +) + +type BlogProps struct { + Posts []lib.CardLink +} + +func Blog(c echo.Context) error { + var posts []lib.CardLink + + files, err := os.ReadDir("./content/") + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "There was an finding posts!") + } + + for _, file := range files { + frontMatter, err := lib.ExtractFrontMatter(file, "./content/") + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering the posts!") + } + + frontMatter.Href = "post/" + strings.TrimSuffix(file.Name(), ".md") + frontMatter.Internal = true + + posts = append(posts, frontMatter) + } + + const layout = "January 2 2006" + + sort.Slice(posts, func(i, j int) bool { + iDate, err := time.Parse(layout, posts[i].Date) + if err != nil { + log.Fatal(err) + } + + jDate, err := time.Parse(layout, posts[j].Date) + if err != nil { + log.Fatal(err) + } + + return iDate.Before(jDate) + }) + + props := BlogProps{ + Posts: posts, + } + + templates := []string{ + "./pages/templates/layouts/base.html", + "./pages/templates/partials/header.html", + "./pages/templates/partials/navitems.html", + "./pages/templates/partials/cardlinks.html", + "./pages/templates/blog.html", + } + + ts, err := template.ParseFiles(templates...) + if err != nil { + log.Print(err.Error()) + return err + } + + return ts.ExecuteTemplate(c.Response().Writer, "base", props) +} diff --git a/pages/home.go b/pages/home.go new file mode 100644 index 0000000..b25caa2 --- /dev/null +++ b/pages/home.go @@ -0,0 +1,78 @@ +package pages + +import ( + "html/template" + "log" + + "github.com/labstack/echo/v4" + "goth.stack/lib" +) + +type HomeProps struct { + Socials []lib.IconLink + Tech []lib.IconLink + ContractLink string + ResumeURL string + SupportLink string +} + +func Home(c echo.Context) error { + socials := []lib.IconLink{ + { + Name: "Email", + Href: "mailto:me@atri.dad", + Icon: template.HTML(``), + }, + { + Name: "GitHub", + Href: "https://github.com/atridadl/goth-stack", + Icon: template.HTML(``), + }, + } + + tech := []lib.IconLink{ + { + Name: "Go", + Href: "https://golang.org", + Icon: template.HTML(`Go`), + }, + { + Name: "Redis", + Href: "https://redis.io", + Icon: template.HTML(`Redis`), + }, + { + Name: "Fly.io", + Href: "https://fly.io", + Icon: template.HTML(``), + }, + { + Name: "Docker", + Href: "https://docker.com", + Icon: template.HTML(`Docker`), + }, + } + + props := HomeProps{ + Socials: socials, + Tech: tech, + ContractLink: "mailto:contract@atri.dad", + ResumeURL: "https://srv.atri.dad/Atridad_Lahiji_Resume.pdf", + SupportLink: "https://donate.stripe.com/8wMeVF25c78L0V2288", + } + + templates := []string{ + "./pages/templates/layouts/base.html", + "./pages/templates/partials/header.html", + "./pages/templates/partials/navitems.html", + "./pages/templates/home.html", + } + + ts, err := template.ParseFiles(templates...) + if err != nil { + log.Print(err.Error()) + return err + } + + return ts.ExecuteTemplate(c.Response().Writer, "base", props) +} diff --git a/pages/post.go b/pages/post.go new file mode 100644 index 0000000..f4e8ffe --- /dev/null +++ b/pages/post.go @@ -0,0 +1,80 @@ +package pages + +import ( + "bytes" + "html/template" + "net/http" + "os" + + chromahtml "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/labstack/echo/v4" + "github.com/yuin/goldmark" + highlighting "github.com/yuin/goldmark-highlighting/v2" + "gopkg.in/yaml.v2" + "goth.stack/lib" +) + +type PostProps struct { + Content template.HTML + Name string + Date string + Tags []string +} + +func Post(c echo.Context) error { + postName := c.ParamValues()[0] + + filePath := "content/" + postName + ".md" + + md, err := os.ReadFile(filePath) + if err != nil { + return echo.NewHTTPError(http.StatusNotFound, "This post does not exist!") + } + + frontmatterBytes, content, err := lib.SplitFrontmatter(md) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!") + } + + var frontmatter lib.FrontMatter + if err := yaml.Unmarshal(frontmatterBytes, &frontmatter); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!") + } + + var buf bytes.Buffer + markdown := goldmark.New( + goldmark.WithExtensions( + highlighting.NewHighlighting( + highlighting.WithStyle("dracula"), + highlighting.WithFormatOptions( + chromahtml.WithLineNumbers(true), + ), + ), + ), + ) + + if err := markdown.Convert(content, &buf); err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!") + } + + props := PostProps{ + Content: template.HTML(buf.String()), + Name: frontmatter.Name, + Date: frontmatter.Date, + Tags: frontmatter.Tags, + } + + templates := []string{ + "./pages/templates/layouts/post.html", + "./pages/templates/partials/header.html", + "./pages/templates/partials/navitems.html", + "./pages/templates/post.html", + } + + ts, err := template.ParseFiles(templates...) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "There was an issue rendering this post!") + } + + return ts.ExecuteTemplate(c.Response().Writer, "post", props) +} diff --git a/pages/ssedemo.go b/pages/ssedemo.go new file mode 100644 index 0000000..6f58e8a --- /dev/null +++ b/pages/ssedemo.go @@ -0,0 +1,25 @@ +package pages + +import ( + "html/template" + "log" + + "github.com/labstack/echo/v4" +) + +func SSEDemo(c echo.Context) error { + templates := []string{ + "./pages/templates/layouts/base.html", + "./pages/templates/partials/header.html", + "./pages/templates/partials/navitems.html", + "./pages/templates/ssedemo.html", + } + + ts, err := template.ParseFiles(templates...) + if err != nil { + log.Print(err.Error()) + return err + } + + return ts.ExecuteTemplate(c.Response().Writer, "base", nil) +} diff --git a/pages/templates/blog.html b/pages/templates/blog.html new file mode 100644 index 0000000..3a3c8d4 --- /dev/null +++ b/pages/templates/blog.html @@ -0,0 +1,18 @@ +{{define "title"}} +GOTH // Blog +{{end}} + +{{define "headercontent"}} +GOTH // Blog +{{end}} + +{{define "head"}} +{{end}} + +{{define "main"}} +
+ {{range .Posts}} + {{template "cardlinks" .}} + {{end}} +
+{{end}} diff --git a/pages/templates/home.html b/pages/templates/home.html new file mode 100644 index 0000000..66e8885 --- /dev/null +++ b/pages/templates/home.html @@ -0,0 +1,51 @@ +{{define "title"}} +GOTH // Home +{{end}} + +{{define "headercontent"}} +GOTH // Home +{{end}} + +{{define "head"}} +{{end}} + +{{define "main"}} +

+ GOTH Stack +

+ +

+ A full-stack template for building modern web applications with Go and HTMX. +

+ + +

Links:

+
+ {{range .Socials}} + + {{.Icon}} + + {{end}} +
+
+ + +

Technologies Used:

+
+ {{range .Tech}} + + {{.Icon}} + + {{end}} +
+
+{{end}} diff --git a/pages/templates/layouts/base.html b/pages/templates/layouts/base.html new file mode 100644 index 0000000..973d49b --- /dev/null +++ b/pages/templates/layouts/base.html @@ -0,0 +1,22 @@ +{{define "base"}} + + + + + + + {{template "title" .}} + + + {{template "head" .}} + + + + {{template "header" .}} + +
+ {{template "main" .}} +
+ + +{{end}} \ No newline at end of file diff --git a/pages/templates/layouts/post.html b/pages/templates/layouts/post.html new file mode 100644 index 0000000..b964e71 --- /dev/null +++ b/pages/templates/layouts/post.html @@ -0,0 +1,43 @@ +{{define "post"}} + + + + + + {{.Name}} + + + + + + {{template "header" .}} + +
+
+

{{.Name}}

+
+ {{if .Date}} +

+

+ + {{.Date}} +
+

+ {{end}} + + {{if .Tags}} +
+ {{range .Tags}} +
#{{.}}
+ {{end}} +
+ {{end}} +
+
+ {{template "main" .}} + +
+
+ + +{{end}} \ No newline at end of file diff --git a/pages/templates/partials/cardlinks.html b/pages/templates/partials/cardlinks.html new file mode 100644 index 0000000..7cd004e --- /dev/null +++ b/pages/templates/partials/cardlinks.html @@ -0,0 +1,45 @@ +{{define "cardlinks"}} +
+
+

{{.Name}}

+ + {{if .Description}} +

{{.Description}}

+ {{end}} + + {{if .Date}} +

+

+ + {{.Date}} +
+

+ {{end}} + + {{if .Tags}} +
+ {{range .Tags}} +
#{{.}}
+ {{end}} +
+ {{end}} + + {{if .Href}} + + {{end}} +
+
+{{end}} \ No newline at end of file diff --git a/pages/templates/partials/header.html b/pages/templates/partials/header.html new file mode 100644 index 0000000..b50fcd9 --- /dev/null +++ b/pages/templates/partials/header.html @@ -0,0 +1,20 @@ +{{define "header"}} + +{{end}} \ No newline at end of file diff --git a/pages/templates/partials/navitems.html b/pages/templates/partials/navitems.html new file mode 100644 index 0000000..ef27d16 --- /dev/null +++ b/pages/templates/partials/navitems.html @@ -0,0 +1,17 @@ +{{define "navitems"}} +
  • + + Home + +
  • +
  • + + SSE Demo + +
  • +
  • + + Blog Demo + +
  • +{{end}} diff --git a/pages/templates/post.html b/pages/templates/post.html new file mode 100644 index 0000000..8166152 --- /dev/null +++ b/pages/templates/post.html @@ -0,0 +1,14 @@ +{{define "title"}} +GOTH // Post +{{end}} + +{{define "headercontent"}} +GOTH // Post +{{end}} + +{{define "head"}} +{{end}} + +{{define "main"}} +{{.Content}} +{{end}} diff --git a/pages/templates/ssedemo.html b/pages/templates/ssedemo.html new file mode 100644 index 0000000..40c3aa3 --- /dev/null +++ b/pages/templates/ssedemo.html @@ -0,0 +1,29 @@ +{{define "title"}}GOTH // SSE{{end}} + +{{define "headercontent"}} +GOTH // SSE
    DEMO
    +{{end}} + +{{define "head"}} + + +{{end}} + +{{define "main"}} +

    Server Sent Events

    +

    This page demonstrates the use of the HTMX SSE Extention to receive Server Sent Events on the "default" channel.

    +

    Any events received on the "default" channel will appear below:

    +
    + Waiting for SSE Message... +
    + +

    Here you can send messages on the default channel:

    +
    +
    + Message +
    + + + +
    +{{end}} diff --git a/public/css/styles.css b/public/css/styles.css new file mode 100644 index 0000000..7d41098 --- /dev/null +++ b/public/css/styles.css @@ -0,0 +1 @@ +/*! tailwindcss v3.4.0 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}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-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-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,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}:root,[data-theme]{background-color:var(--fallback-b1,oklch(var(--b1)/1));color:var(--fallback-bc,oklch(var(--bc)/1))}@supports not (color:oklch(0 0 0)){:root{color-scheme:light;--fallback-p:#491eff;--fallback-pc:#d4dbff;--fallback-s:#ff41c7;--fallback-sc:#fff9fc;--fallback-a:#00cfbd;--fallback-ac:#00100d;--fallback-n:#2b3440;--fallback-nc:#d7dde4;--fallback-b1:#fff;--fallback-b2:#e5e6e6;--fallback-b3:#e5e6e6;--fallback-bc:#1f2937;--fallback-in:#00b3f0;--fallback-inc:#000;--fallback-su:#00ca92;--fallback-suc:#000;--fallback-wa:#ffc22d;--fallback-wac:#000;--fallback-er:#ff6f70;--fallback-erc:#000}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--fallback-p:#7582ff;--fallback-pc:#050617;--fallback-s:#ff71cf;--fallback-sc:#190211;--fallback-a:#00c7b5;--fallback-ac:#000e0c;--fallback-n:#2a323c;--fallback-nc:#a6adbb;--fallback-b1:#1d232a;--fallback-b2:#191e24;--fallback-b3:#15191e;--fallback-bc:#a6adbb;--fallback-in:#00b3f0;--fallback-inc:#000;--fallback-su:#00ca92;--fallback-suc:#000;--fallback-wa:#ffc22d;--fallback-wac:#000;--fallback-er:#ff6f70;--fallback-erc:#000}}}html{-webkit-tap-highlight-color:transparent}:root{color-scheme:dark;--b2:0.193144 0.037037 265.754874;--b3:0.178606 0.034249 265.754874;--bc:0.841536 0.007965 265.754874;--pc:0.150703 0.027798 232.66148;--sc:0.136023 0.031661 276.934902;--ac:0.144721 0.035244 350.048739;--nc:0.855899 0.00737 260.030984;--suc:0.156904 0.026506 181.911977;--wac:0.166486 0.027912 82.95003;--erc:0.143572 0.034051 13.11834;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:0.753513 0.138989 232.66148;--s:0.680113 0.158303 276.934902;--a:0.723603 0.176218 350.048739;--n:0.279495 0.036848 260.030984;--b1:0.207682 0.039824 265.754874;--in:0.684553 0.148062 237.25135;--inc:0 0 0;--su:0.78452 0.132529 181.911977;--wa:0.832428 0.139558 82.95003;--er:0.717858 0.170255 13.11834}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.badge{display:inline-flex;align-items:center;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;height:1.25rem;font-size:.875rem;line-height:1.25rem;width:-moz-fit-content;width:fit-content;padding-left:.563rem;padding-right:.563rem;border-radius:var(--rounded-badge,1.9rem);border-width:1px;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}@media (hover:hover){.label a:hover{--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.menu li>:not(ul):not(.menu-title):not(details).active,.menu li>:not(ul):not(.menu-title):not(details):active,.menu li>details>summary:active{--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}}.btn{display:inline-flex;height:3rem;min-height:3rem;flex-shrink:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;border-radius:var(--rounded-btn,.5rem);border-color:#0000;border-color:oklch(var(--btn-color,var(--b2))/var(--tw-border-opacity));padding-left:1rem;padding-right:1rem;text-align:center;font-size:.875rem;line-height:1em;gap:.5rem;font-weight:600;text-decoration-line:none;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);border-width:var(--border-btn,1px);animation:button-pop var(--animation-btn,.25s) ease-out;transition-property:color,background-color,border-color,opacity,box-shadow,transform;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);outline-color:var(--fallback-bc,oklch(var(--bc)/1));background-color:oklch(var(--btn-color,var(--b2))/var(--tw-bg-opacity));--tw-bg-opacity:1;--tw-border-opacity:1}.btn-disabled,.btn:disabled,.btn[disabled]{pointer-events:none}.btn-circle{height:3rem;width:3rem;border-radius:9999px;padding:0}:where(.btn:is(input[type=checkbox])),:where(.btn:is(input[type=radio])){width:auto;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn:is(input[type=checkbox]):after,.btn:is(input[type=radio]):after{--tw-content:attr(aria-label);content:var(--tw-content)}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid #0000;outline-offset:2px}.card-body{display:flex;flex:1 1 auto;flex-direction:column;padding:var(--padding-card,2rem);gap:.5rem}.card-body :where(p){flex-grow:1}.card-actions{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.5rem}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;border-radius:var(--rounded-box,1rem);--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));opacity:.75}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.dropdown{position:relative;display:inline-block}.dropdown>:not(summary):focus{outline:2px solid #0000;outline-offset:2px}.dropdown .dropdown-content{position:absolute}.dropdown:is(:not(details)) .dropdown-content{visibility:hidden;opacity:0;transform-origin:top;--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s}.dropdown-end .dropdown-content{inset-inline-end:0}.dropdown-left .dropdown-content{bottom:auto;inset-inline-end:100%;top:0;transform-origin:right}.dropdown-right .dropdown-content{bottom:auto;inset-inline-start:100%;top:0;transform-origin:left}.dropdown-bottom .dropdown-content{bottom:auto;top:100%;transform-origin:top}.dropdown-top .dropdown-content{bottom:100%;top:auto;transform-origin:bottom}.dropdown-end.dropdown-left .dropdown-content,.dropdown-end.dropdown-right .dropdown-content{bottom:0;top:auto}.dropdown.dropdown-open .dropdown-content,.dropdown:focus-within .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content{visibility:visible;opacity:1}@media (hover:hover){.dropdown.dropdown-hover:hover .dropdown-content{visibility:visible;opacity:1}.btn:hover{--tw-border-opacity:1;border-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn:hover{background-color:color-mix(in oklab,oklch(var(--btn-color,var(--b2))/var(--tw-bg-opacity,1)) 90%,#000);border-color:color-mix(in oklab,oklch(var(--btn-color,var(--b2))/var(--tw-border-opacity,1)) 90%,#000)}}@supports not (color:oklch(0 0 0)){.btn:hover{background-color:var(--btn-color,var(--fallback-b2));border-color:var(--btn-color,var(--fallback-b2))}}.btn.glass:hover{--glass-opacity:25%;--glass-border-opacity:15%}.btn-ghost:hover{border-color:#0000}@supports (color:oklch(0 0 0)){.btn-ghost:hover{background-color:var(--fallback-bc,oklch(var(--bc)/.2))}}.btn-outline.btn-primary:hover{--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-primary:hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000)}}.btn-disabled:hover,.btn:disabled:hover,.btn[disabled]:hover{--tw-border-opacity:0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity:0.2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity:0.2}@supports (color:color-mix(in oklab,black,black)){.btn:is(input[type=checkbox]:checked):hover,.btn:is(input[type=radio]:checked):hover{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000)}}.dropdown.dropdown-hover:hover .dropdown-content{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:where(.menu li:not(.menu-title):not(.disabled)>:not(ul):not(details):not(.menu-title)):not(.active):hover,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(.active):hover{cursor:pointer;outline:2px solid #0000;outline-offset:2px}@supports (color:oklch(0 0 0)){:where(.menu li:not(.menu-title):not(.disabled)>:not(ul):not(details):not(.menu-title)):not(.active):hover,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(.active):hover{background-color:var(--fallback-bc,oklch(var(--bc)/.1))}}}.dropdown:is(details) summary::-webkit-details-marker{display:none}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding:.5rem .25rem}.input{flex-shrink:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:1rem;font-size:1rem;line-height:2;line-height:1.5rem;border-radius:var(--rounded-btn,.5rem);border-width:1px;border-color:#0000;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.join .dropdown .join-item:first-child:not(:last-child),.join :first-child:not(:last-child) .dropdown .join-item{border-start-end-radius:inherit;border-end-end-radius:inherit}.link{cursor:pointer;text-decoration-line:underline}.menu{display:flex;flex-direction:column;flex-wrap:wrap;font-size:.875rem;line-height:1.25rem;padding:.5rem}.menu :where(li ul){position:relative;white-space:nowrap;margin-inline-start:1rem;padding-inline-start:.5rem}.menu :where(li:not(.menu-title)>:not(ul):not(details):not(.menu-title)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){display:grid;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:.5rem;grid-auto-columns:minmax(auto,max-content) auto max-content;-webkit-user-select:none;-moz-user-select:none;user-select:none}.menu li.disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:var(--fallback-bc,oklch(var(--bc)/.3))}.menu :where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}:where(.menu li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}:where(.menu li) .badge{justify-self:end}.navbar{display:flex;align-items:center;padding:var(--navbar-padding,.5rem);min-height:4rem;width:100%}:where(.navbar>*){display:inline-flex;align-items:center}.navbar-start{width:50%;justify-content:flex-start}.navbar-end{width:50%;justify-content:flex-end}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw:var(--fallback-bc,oklch(var(--bc)/1));overflow:hidden;border-radius:var(--rounded-box,1rem);background-color:initial}.range:focus{outline:none}.stats{display:inline-grid;border-radius:var(--rounded-box,1rem);--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}:where(.stats){grid-auto-flow:column;overflow-x:auto}.badge-accent{--tw-border-opacity:1;border-color:var(--fallback-a,oklch(var(--a)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}.badge-outline.badge-accent{--tw-text-opacity:1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)))}.btm-nav>* .label{font-size:1rem;line-height:1.5rem}.btn:active:focus,.btn:active:hover{animation:button-pop 0s ease-out;transform:scale(var(--btn-focus-scale,.97))}@supports not (color:oklch(0 0 0)){.btn{background-color:var(--btn-color,var(--fallback-b2));border-color:var(--btn-color,var(--fallback-b2))}.btn-primary{--btn-color:var(--fallback-p)}.prose :where(code):not(:where([class~=not-prose] *,pre *)){background-color:var(--fallback-b3,oklch(var(--b3)/1))}}@supports (color:color-mix(in oklab,black,black)){.btn-outline.btn-primary.btn-active{background-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-p,oklch(var(--p)/1)) 90%,#000)}}.btn:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px}.btn-primary{--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));outline-color:var(--fallback-p,oklch(var(--p)/1))}@supports (color:oklch(0 0 0)){.btn-primary{--btn-color:var(--p)}}.btn.glass{--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn.glass.btn-active{--glass-opacity:25%;--glass-border-opacity:15%}.btn-ghost{border-width:1px;border-color:#0000;background-color:initial;color:currentColor;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);outline-color:currentColor}.btn-ghost.btn-active{border-color:#0000;background-color:var(--fallback-bc,oklch(var(--bc)/.2))}.btn-outline.btn-primary{--tw-text-opacity:1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.btn-outline.btn-primary.btn-active{--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn.btn-disabled,.btn:disabled,.btn[disabled]{--tw-border-opacity:0;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-bg-opacity:0.2;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));--tw-text-opacity:0.2}.btn:is(input[type=checkbox]:checked),.btn:is(input[type=radio]:checked){--tw-border-opacity:1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)))}.btn:is(input[type=checkbox]:checked):focus-visible,.btn:is(input[type=radio]:checked):focus-visible{outline-color:var(--fallback-p,oklch(var(--p)/1))}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,.98))}40%{transform:scale(1.02)}to{transform:scale(1)}}.card :where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card :where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)))}.card.compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-title{display:flex;align-items:center;gap:.5rem;font-size:1.25rem;line-height:1.75rem;font-weight:600}.card.image-full :where(figure){overflow:hidden;border-radius:inherit}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}to{background-position-y:0}}.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--tw-scale-x:1;--tw-scale-y:1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.label-text{font-size:.875rem;line-height:1.25rem;--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)))}.input input:focus{outline:2px solid #0000;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input-bordered,.input:focus,.input:focus-within{border-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input:focus,.input:focus-within{box-shadow:none;outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:var(--fallback-bc,oklch(var(--bc)/.2))}.input-primary,.input-primary:focus,.input-primary:focus-within{--tw-border-opacity:1;border-color:var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity)))}.input-primary:focus,.input-primary:focus-within{outline-color:var(--fallback-p,oklch(var(--p)/1))}.input-disabled,.input:disabled,.input[disabled]{cursor:not-allowed;--tw-border-opacity:1;border-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));color:var(--fallback-bc,oklch(var(--bc)/.4))}.input-disabled::-moz-placeholder,.input:disabled::-moz-placeholder,.input[disabled]::-moz-placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity:0.2}.input-disabled::placeholder,.input:disabled::placeholder,.input[disabled]::placeholder{color:var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));--tw-placeholder-opacity:0.2}.input::-webkit-date-and-time-value{text-align:inherit}.link:focus{outline:2px solid #0000;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}:where(.menu li:empty){--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.1;margin:.5rem 1rem;height:1px}.menu :where(li ul):before{position:absolute;bottom:.75rem;inset-inline-start:0;top:.75rem;width:1px;--tw-bg-opacity:1;background-color:var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));opacity:.1;content:""}.menu :where(li:not(.menu-title)>:not(ul):not(details):not(.menu-title)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--rounded-btn,.5rem);padding:.5rem 1rem;text-align:start;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-timing-function:cubic-bezier(0,0,.2,1);transition-duration:.2s;text-wrap:balance}:where(.menu li:not(.menu-title):not(.disabled)>:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible,:where(.menu li:not(.menu-title):not(.disabled)>:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):is(summary):not(.active):focus-visible,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active):focus{cursor:pointer;background-color:var(--fallback-bc,oklch(var(--bc)/.1));--tw-text-opacity:1;color:var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));outline:2px solid #0000;outline-offset:2px}.menu li>:not(ul):not(.menu-title):not(details).active,.menu li>:not(ul):not(.menu-title):not(details):active,.menu li>details>summary:active{--tw-bg-opacity:1;background-color:var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));--tw-text-opacity:1;color:var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)))}.menu :where(li>details>summary)::-webkit-details-marker{display:none}.menu :where(li>.menu-dropdown-toggle):after,.menu :where(li>details>summary):after{justify-self:end;display:block;margin-top:-.5rem;height:.5rem;width:.5rem;transform:rotate(45deg);transition-property:transform,margin-top;transition-duration:.3s;transition-timing-function:cubic-bezier(.4,0,.2,1);content:"";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.menu :where(li>.menu-dropdown-toggle.menu-dropdown-show):after,.menu :where(li>details[open]>summary):after{transform:rotate(225deg);margin-top:0}.mockup-browser .mockup-browser-toolbar .input{position:relative;margin-left:auto;margin-right:auto;display:block;height:1.75rem;width:24rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;--tw-bg-opacity:1;background-color:var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));padding-left:2rem;direction:ltr}.mockup-browser .mockup-browser-toolbar .input:before{left:.5rem;aspect-ratio:1/1;height:.75rem;--tw-translate-y:-50%;border-radius:9999px;border-width:2px;border-color:currentColor}.mockup-browser .mockup-browser-toolbar .input:after,.mockup-browser .mockup-browser-toolbar .input:before{content:"";position:absolute;top:50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));opacity:.6}.mockup-browser .mockup-browser-toolbar .input:after{left:1.25rem;height:.5rem;--tw-translate-y:25%;--tw-rotate:-45deg;border-radius:9999px;border-width:1px;border-color:currentColor}@keyframes modal-pop{0%{opacity:0}}@keyframes progress-loading{50%{background-position-x:-115%}}@keyframes radiomark{0%{box-shadow:0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 12px var(--fallback-b1,oklch(var(--b1)/1)) inset}50%{box-shadow:0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 3px var(--fallback-b1,oklch(var(--b1)/1)) inset}to{box-shadow:0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 4px var(--fallback-b1,oklch(var(--b1)/1)) inset}}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 2rem var(--range-shdw) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px var(--fallback-b1,oklch(var(--b1)/1)) inset,0 0 0 2rem var(--range-shdw) inset}.range::-webkit-slider-runnable-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.range::-moz-range-track{height:.5rem;width:100%;border-radius:var(--rounded-box,1rem);background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.range::-webkit-slider-thumb{position:relative;height:1.5rem;width:1.5rem;border-radius:var(--rounded-box,1rem);border-style:none;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));appearance:none;-webkit-appearance:none;top:50%;color:var(--range-shdw);transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px var(--range-shdw) inset,var(--focus-shadow,0 0),calc(var(--filler-size)*-1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{position:relative;height:1.5rem;width:1.5rem;border-radius:var(--rounded-box,1rem);border-style:none;--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));top:50%;color:var(--range-shdw);--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px var(--range-shdw) inset,var(--focus-shadow,0 0),calc(var(--filler-size)*-1 - var(--filler-offset)) 0 0 var(--filler-size)}@keyframes rating-pop{0%{transform:translateY(-.125em)}40%{transform:translateY(-.125em)}to{transform:translateY(0)}}@keyframes skeleton{0%{background-position:150%}to{background-position:-50%}}:where(.stats)>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;--tw-divide-y-reverse:0;border-width:calc(0px*(1 - var(--tw-divide-y-reverse))) calc(1px*var(--tw-divide-x-reverse)) calc(0px*var(--tw-divide-y-reverse)) calc(1px*(1 - var(--tw-divide-x-reverse)))}:is([dir=rtl] .stats>:not([hidden])~:not([hidden])){--tw-divide-x-reverse:1}@keyframes toast-pop{0%{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}:root .prose{--tw-prose-body:var(--fallback-bc,oklch(var(--bc)/0.8));--tw-prose-headings:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-lead:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-links:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-bold:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-counters:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-bullets:var(--fallback-bc,oklch(var(--bc)/0.5));--tw-prose-hr:var(--fallback-bc,oklch(var(--bc)/0.2));--tw-prose-quotes:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-quote-borders:var(--fallback-bc,oklch(var(--bc)/0.2));--tw-prose-captions:var(--fallback-bc,oklch(var(--bc)/0.5));--tw-prose-code:var(--fallback-bc,oklch(var(--bc)/1));--tw-prose-pre-code:var(--fallback-nc,oklch(var(--nc)/1));--tw-prose-pre-bg:var(--fallback-n,oklch(var(--n)/1));--tw-prose-th-borders:var(--fallback-bc,oklch(var(--bc)/0.5));--tw-prose-td-borders:var(--fallback-bc,oklch(var(--bc)/0.2))}.prose :where(code):not(:where([class~=not-prose] *,pre *)){padding:1px 8px;border-radius:var(--rounded-badge);font-weight:400;background-color:var(--fallback-bc,oklch(var(--bc)/.1))}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after,.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{display:none}.prose pre code{border-radius:0;padding:0}.prose :where(tbody tr,thead):not(:where([class~=not-prose] *)){border-bottom-color:var(--fallback-bc,oklch(var(--bc)/.2))}.btn-sm{height:2rem;min-height:2rem;padding-left:.75rem;padding-right:.75rem;font-size:.875rem}.btn-square:where(.btn-sm){height:2rem;width:2rem;padding:0}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0}.card-compact .card-body{padding:1rem;font-size:.875rem;line-height:1.25rem}.card-compact .card-title{margin-bottom:.25rem}.card-normal .card-body{padding:var(--padding-card,2rem);font-size:1rem;line-height:1.5rem}.card-normal .card-title{margin-bottom:.75rem}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);text-decoration:underline;font-weight:500}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-left:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{font-weight:400;color:var(--tw-prose-counters)}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-style:italic;color:var(--tw-prose-quotes);border-left-width:.25rem;border-left-color:var(--tw-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-left:1em}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:900;color:inherit}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:800;color:inherit}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:.6em;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:700;color:inherit}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-top:2em;margin-bottom:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:500;font-family:inherit;color:var(--tw-prose-kbd);box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);font-size:.875em;border-radius:.3125rem;padding:.1875em .375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-weight:600;font-size:.875em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:.375rem;padding:.8571429em 1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:initial;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:initial}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-kbd:#111827;--tw-prose-kbd-shadows:17 24 39;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-left:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-right:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-left:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-right:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.prose-invert{--tw-prose-body:var(--tw-prose-invert-body);--tw-prose-headings:var(--tw-prose-invert-headings);--tw-prose-lead:var(--tw-prose-invert-lead);--tw-prose-links:var(--tw-prose-invert-links);--tw-prose-bold:var(--tw-prose-invert-bold);--tw-prose-counters:var(--tw-prose-invert-counters);--tw-prose-bullets:var(--tw-prose-invert-bullets);--tw-prose-hr:var(--tw-prose-invert-hr);--tw-prose-quotes:var(--tw-prose-invert-quotes);--tw-prose-quote-borders:var(--tw-prose-invert-quote-borders);--tw-prose-captions:var(--tw-prose-invert-captions);--tw-prose-kbd:var(--tw-prose-invert-kbd);--tw-prose-kbd-shadows:var(--tw-prose-invert-kbd-shadows);--tw-prose-code:var(--tw-prose-invert-code);--tw-prose-pre-code:var(--tw-prose-invert-pre-code);--tw-prose-pre-bg:var(--tw-prose-invert-pre-bg);--tw-prose-th-borders:var(--tw-prose-invert-th-borders);--tw-prose-td-borders:var(--tw-prose-invert-td-borders)}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.flex{display:flex}.h-\[100\%\]{height:100%}.min-h-\[calc\(100\%-64px\)\]{min-height:calc(100% - 64px)}.w-64{width:16rem}.w-full{width:100%}.max-w-xs{max-width:20rem}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.rounded-box{border-radius:var(--rounded-box,1rem)}.bg-base-100{--tw-bg-opacity:1;background-color:var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)))}.bg-secondary{--tw-bg-opacity:1;background-color:var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-pink-500{--tw-gradient-from:#ec4899 var(--tw-gradient-from-position);--tw-gradient-to:#ec489900 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-blue-500{--tw-gradient-to:#3b82f6 var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.fill-white{fill:#fff}.p-2{padding:.5rem}.p-4{padding:1rem}.text-center{text-align:center}.align-middle{vertical-align:middle}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.font-extrabold{font-weight:800}.normal-case{text-transform:none}.tracking-tight{letter-spacing:-.025em}.text-base-100{--tw-text-opacity:1;color:var(--fallback-b1,oklch(var(--b1)/var(--tw-text-opacity)))}.text-primary{--tw-text-opacity:1;color:var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)))}.text-transparent{color:#0000}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.no-underline{text-decoration-line:none}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}body,container,html{height:100%;width:100%;overflow-y:auto;position:fixed}@media (hover:hover){.hover\:btn-accent:hover.btn-outline:hover{--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}@supports (color:color-mix(in oklab,black,black)){.hover\:btn-accent:hover.btn-outline:hover{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000)}}}@supports not (color:oklch(0 0 0)){.hover\:btn-accent:hover{--btn-color:var(--fallback-a)}}@supports (color:color-mix(in oklab,black,black)){.hover\:btn-accent:hover.btn-outline.btn-active{background-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000);border-color:color-mix(in oklab,var(--fallback-a,oklch(var(--a)/1)) 90%,#000)}}@supports (color:oklch(0 0 0)){.hover\:btn-accent:hover{--btn-color:var(--a)}}.hover\:btn-accent:hover{--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));outline-color:var(--fallback-a,oklch(var(--a)/1))}.hover\:btn-accent:hover.btn-outline{--tw-text-opacity:1;color:var(--fallback-a,oklch(var(--a)/var(--tw-text-opacity)))}.hover\:btn-accent:hover.btn-outline.btn-active{--tw-text-opacity:1;color:var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)))}.hover\:fill-pink-500:hover{fill:#ec4899}.hover\:text-neutral:hover{--tw-text-opacity:1;color:var(--fallback-n,oklch(var(--n)/var(--tw-text-opacity)))}@media (min-width:640px){.sm\:gap-6{gap:1.5rem}.sm\:text-8xl{font-size:6rem;line-height:1}.sm\:text-\[1\.5rem\]{font-size:1.5rem}.sm\:text-\[2rem\]{font-size:2rem}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}} \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f490ffabbd00afdfc3b8d8d88dbd4752ecdc49ba GIT binary patch literal 15406 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 literal 0 HcmV?d00001 diff --git a/public/js/htmx.min.js b/public/js/htmx.min.js new file mode 100644 index 0000000..47eb70f --- /dev/null +++ b/public/js/htmx.min.js @@ -0,0 +1 @@ +(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:F,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(e,t){var r=dr(e,t||"post");return r.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Br,logAll:V,logNone:j,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.10"};var r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R};var w=["get","post","put","delete","patch"];var i=w.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var S=e("head"),q=e("title"),H=e("svg",true);function e(e,t=false){return new RegExp(`<${e}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${e}>`,t?"gim":"im")}function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function L(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=L(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function A(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function a(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function N(e){return/",0);return i.querySelector("template").content}switch(r){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return a(""+n+"
    ",1);case"col":return a(""+n+"
    ",2);case"tr":return a(""+n+"
    ",2);case"td":case"th":return a(""+n+"
    ",3);case"script":case"style":return a("
    "+n+"
    ",1);default:return a(n,0)}}function ie(e){if(e){e()}}function I(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return I(e,"Function")}function P(e){return I(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function M(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function D(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){b(e);return null}}function U(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function B(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function t(e){return Tr(re().body,function(){return eval(e)})}function F(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function j(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function _(e,t){e=g(e);if(t){setTimeout(function(){_(e);e=null},t)}else{e.parentElement.removeChild(e)}}function z(e,t,r){e=g(e);if(r){setTimeout(function(){z(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=g(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function $(e,t){e=g(e);e.classList.toggle(t)}function W(e,t){e=g(e);oe(e.parentElement.children,function(e){n(e,t)});z(e,t)}function v(e,t){e=g(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function s(e,t){return e.substring(0,t.length)===t}function G(e,t){return e.substring(e.length-t.length)===t}function J(e){var t=e.trim();if(s(t,"<")&&G(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function Z(e,t){if(t.indexOf("closest ")===0){return[v(e,J(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,J(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[K(e,J(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[Y(e,J(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(J(t))}}var K=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return Z(e,t)[0]}else{return Z(re().body,e)[0]}}function g(e){if(I(e,"String")){return C(e)}else{return e}}function ve(e,t,r){if(k(t)){return{target:re().body,event:e,listener:t}}else{return{target:g(e),event:t,listener:r}}}function de(t,r,n){jr(function(){var e=ve(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=k(r);return e?r:n}function ge(t,r,n){jr(function(){var e=ve(t,r,n);e.target.removeEventListener(e.event,e.listener)});return k(r)?r:n}var me=re().createElement("output");function pe(e,t){var r=ne(e,t);if(r){if(r==="this"){return[xe(e,t)]}else{var n=Z(e,r);if(n.length===0){b('The selector "'+r+'" on '+t+" returned no matches!");return[me]}else{return n}}}}function xe(e,t){return c(e,function(e){return te(e,t)!=null})}function ye(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return xe(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function be(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!Se(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){Fe(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function Ce(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(var a=0;a0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();we(e,i);s.tasks.push(function(){we(e,a)})}}})}function Oe(e){return function(){n(e,Q.config.addedClass);zt(e);Nt(e);qe(e);ce(e,"htmx:load")}}function qe(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function m(e,t,r,n){Te(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;z(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Oe(i))}}}function He(e,t){var r=0;while(r-1){var t=e.replace(H,"");var r=t.match(q);if(r){return r[2]}}}function je(e,t,r,n,i,a){i.title=Ve(n);var o=l(n);if(o){Ce(r,o,i);o=Be(r,o,a);Re(o);return Fe(e,r,t,o,i)}}function _e(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!P(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=Tr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Qe(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!t.test(e[0])){r+=e.shift()}return r}function tt(e){var t;if(e.length>0&&Ze.test(e[0])){e.shift();t=y(e,Ke).trim();e.shift()}else{t=y(e,x)}return t}var rt="input, textarea, select";function nt(e,t,r){var n=[];var i=Ye(t);do{y(i,Je);var a=i.length;var o=y(i,/[,\[\s]/);if(o!==""){if(o==="every"){var s={trigger:"every"};y(i,Je);s.pollInterval=d(y(i,/[,\[\s]/));y(i,Je);var l=et(e,i,"event");if(l){s.eventFilter=l}n.push(s)}else if(o.indexOf("sse:")===0){n.push({trigger:"sse",sseEvent:o.substr(4)})}else{var u={trigger:o};var l=et(e,i,"event");if(l){u.eventFilter=l}while(i.length>0&&i[0]!==","){y(i,Je);var f=i.shift();if(f==="changed"){u.changed=true}else if(f==="once"){u.once=true}else if(f==="consume"){u.consume=true}else if(f==="delay"&&i[0]===":"){i.shift();u.delay=d(y(i,x))}else if(f==="from"&&i[0]===":"){i.shift();if(Ze.test(i[0])){var c=tt(i)}else{var c=y(i,x);if(c==="closest"||c==="find"||c==="next"||c==="previous"){i.shift();var h=tt(i);if(h.length>0){c+=" "+h}}}u.from=c}else if(f==="target"&&i[0]===":"){i.shift();u.target=tt(i)}else if(f==="throttle"&&i[0]===":"){i.shift();u.throttle=d(y(i,x))}else if(f==="queue"&&i[0]===":"){i.shift();u.queue=y(i,x)}else if(f==="root"&&i[0]===":"){i.shift();u[f]=tt(i)}else if(f==="threshold"&&i[0]===":"){i.shift();u[f]=y(i,x)}else{fe(e,"htmx:syntax:error",{token:i.shift()})}}n.push(u)}}if(i.length===a){fe(e,"htmx:syntax:error",{token:i.shift()})}y(i,Je)}while(i[0]===","&&i.shift());if(r){r[t]=n}return n}function it(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Q.config.triggerSpecsCache;r=n&&n[t]||nt(e,t,n)}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,rt)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function at(e){ae(e).cancelled=true}function ot(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ct(r,e,Wt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}ot(e,t,r)}},r.pollInterval)}function st(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function lt(t,r,e){if(t.tagName==="A"&&st(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){ht(t,function(e,t){if(v(e,Q.config.disableSelector)){p(e);return}he(n,i,e,t)},r,e,true)})}}function ut(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function ft(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ct(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function ht(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=Z(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(ft(a,e)){return}if(l||ut(e,a)){e.preventDefault()}if(ct(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle>0){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay>0){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var vt=false;var dt=null;function gt(){if(!dt){dt=function(){vt=true};window.addEventListener("scroll",dt);setInterval(function(){if(vt){vt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){mt(e)})}},200)}}function mt(t){if(!o(t,"data-hx-revealed")&&X(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function pt(e,t,r){var n=D(r);for(var i=0;i=0){var t=wt(n);setTimeout(function(){xt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(yt(s)){return}var t=e.data;R(s,function(e){t=e.transformResponse(t,null,s)});var r=T(s);var n=l(t);var i=M(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(ut(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function wt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(e,t,r){var n=D(r);for(var i=0;i0){setTimeout(i,n)}else{i()}}function Ht(t,i,e){var a=false;oe(w,function(r){if(o(t,"hx-"+r)){var n=te(t,"hx-"+r);a=true;i.path=n;i.verb=r;e.forEach(function(e){Lt(t,e,i,function(e,t){if(v(e,Q.config.disableSelector)){p(e);return}he(r,n,e,t)})})}});return a}function Lt(n,e,t,r){if(e.sseEvent){Rt(n,r,e.sseEvent)}else if(e.trigger==="revealed"){gt();ht(n,r,t,e);mt(n)}else if(e.trigger==="intersect"){var i={};if(e.root){i.root=ue(n,e.root)}if(e.threshold){i.threshold=parseFloat(e.threshold)}var a=new IntersectionObserver(function(e){for(var t=0;t0){t.polling=true;ot(n,r,e)}else{ht(n,r,t,e)}}function At(e){if(Q.config.allowScriptTags&&(e.type==="text/javascript"||e.type==="module"||e.type==="")){var t=re().createElement("script");oe(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}var r=e.parentElement;try{r.insertBefore(t,e)}catch(e){b(e)}finally{if(e.parentElement){e.parentElement.removeChild(e)}}}}function Nt(e){if(h(e,"script")){At(e)}oe(f(e,"script"),function(e){At(e)})}function It(e){var t=e.attributes;for(var r=0;r0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Bt(o)}for(var l in r){Ft(e,l,r[l])}}}function jt(e){Ae(e);for(var t=0;tQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Yt(e){if(!U()){return null}e=B(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Zt();var r=T(t);var n=Ve(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Ue(t,e,r);nr(r.tasks);Jt=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function ar(e){er();e=e||location.pathname+location.search;var t=Yt(e);if(t){var r=l(t.content);var n=Zt();var i=T(n);Ue(n,r,i);nr(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);Jt=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{ir(e)}}}function or(e){var t=pe(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function sr(e){var t=pe(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function lr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ur(e,t){for(var r=0;r=0}function wr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!br(e)){n["show"]="top"}if(r){var i=D(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{b("Unknown modifier in hx-swap: "+o)}}}}return n}function Sr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function Er(t,r,n){var i=null;R(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(Sr(r)){return pr(n)}else{return mr(n)}}}function T(e){return{tasks:[],elts:[e]}}function Cr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Rr(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=Tr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return Rr(u(e),t,r,n)}function Tr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Or(e,t){return Rr(e,"hx-vars",true,t)}function qr(e,t){return Rr(e,"hx-vals",false,t)}function Hr(e){return le(Or(e),qr(e))}function Lr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Ar(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return t.test(e.getAllResponseHeaders())}function Nr(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||I(r,"String")){return he(e,t,null,null,{targetOverride:g(r),returnPromise:true})}else{return he(e,t,g(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:g(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Ir(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function kr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=s(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Mr;var X=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ye(n);if(u==null||u==me){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var D=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:D,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var m=ne(n,"hx-sync");var p=null;var x=false;if(m){var B=m.split(":");var F=B[0].trim();if(F==="this"){g=xe(n,"hx-sync")}else{g=ue(n,F)}m=(B[1]||"drop").trim();f=ae(g);if(m==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(m==="abort"){if(f.xhr){ie(o);return l}else{x=true}}else if(m==="replace"){ce(g,"htmx:abort")}else if(m.indexOf("queue")===0){var V=m.split(" ");p=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(p==null){if(i){var y=ae(i);if(y&&y.triggerSpec&&y.triggerSpec.queue){p=y.triggerSpec.queue}}if(p==null){p="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(p==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(p==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(p==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=x;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=xr(n,u,S);if(t!=="get"&&!Sr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=dr(n,t);var C=_.errors;var R=_.values;if(a.values){R=le(R,a.values)}var z=Hr(n);var $=le(R,z);var T=yr($,n);if(Q.config.getCacheBusterParam&&t==="get"){T["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=Rr(n,"hx-request");var W=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:W,useUrlParams:q,parameters:T,unfilteredParameters:$,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;T=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(T).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=mr(T);if(L){A+="#"+L}}}if(!kr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Lr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:W,select:X,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=Ir(n);I.pathInfo.responsePath=Ar(b);M(n,I);lr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=or(n);var P=sr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:Er(b,n,T);b.send(Y);return l}function Pr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Mr(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){_e(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){er();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Nr("GET",r,v).then(function(){tr(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){if(f.getResponseHeader("HX-Retarget")==="this"){u.target=l}else{u.target=ue(l,f.getResponseHeader("HX-Retarget"))}}var d=Pr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var m=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:m},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;m=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){at(l)}R(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){er()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=wr(l,s);if(v.hasOwnProperty("ignoreTitle")){m=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var p=null;var x=null;var y=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){tr(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{rr(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=T(c);je(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}_e(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!m){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}Cr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}_e(f,"HX-Trigger-After-Settle",r)}ie(p)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(x);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){p=e;x=t});var S=y;y=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(y,v.swapDelay)}else{y()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Xr={};function Dr(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Ur(e,t){if(t.init){t.init(r)}Xr[e]=le(Dr(),t)}function Br(e){delete Xr[e]}function Fr(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Xr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Fr(u(e),r,n)}var Vr=false;re().addEventListener("DOMContentLoaded",function(){Vr=true});function jr(e){if(Vr||re().readyState==="complete"){e()}else{re().addEventListener("DOMContentLoaded",e)}}function _r(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file diff --git a/public/js/htmx.sse.js b/public/js/htmx.sse.js new file mode 100644 index 0000000..943d80a --- /dev/null +++ b/public/js/htmx.sse.js @@ -0,0 +1,355 @@ +/* +Server Sent Events Extension +============================ +This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions. + +*/ + +(function() { + + /** @type {import("../htmx").HtmxInternalApi} */ + var api; + + htmx.defineExtension("sse", { + + /** + * Init saves the provided reference to the internal HTMX API. + * + * @param {import("../htmx").HtmxInternalApi} api + * @returns void + */ + init: function(apiRef) { + // store a reference to the internal API. + api = apiRef; + + // set a function in the public API for creating new EventSource objects + if (htmx.createEventSource == undefined) { + htmx.createEventSource = createEventSource; + } + }, + + /** + * onEvent handles all events passed to this extension. + * + * @param {string} name + * @param {Event} evt + * @returns void + */ + onEvent: function(name, evt) { + + switch (name) { + + case "htmx:beforeCleanupElement": + var internalData = api.getInternalData(evt.target) + // Try to remove remove an EventSource when elements are removed + if (internalData.sseEventSource) { + internalData.sseEventSource.close(); + } + + return; + + // Try to create EventSources when elements are processed + case "htmx:afterProcessNode": + ensureEventSourceOnElement(evt.target); + registerSSE(evt.target); + } + } + }); + + /////////////////////////////////////////////// + // HELPER FUNCTIONS + /////////////////////////////////////////////// + + + /** + * createEventSource is the default method for creating new EventSource objects. + * it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed. + * + * @param {string} url + * @returns EventSource + */ + function createEventSource(url) { + return new EventSource(url, { withCredentials: true }); + } + + function splitOnWhitespace(trigger) { + return trigger.trim().split(/\s+/); + } + + function getLegacySSEURL(elt) { + var legacySSEValue = api.getAttributeValue(elt, "hx-sse"); + if (legacySSEValue) { + var values = splitOnWhitespace(legacySSEValue); + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/); + if (value[0] === "connect") { + return value[1]; + } + } + } + } + + function getLegacySSESwaps(elt) { + var legacySSEValue = api.getAttributeValue(elt, "hx-sse"); + var returnArr = []; + if (legacySSEValue != null) { + var values = splitOnWhitespace(legacySSEValue); + for (var i = 0; i < values.length; i++) { + var value = values[i].split(/:(.+)/); + if (value[0] === "swap") { + returnArr.push(value[1]); + } + } + } + return returnArr; + } + + /** + * registerSSE looks for attributes that can contain sse events, right + * now hx-trigger and sse-swap and adds listeners based on these attributes too + * the closest event source + * + * @param {HTMLElement} elt + */ + function registerSSE(elt) { + // Find closest existing event source + var sourceElement = api.getClosestMatch(elt, hasEventSource); + if (sourceElement == null) { + // api.triggerErrorEvent(elt, "htmx:noSSESourceError") + return null; // no eventsource in parentage, orphaned element + } + + // Set internalData and source + var internalData = api.getInternalData(sourceElement); + var source = internalData.sseEventSource; + + // Add message handlers for every `sse-swap` attribute + queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) { + + var sseSwapAttr = api.getAttributeValue(child, "sse-swap"); + if (sseSwapAttr) { + var sseEventNames = sseSwapAttr.split(","); + } else { + var sseEventNames = getLegacySSESwaps(child); + } + + for (var i = 0; i < sseEventNames.length; i++) { + var sseEventName = sseEventNames[i].trim(); + var listener = function(event) { + + // If the source is missing then close SSE + if (maybeCloseSSESource(sourceElement)) { + return; + } + + // If the body no longer contains the element, remove the listener + if (!api.bodyContains(child)) { + source.removeEventListener(sseEventName, listener); + } + + // swap the response into the DOM and trigger a notification + swap(child, event.data); + api.triggerEvent(elt, "htmx:sseMessage", event); + }; + + // Register the new listener + api.getInternalData(child).sseEventListener = listener; + source.addEventListener(sseEventName, listener); + } + }); + + // Add message handlers for every `hx-trigger="sse:*"` attribute + queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) { + + var sseEventName = api.getAttributeValue(child, "hx-trigger"); + if (sseEventName == null) { + return; + } + + // Only process hx-triggers for events with the "sse:" prefix + if (sseEventName.slice(0, 4) != "sse:") { + return; + } + + // remove the sse: prefix from here on out + sseEventName = sseEventName.substr(4); + + var listener = function() { + if (maybeCloseSSESource(sourceElement)) { + return + } + + if (!api.bodyContains(child)) { + source.removeEventListener(sseEventName, listener); + } + } + }); + } + + /** + * ensureEventSourceOnElement creates a new EventSource connection on the provided element. + * If a usable EventSource already exists, then it is returned. If not, then a new EventSource + * is created and stored in the element's internalData. + * @param {HTMLElement} elt + * @param {number} retryCount + * @returns {EventSource | null} + */ + function ensureEventSourceOnElement(elt, retryCount) { + + if (elt == null) { + return null; + } + + // handle extension source creation attribute + queryAttributeOnThisOrChildren(elt, "sse-connect").forEach(function(child) { + var sseURL = api.getAttributeValue(child, "sse-connect"); + if (sseURL == null) { + return; + } + + ensureEventSource(child, sseURL, retryCount); + }); + + // handle legacy sse, remove for HTMX2 + queryAttributeOnThisOrChildren(elt, "hx-sse").forEach(function(child) { + var sseURL = getLegacySSEURL(child); + if (sseURL == null) { + return; + } + + ensureEventSource(child, sseURL, retryCount); + }); + + } + + function ensureEventSource(elt, url, retryCount) { + var source = htmx.createEventSource(url); + + source.onerror = function(err) { + + // Log an error event + api.triggerErrorEvent(elt, "htmx:sseError", { error: err, source: source }); + + // If parent no longer exists in the document, then clean up this EventSource + if (maybeCloseSSESource(elt)) { + return; + } + + // Otherwise, try to reconnect the EventSource + if (source.readyState === EventSource.CLOSED) { + retryCount = retryCount || 0; + var timeout = Math.random() * (2 ^ retryCount) * 500; + window.setTimeout(function() { + ensureEventSourceOnElement(elt, Math.min(7, retryCount + 1)); + }, timeout); + } + }; + + source.onopen = function(evt) { + api.triggerEvent(elt, "htmx:sseOpen", { source: source }); + } + + api.getInternalData(elt).sseEventSource = source; + } + + /** + * maybeCloseSSESource confirms that the parent element still exists. + * If not, then any associated SSE source is closed and the function returns true. + * + * @param {HTMLElement} elt + * @returns boolean + */ + function maybeCloseSSESource(elt) { + if (!api.bodyContains(elt)) { + var source = api.getInternalData(elt).sseEventSource; + if (source != undefined) { + source.close(); + // source = null + return true; + } + } + return false; + } + + /** + * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT. + * + * @param {HTMLElement} elt + * @param {string} attributeName + */ + function queryAttributeOnThisOrChildren(elt, attributeName) { + + var result = []; + + // If the parent element also contains the requested attribute, then add it to the results too. + if (api.hasAttribute(elt, attributeName)) { + result.push(elt); + } + + // Search all child nodes that match the requested attribute + elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "]").forEach(function(node) { + result.push(node); + }); + + return result; + } + + /** + * @param {HTMLElement} elt + * @param {string} content + */ + function swap(elt, content) { + + api.withExtensions(elt, function(extension) { + content = extension.transformResponse(content, null, elt); + }); + + var swapSpec = api.getSwapSpecification(elt); + var target = api.getTarget(elt); + var settleInfo = api.makeSettleInfo(elt); + + api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo); + + settleInfo.elts.forEach(function(elt) { + if (elt.classList) { + elt.classList.add(htmx.config.settlingClass); + } + api.triggerEvent(elt, 'htmx:beforeSettle'); + }); + + // Handle settle tasks (with delay if requested) + if (swapSpec.settleDelay > 0) { + setTimeout(doSettle(settleInfo), swapSpec.settleDelay); + } else { + doSettle(settleInfo)(); + } + } + + /** + * doSettle mirrors much of the functionality in htmx that + * settles elements after their content has been swapped. + * TODO: this should be published by htmx, and not duplicated here + * @param {import("../htmx").HtmxSettleInfo} settleInfo + * @returns () => void + */ + function doSettle(settleInfo) { + + return function() { + settleInfo.tasks.forEach(function(task) { + task.call(); + }); + + settleInfo.elts.forEach(function(elt) { + if (elt.classList) { + elt.classList.remove(htmx.config.settlingClass); + } + api.triggerEvent(elt, 'htmx:afterSettle'); + }); + } + } + + function hasEventSource(node) { + return api.getInternalData(node).sseEventSource != null; + } + +})(); diff --git a/stylegen/base.css b/stylegen/base.css new file mode 100644 index 0000000..fcddca8 --- /dev/null +++ b/stylegen/base.css @@ -0,0 +1,12 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +container, +body { + height: 100%; + width: 100%; + overflow-y: auto; + position: fixed; +} \ No newline at end of file diff --git a/stylegen/bun.lockb b/stylegen/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..12542b6e0ee304c6a9aa99eb92d7dc6534f084d9 GIT binary patch literal 43927 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