From ee15fdf867a3738f34668249788b743aad5771b2 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 13:53:10 +0100 Subject: [PATCH 01/20] add goReleaser --- .gitignore | 3 +++ .goreleaser.yaml | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .goreleaser.yaml diff --git a/.gitignore b/.gitignore index 99e666a..6474f31 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ bin node_modules/ package-lock.json package.json + +# Added by goreleaser init: +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..db691e2 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,47 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - main: ./cmd/survey + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + +archives: + - formats: [tar.gz] + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + formats: [zip] + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" From b2b8c5edcd1c9ec8871ed34839daa6182891a625 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 14:18:37 +0100 Subject: [PATCH 02/20] add dockerignore --- .dockerignore | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..720f857 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +.pm +.git +bin + +.idea +.vscode + +node_modules/ +package-lock.json +package.json + +.gitignore +.goreleaser.yaml + +*.db +*.ext + +Makefile + +# Added by goreleaser init: +dist/ From a0535085a0ea4f70450ea023b24dd4fc18b84790 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:22:08 +0100 Subject: [PATCH 03/20] Add GitHub Actions workflow for Go project --- .github/workflows/go.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..0b443f3 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Go + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... From 8a2c6582e4921709cad6f97c0657bab0f80ea44e Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:23:09 +0100 Subject: [PATCH 04/20] Update Go version to 1.25.6 in workflow --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0b443f3..74d111b 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.25.6' - name: Build run: go build -v ./... From 8dcf97c6c578036f42eec265513708ed509ffaa4 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:26:16 +0100 Subject: [PATCH 05/20] Add Apache License 2.0 to the project --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 133996c73451cae94b8e0ce50389a7dbdd4847bf Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:28:39 +0100 Subject: [PATCH 06/20] Update copyright year and owner in LICENSE file --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 261eeb9..8e13446 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 LazyBachelor Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From c1b34a145d038a59a2a4b92372966fef8fc17edb Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:31:17 +0100 Subject: [PATCH 07/20] Add Docker publish workflow to GitHub Actions --- .github/workflows/docker-publish.yml | 98 ++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..c63a19f --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,98 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + schedule: + - cron: '45 15 * * *' + push: + branches: [ "main" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "main" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 + with: + cosign-release: 'v2.2.4' + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} From 4f30744314b4439096c90eb1e5b347e13d3bc04e Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 14:39:42 +0100 Subject: [PATCH 08/20] add goreleaser action --- .github/workflows/goreleaser.yml | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/goreleaser.yml diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml new file mode 100644 index 0000000..b8192bc --- /dev/null +++ b/.github/workflows/goreleaser.yml @@ -0,0 +1,35 @@ +name: goreleaser + +on: + push: + tags: + - '*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v6 + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v7 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + # 'latest', 'nightly', or a semver + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution + # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file From dbf619a0548446ed22d6f53f5072182cc20dd67f Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:49:15 +0100 Subject: [PATCH 09/20] Update Go version to 1.25.6 in workflow --- .github/workflows/goreleaser.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index b8192bc..b05894e 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -20,6 +20,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 + with: + go-version: '1.25.6' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 @@ -32,4 +34,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution - # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file + # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} From 118e67dbf4c03181f90a2ae4ce1c9f4366838684 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 16:16:09 +0100 Subject: [PATCH 10/20] refactor services to use Interface and change name to app --- cmd/pm/main.go | 12 ++----- cmd/survey/init.go | 14 ++++---- cmd/survey/tasks/base.go | 13 +++---- cmd/survey/tasks/codingTask.go | 11 +++--- cmd/survey/tasks/createIssue.go | 14 ++++---- cmd/tui/main.go | 13 ++----- cmd/web/main.go | 16 ++------- internal/models/issue.go | 5 +-- internal/service/config.go | 8 +++++ internal/service/interfaces.go | 28 +++++++++++++++ internal/service/service.go | 23 +++++------- pkg/cli/cli.go | 16 ++++----- pkg/cli/commands/close.go | 6 ++-- pkg/cli/commands/completion.go | 5 +-- pkg/cli/commands/create.go | 3 +- pkg/cli/commands/delete.go | 11 +++--- pkg/cli/commands/ls.go | 3 +- pkg/cli/commands/read.go | 3 +- pkg/cli/commands/root.go | 32 +++++++++++++---- pkg/cli/commands/update.go | 8 +++-- pkg/cli/repl/repl.go | 8 ++--- pkg/task/register.go | 8 ++--- pkg/task/runner.go | 13 ++++--- pkg/task/types.go | 6 ++-- pkg/tui/tui.go | 8 ++--- pkg/tui/views/dashboard/issue_list.go | 12 +++---- pkg/tui/views/dashboard/model.go | 52 +++++++++++++-------------- pkg/tui/views/dashboard/operations.go | 44 +++++++++++------------ pkg/tui/views/views.go | 4 +-- pkg/web/handler/comments.go | 4 +-- pkg/web/handler/context.go | 12 +++---- pkg/web/handler/index.go | 4 +-- pkg/web/handler/issues.go | 24 ++++++------- pkg/web/server/routes.go | 2 +- pkg/web/server/server.go | 6 ++-- pkg/web/web.go | 15 ++++---- 36 files changed, 247 insertions(+), 219 deletions(-) create mode 100644 internal/service/interfaces.go diff --git a/cmd/pm/main.go b/cmd/pm/main.go index d067ef4..3ac39fd 100644 --- a/cmd/pm/main.go +++ b/cmd/pm/main.go @@ -3,20 +3,12 @@ package main import ( "context" + "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/cli" ) func main() { - config := cli.CLIConfig{ - RootCmd: "pm", - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - - cli := cli.NewCli() - - if err := cli.Run(context.Background(), config); err != nil { + if err := cli.NewCli().Run(context.Background(), service.BaseConfig); err != nil { return } } diff --git a/cmd/survey/init.go b/cmd/survey/init.go index 6016132..6587a51 100644 --- a/cmd/survey/init.go +++ b/cmd/survey/init.go @@ -14,19 +14,19 @@ import ( ) func init() { - task.Register("create_issue", func(svc *service.Services) task.Tasker { - return tasks.NewCreateIssueTask(svc) + task.Register("create_issue", func(app *service.App) task.Tasker { + return tasks.NewCreateIssueTask(app) }) - task.Register("coding_task", func(svc *service.Services) task.Tasker { - return tasks.NewCodingTask(svc) + task.Register("coding_task", func(app *service.App) task.Tasker { + return tasks.NewCodingTask(app) }) } -func initTasks(svc *service.Services) []task.Tasker { +func initTasks(app *service.App) []task.Tasker { var taskers []task.Tasker for _, name := range task.List() { - t, err := task.Get(name, svc) + t, err := task.Get(name, app) if err != nil { continue } @@ -36,7 +36,7 @@ func initTasks(svc *service.Services) []task.Tasker { return taskers } -func initializeServices(ctx context.Context) (*service.Services, func(), error) { +func initializeServices(ctx context.Context) (*service.App, func(), error) { return service.NewServices(ctx, tasks.BaseConfig()) } diff --git a/cmd/survey/tasks/base.go b/cmd/survey/tasks/base.go index 1550bd9..ccc8bbe 100644 --- a/cmd/survey/tasks/base.go +++ b/cmd/survey/tasks/base.go @@ -38,17 +38,12 @@ func BaseDetails() taskui.TaskDetails { } } -func BaseConfig() task.TaskConfig { - return task.TaskConfig{ - IssuePrefix: "pm", - WebAddress: ":8080", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } +func BaseConfig() task.Config { + return service.BaseConfig } -func ClearIssues(svc *service.Services) error { - return svc.Beads.DeleteIssues() +func ClearIssues(app *service.App) error { + return app.Issues.DeleteIssues() } func BaseQuestions(interfaceType task.InterfaceType) taskui.Questions { diff --git a/cmd/survey/tasks/codingTask.go b/cmd/survey/tasks/codingTask.go index 9d3110d..520e7ef 100644 --- a/cmd/survey/tasks/codingTask.go +++ b/cmd/survey/tasks/codingTask.go @@ -23,14 +23,14 @@ Please write your code below this line! ` type CodingTask struct { - svc *service.Services + app *service.App } -func NewCodingTask(svc *service.Services) *CodingTask { - return &CodingTask{svc: svc} +func NewCodingTask(app *service.App) *CodingTask { + return &CodingTask{app: app} } -func (t *CodingTask) Config() task.TaskConfig { +func (t *CodingTask) Config() task.Config { return BaseConfig().WithStatisticsStoragePath("./.pm/coding-task-stats.json") } @@ -59,7 +59,7 @@ func (t *CodingTask) Questions(interfaceType task.InterfaceType) (questions task } func (t *CodingTask) Setup(ctx context.Context) error { - if err := ClearIssues(t.svc); err != nil { + if err := ClearIssues(t.app); err != nil { return err } @@ -71,7 +71,6 @@ func (t *CodingTask) Setup(ctx context.Context) error { } func (t *CodingTask) Validate(ctx context.Context) (bool, error) { - file, err := os.ReadFile("./code.txt") if err != nil { return false, err diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go index f6552d1..ee96e55 100644 --- a/cmd/survey/tasks/createIssue.go +++ b/cmd/survey/tasks/createIssue.go @@ -17,14 +17,14 @@ Assign this task to yourself and start creating the issue. Make sure to fill out all the necessary details, including the title, description, and assignee.` type CreateIssueTask struct { - svc *service.Services + app *service.App } -func NewCreateIssueTask(svc *service.Services) *CreateIssueTask { - return &CreateIssueTask{svc: svc} +func NewCreateIssueTask(app *service.App) *CreateIssueTask { + return &CreateIssueTask{app: app} } -func (t *CreateIssueTask) Config() task.TaskConfig { +func (t *CreateIssueTask) Config() task.Config { return BaseConfig().WithStatisticsStoragePath("./.pm/create-issue-stats.json") } @@ -38,18 +38,18 @@ func (t *CreateIssueTask) Questions(interfaceType task.InterfaceType) taskui.Que func (t *CreateIssueTask) Setup(ctx context.Context) error { // Clear existing issues to ensure a clean state for the task - if err := ClearIssues(t.svc); err != nil { + if err := ClearIssues(t.app); err != nil { return err } issue := models.NewBaseIssue(). WithTitle("Create a New Issue").WithDescription(description).Build() - return t.svc.Beads.CreateIssue(ctx, &issue, "") + return t.app.Issues.CreateIssue(ctx, &issue, "") } func (t *CreateIssueTask) Validate(ctx context.Context) (bool, error) { - issues, err := t.svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) + issues, err := t.app.Issues.SearchIssues(ctx, "", models.IssueFilter{}) if err != nil { return false, err } diff --git a/cmd/tui/main.go b/cmd/tui/main.go index 824e6aa..b381675 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -3,19 +3,12 @@ package main import ( "context" + "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/tui" ) func main() { - config := tui.TUIConfig{ - StatisticsStoragePath: "./.pm/stats.json", - BeadsDBPath: "./.pm/db.db", - IssuePrefix: "pm", - } - - tui := tui.NewTui() - - if err := tui.Run(context.Background(), config); err != nil { - panic(err) + if err := tui.NewTui().Run(context.Background(), service.BaseConfig); err != nil { + return } } diff --git a/cmd/web/main.go b/cmd/web/main.go index 1cb7518..5bdc103 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -2,25 +2,13 @@ package main import ( "context" - "fmt" - "os" "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/web" ) func main() { - web := web.NewWeb() - - config := service.Config{ - WebAddress: "localhost:8080", - BeadsDBPath: "./.pm/db.db", - IssuePrefix: "pm", - StatisticsStoragePath: "./.pm/stats.json", - } - - if err := web.Run(context.Background(), config); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) + if err := web.NewWeb().Run(context.Background(), service.BaseConfig); err != nil { + return } } diff --git a/internal/models/issue.go b/internal/models/issue.go index e751bfa..813f6ac 100644 --- a/internal/models/issue.go +++ b/internal/models/issue.go @@ -18,8 +18,9 @@ func NewBaseIssue() *IssueBuilder { WithID("pm-abc"). WithTitle("Basic Issue"). WithDescription("Basic Description"). - WithIssueType(TypeTask). - WithStatus(StatusOpen) + WithStatus(StatusOpen). + WithIssueType(TypeTask) + } func (b *IssueBuilder) WithID(id string) *IssueBuilder { diff --git a/internal/service/config.go b/internal/service/config.go index 7532ab5..c5cc627 100644 --- a/internal/service/config.go +++ b/internal/service/config.go @@ -8,6 +8,14 @@ type Config struct { StatisticsStoragePath string } +var BaseConfig = Config{ + RootCmd: "pm", + IssuePrefix: "pm", + WebAddress: ":8080", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", +} + func (c Config) WithRootCmd(rootCmd string) Config { c.RootCmd = rootCmd return c diff --git a/internal/service/interfaces.go b/internal/service/interfaces.go new file mode 100644 index 0000000..33095ba --- /dev/null +++ b/internal/service/interfaces.go @@ -0,0 +1,28 @@ +package service + +import ( + "context" + "log/slog" + + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/steveyegge/beads" +) + +type App struct { + Config Config + Issues IssueService + Stats StatsService + Logger *slog.Logger +} + +type IssueService interface { + beads.Storage + AllIssues(ctx context.Context) ([]models.Issue, error) + DeleteIssues() error +} + +type StatsService interface { + Load(ctx context.Context) error + Save(ctx context.Context) error + GetStatistics() (models.Statistics, error) +} diff --git a/internal/service/service.go b/internal/service/service.go index 009f3a5..72d72d1 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -2,8 +2,8 @@ package service import ( "context" - "database/sql" "fmt" + "log/slog" "os" "time" @@ -15,14 +15,7 @@ import ( "github.com/steveyegge/beads" ) -type Services struct { - Config Config - DB *sql.DB - Beads *BeadsService - Statistics *StatisticsService -} - -func NewServices(ctx context.Context, config Config) (*Services, func(), error) { +func NewServices(ctx context.Context, config Config) (*App, func(), error) { var cleanupFuncs []func() if !initialized(config.BeadsDBPath) { @@ -47,16 +40,18 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) StartTime: time.Now(), }) + logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + statSvc, err := NewStatisticsService(statStore) if err != nil { return nil, nil, err } - return &Services{ - DB: beadsSvc.UnderlyingDB(), - Beads: beadsSvc, - Statistics: statSvc, - Config: config, + return &App{ + Issues: beadsSvc, + Stats: statSvc, + Config: config, + Logger: logger, }, func() { runCleanup(cleanupFuncs) }, nil } diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 27dd22a..420e37d 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -8,8 +8,8 @@ import ( "github.com/LazyBachelor/LazyPM/pkg/cli/commands" ) -// CLIConfig is an alias for service.Config, used to configure the CLI. -type CLIConfig = service.Config +// Config is an alias for service.Config, used to configure the CLI. +type Config = service.Config type CLI struct{} @@ -18,15 +18,15 @@ func NewCli() *CLI { } // Run initializes the services and executes the CLI commands. -func (c *CLI) Run(ctx context.Context, config CLIConfig) error { - svc, cleanup, err := service.NewServices(ctx, config) +func (c *CLI) Run(ctx context.Context, config Config) error { + app, cleanup, err := service.NewServices(ctx, config) if err != nil { return err } defer cleanup() - commands.SetServices(svc) + commands.SetApp(app) if err := commands.Execute(); err != nil { return err @@ -36,15 +36,15 @@ func (c *CLI) Run(ctx context.Context, config CLIConfig) error { } // RunWithArgs initializes the services and executes the CLI commands with the provided arguments. -func (c *CLI) RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { - svc, cleanup, err := service.NewServices(ctx, config) +func (c *CLI) RunWithArgs(ctx context.Context, config Config, args []string) error { + app, cleanup, err := service.NewServices(ctx, config) if err != nil { return err } defer cleanup() - commands.SetServices(svc) + commands.SetApp(app) if err := commands.ExecuteArgs(args); err != nil { return err diff --git a/pkg/cli/commands/close.go b/pkg/cli/commands/close.go index b4ff882..2bafcbb 100644 --- a/pkg/cli/commands/close.go +++ b/pkg/cli/commands/close.go @@ -30,8 +30,10 @@ func runCloseCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("issue ID cannot be empty") } + app := AppFromContext(cmd.Context()) + // Fetch the issue to ensure it exists before closing. - issue, err := svc.Beads.GetIssue(cmd.Context(), closeID) + issue, err := app.Issues.GetIssue(cmd.Context(), closeID) if err != nil { return fmt.Errorf("error fetching issue: %w", err) } @@ -47,7 +49,7 @@ func runCloseCmd(cmd *cobra.Command, args []string) error { } // Close the issue. - err = svc.Beads.CloseIssue(cmd.Context(), closeID, issue.CloseReason, "", "") + err = app.Issues.CloseIssue(cmd.Context(), closeID, issue.CloseReason, "", "") if err != nil { return fmt.Errorf("error closing issue: %w", err) } diff --git a/pkg/cli/commands/completion.go b/pkg/cli/commands/completion.go index 77d3fcf..e164585 100644 --- a/pkg/cli/commands/completion.go +++ b/pkg/cli/commands/completion.go @@ -34,11 +34,12 @@ func completeIssues(cmd *cobra.Command, args []string, toComplete string) ([]str // GetIssueCompletions fetches issues matching the toComplete string for shell completion. func GetIssueCompletions(ctx context.Context, toComplete string) ([]models.Issue, cobra.ShellCompDirective) { - if svc == nil { + app := AppFromContext(ctx) + if app == nil { return nil, cobra.ShellCompDirectiveNoFileComp } - issues, err := svc.Beads.AllIssues(ctx) + issues, err := app.Issues.AllIssues(ctx) if err != nil { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/pkg/cli/commands/create.go b/pkg/cli/commands/create.go index 538ed27..a270f03 100644 --- a/pkg/cli/commands/create.go +++ b/pkg/cli/commands/create.go @@ -54,7 +54,8 @@ func runCreateCmd(cmd *cobra.Command, args []string) error { } // Create the issue using the service layer. - err := svc.Beads.CreateIssue(cmd.Context(), issue, "test_actor") + app := AppFromContext(cmd.Context()) + err := app.Issues.CreateIssue(cmd.Context(), issue, "test_actor") if err != nil { return fmt.Errorf("error creating issue: %w", err) } diff --git a/pkg/cli/commands/delete.go b/pkg/cli/commands/delete.go index b96e659..0b66590 100644 --- a/pkg/cli/commands/delete.go +++ b/pkg/cli/commands/delete.go @@ -46,8 +46,10 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("issue ID cannot be empty") } + app := AppFromContext(cmd.Context()) + // Fetch the issue to ensure it exists before deletion. - issue, err := svc.Beads.GetIssue(cmd.Context(), deleteID) + issue, err := app.Issues.GetIssue(cmd.Context(), deleteID) if err != nil { return fmt.Errorf("error fetching issue: %w", err) } @@ -70,7 +72,7 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error { } // Delete the issue. - err = svc.Beads.DeleteIssue(cmd.Context(), deleteID) + err = app.Issues.DeleteIssue(cmd.Context(), deleteID) if err != nil { return fmt.Errorf("error deleting issue: %w", err) } @@ -83,9 +85,10 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error { // runDeleteInteractive runs the interactive mode for deleting issues, // allowing users to select multiple issues for deletion. func runDeleteInteractive(ctx context.Context) error { + app := AppFromContext(ctx) options := []huh.Option[string]{} - issues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) + issues, err := app.Issues.SearchIssues(ctx, "", models.IssueFilter{}) if err != nil { return fmt.Errorf("error fetching issues: %w", err) } @@ -110,7 +113,7 @@ func runDeleteInteractive(ctx context.Context) error { } for _, id := range deleteIDs { - err := svc.Beads.DeleteIssue(ctx, id) + err := app.Issues.DeleteIssue(ctx, id) if err != nil { return fmt.Errorf("error deleting issue with ID %s: %w", id, err) } diff --git a/pkg/cli/commands/ls.go b/pkg/cli/commands/ls.go index bd65899..1eb13cc 100644 --- a/pkg/cli/commands/ls.go +++ b/pkg/cli/commands/ls.go @@ -55,7 +55,8 @@ func runGetIssuesCmd(cmd *cobra.Command, args []string) error { } // Fetch issues based on the search query and filters. - issuesPtr, err := svc.Beads.SearchIssues(cmd.Context(), queryArg, filter) + app := AppFromContext(cmd.Context()) + issuesPtr, err := app.Issues.SearchIssues(cmd.Context(), queryArg, filter) if err != nil { return err } diff --git a/pkg/cli/commands/read.go b/pkg/cli/commands/read.go index 4ea6a54..40c8c8c 100644 --- a/pkg/cli/commands/read.go +++ b/pkg/cli/commands/read.go @@ -24,7 +24,8 @@ func runGetCmd(cmd *cobra.Command, args []string) error { issueID := args[0] // Fetch the issue details using the service layer. - issuePtr, err := svc.Beads.GetIssue(cmd.Context(), issueID) + app := AppFromContext(cmd.Context()) + issuePtr, err := app.Issues.GetIssue(cmd.Context(), issueID) if err != nil { return err } diff --git a/pkg/cli/commands/root.go b/pkg/cli/commands/root.go index 9e18856..45b1c45 100644 --- a/pkg/cli/commands/root.go +++ b/pkg/cli/commands/root.go @@ -10,9 +10,12 @@ import ( "github.com/spf13/cobra" ) -// svc is a global variable that holds beads, config and stats services. -// Must be called before executing any commands to ensure services are available. -var svc *service.Services +type contextKey string + +const appKey contextKey = "app" + +// app is a package-level variable used during command setup +var app *service.App // Flags struct to hold command-line flag values for issues. type Flags struct { @@ -30,13 +33,28 @@ type Flags struct { var rootCmd = &cobra.Command{ Short: "Project Management CLI", Long: `Project Management CLI for managing issues and tasks.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // Inject app into context for all commands + if app != nil { + cmd.SetContext(context.WithValue(cmd.Context(), appKey, app)) + } + }, } -// SetServices sets the global services variable for use in command execution. +// SetApp sets the app variable for use in command execution. // Must be called before executing any commands to ensure services are available. -func SetServices(services *service.Services) { - svc = services - rootCmd.Use = svc.Config.RootCmd +func SetApp(application *service.App) { + app = application + rootCmd.Use = app.Config.RootCmd +} + +// AppFromContext retrieves the App from the command context +func AppFromContext(ctx context.Context) *service.App { + if a, ok := ctx.Value(appKey).(*service.App); ok { + return a + } + // Fallback to package-level app (for testing or edge cases) + return app } // Execute executes the root command using the fang library. diff --git a/pkg/cli/commands/update.go b/pkg/cli/commands/update.go index 099a08c..f26b23e 100644 --- a/pkg/cli/commands/update.go +++ b/pkg/cli/commands/update.go @@ -23,7 +23,9 @@ var updateCmd = &cobra.Command{ func runUpdateCmd(cmd *cobra.Command, args []string) error { issueID := args[0] - issue, err := svc.Beads.GetIssue(cmd.Context(), issueID) + app := AppFromContext(cmd.Context()) + + issue, err := app.Issues.GetIssue(cmd.Context(), issueID) if err != nil { return fmt.Errorf("error getting issue: %w", err) } @@ -37,12 +39,12 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error { return fmt.Errorf("error getting update values: %w", err) } - err = svc.Beads.UpdateIssue(cmd.Context(), issueID, updates, "test_actor") + err = app.Issues.UpdateIssue(cmd.Context(), issueID, updates, "test_actor") if err != nil { return fmt.Errorf("error updating issue: %w", err) } - updatedIssue, err := svc.Beads.GetIssue(cmd.Context(), issueID) + updatedIssue, err := app.Issues.GetIssue(cmd.Context(), issueID) if err != nil { return fmt.Errorf("error getting updated issue: %w", err) } diff --git a/pkg/cli/repl/repl.go b/pkg/cli/repl/repl.go index 1133586..ff49153 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/cli/repl/repl.go @@ -37,7 +37,7 @@ func NewRepl() *REPL { } // Run starts the interactive Read-Eval-Print Loop for the PM CLI. -func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error { +func (r *REPL) Run(ctx context.Context, config cli.Config) error { // Set terminal to raw mode to capture input properly in the REPL. // This allows us to handle input character by character and provide a better user experience. // We also ensure that the terminal state is restored when the REPL exits, even if an error occurs. @@ -48,14 +48,14 @@ func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error { defer term.Restore(int(os.Stdin.Fd()), oldState) // Initialize services for beads, config and stats. - svc, cleanup, err := service.NewServices(ctx, config) + app, cleanup, err := service.NewServices(ctx, config) if err != nil { return fmt.Errorf("failed to initialize services: %w", err) } defer cleanup() - // Make sure to set services, to ensure they are available. - commands.SetServices(svc) + // Make sure to set app, to ensure they are available. + commands.SetApp(app) // Set the REPL instance so status command can access it commands.SetRepl(r) diff --git a/pkg/task/register.go b/pkg/task/register.go index 7fda855..4f57ae4 100644 --- a/pkg/task/register.go +++ b/pkg/task/register.go @@ -6,21 +6,21 @@ import ( "github.com/LazyBachelor/LazyPM/internal/service" ) -var registry = make(map[string]func(*service.Services) Tasker) +var registry = make(map[string]func(*service.App) Tasker) -func Register(name string, constructor func(*service.Services) Tasker) { +func Register(name string, constructor func(*service.App) Tasker) { if _, exists := registry[name]; exists { panic(fmt.Sprintf("task %q already registered", name)) } registry[name] = constructor } -func Get(name string, svc *service.Services) (Tasker, error) { +func Get(name string, app *service.App) (Tasker, error) { constructor, ok := registry[name] if !ok { return nil, fmt.Errorf("task %q not found", name) } - return constructor(svc), nil + return constructor(app), nil } func List() []string { diff --git a/pkg/task/runner.go b/pkg/task/runner.go index 37246f9..d060cdd 100644 --- a/pkg/task/runner.go +++ b/pkg/task/runner.go @@ -94,14 +94,13 @@ func startValidationLoop(ctx context.Context, t Tasker, feedbackChan chan Valida feedbackChan <- feedback doneChan <- true return - } else { - if err != nil { - feedback.Message = err.Error() - } else { - feedback.Message = "Task not yet complete" - } - feedbackChan <- feedback } + if err != nil { + feedback.Message = err.Error() + } else { + feedback.Message = "Task not yet complete" + } + feedbackChan <- feedback case <-quitChan: return case <-ctx.Done(): diff --git a/pkg/task/types.go b/pkg/task/types.go index abffe0e..085d3bb 100644 --- a/pkg/task/types.go +++ b/pkg/task/types.go @@ -8,15 +8,15 @@ import ( taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui" ) -type TaskConfig = service.Config +type Config = service.Config type InterfaceType string type Interface interface { - Run(context.Context, TaskConfig) error + Run(context.Context, Config) error } type Tasker interface { - Config() TaskConfig + Config() Config Details() taskui.TaskDetails Questions(InterfaceType) taskui.Questions Setup(context.Context) error diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 2c8f501..25c08c8 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -9,7 +9,7 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -type TUIConfig = service.Config +type Config = service.Config type Tui struct { feedbackChan chan task.ValidationFeedback @@ -20,15 +20,15 @@ func NewTui() *Tui { return &Tui{} } -func (t *Tui) Run(ctx context.Context, config TUIConfig) error { - svc, cleanup, err := service.NewServices(ctx, config) +func (t *Tui) Run(ctx context.Context, config Config) error { + app, cleanup, err := service.NewServices(ctx, config) if err != nil { return err } defer cleanup() - p := tea.NewProgram(views.NewDashboardView(svc, t.feedbackChan, t.quitChan), + p := tea.NewProgram(views.NewDashboardView(app, t.feedbackChan, t.quitChan), tea.WithAltScreen(), tea.WithMouseAllMotion()) if t.quitChan != nil { diff --git a/pkg/tui/views/dashboard/issue_list.go b/pkg/tui/views/dashboard/issue_list.go index 2ea4d80..f3f5dbc 100644 --- a/pkg/tui/views/dashboard/issue_list.go +++ b/pkg/tui/views/dashboard/issue_list.go @@ -15,7 +15,7 @@ import ( type IssueList struct { list list.Model - svc *service.Services + app *service.App width int height int } @@ -74,8 +74,8 @@ func renderHeaders(cols []TableColumn) string { return lipgloss.JoinHorizontal(lipgloss.Left, parts...) } -func NewIssueList(svc *service.Services, width, height int) IssueList { - issues, err := svc.Beads.AllIssues(context.Background()) +func NewIssueList(app *service.App, width, height int) IssueList { + issues, err := app.Issues.AllIssues(context.Background()) if err != nil { return IssueList{} } @@ -102,13 +102,13 @@ func NewIssueList(svc *service.Services, width, height int) IssueList { return IssueList{ list: l, - svc: svc, + app: app, width: width, height: height, } } -func NewIssueListFromIssues(svc *service.Services, issues []models.Issue, width, height int) IssueList { +func NewIssueListFromIssues(app *service.App, issues []models.Issue, width, height int) IssueList { // for making an IssueList from a pre-existing list of issues. listIssues := make([]list.Item, len(issues)) for i, issue := range issues { @@ -125,7 +125,7 @@ func NewIssueListFromIssues(svc *service.Services, issues []models.Issue, width, l.FilterInput.Prompt = "🔍 " return IssueList{ list: l, - svc: svc, + app: app, width: width, height: height, } diff --git a/pkg/tui/views/dashboard/model.go b/pkg/tui/views/dashboard/model.go index 6a925b6..65fc34a 100644 --- a/pkg/tui/views/dashboard/model.go +++ b/pkg/tui/views/dashboard/model.go @@ -15,21 +15,21 @@ type ValidationFeedbackMsg struct { } type Model struct { - header Header - issueList IssueList - issueDetail IssueDetail - closedIssueList IssueList - helpBar HelpBar - keyMap DashboardKeyMap - svc *service.Services - width int - height int - focusedWindow int // 0 = main (display issues), 1 = closed issues - focusedPaneMain int // 0 = list, 1 = detail + header Header + issueList IssueList + issueDetail IssueDetail + closedIssueList IssueList + helpBar HelpBar + keyMap DashboardKeyMap + app *service.App + width int + height int + focusedWindow int // 0 = main (display issues), 1 = closed issues + focusedPaneMain int // 0 = list, 1 = detail focusedPaneClosed int - editingTitle bool // true while we are editing a title - titleInput textinput.Model - editingIssueID string + editingTitle bool // true while we are editing a title + titleInput textinput.Model + editingIssueID string editingDescription bool // true while editing a description descriptionInput textarea.Model @@ -41,32 +41,32 @@ type Model struct { deleteConfirmID string deleteConfirmIndex int - choosingStatus bool - statusIssueID string + choosingStatus bool + statusIssueID string feedbackChan chan task.ValidationFeedback quitChan chan bool currentFeedback task.ValidationFeedback showComplete bool } -func NewDashboard(svc *service.Services, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *Model { +func NewDashboard(app *service.App, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *Model { m := &Model{ header: NewHeader("Project Manager Dashboard"), keyMap: defaultDashboardKeyMap, - svc: svc, + app: app, width: 80, height: 24, - focusedWindow: 0, - focusedPaneMain: 0, - focusedPaneClosed: 0, - feedbackChan: feedbackChan, - quitChan: quitChan, + focusedWindow: 0, + focusedPaneMain: 0, + focusedPaneClosed: 0, + feedbackChan: feedbackChan, + quitChan: quitChan, } - allIssues, _ := svc.Beads.AllIssues(context.Background()) - m.issueList = NewIssueListFromIssues(svc, OpenAndInProgressOnly(allIssues), 0, 0) + allIssues, _ := app.Issues.AllIssues(context.Background()) + m.issueList = NewIssueListFromIssues(app, OpenAndInProgressOnly(allIssues), 0, 0) m.issueDetail = NewIssueDetail() - m.closedIssueList = NewIssueListFromIssues(svc, ClosedOnly(allIssues), 0, 0) + m.closedIssueList = NewIssueListFromIssues(app, ClosedOnly(allIssues), 0, 0) m.helpBar = NewHelpBar(m.keyMap) ti := textinput.New() diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go index 47e9387..a0a3d5d 100644 --- a/pkg/tui/views/dashboard/operations.go +++ b/pkg/tui/views/dashboard/operations.go @@ -39,45 +39,45 @@ type issueDeletedMsg struct { PreviousIndex int } -func updateIssueTitleCmd(svc *service.Services, issueID, newTitle string) tea.Cmd { +func updateIssueTitleCmd(app *service.App, issueID, newTitle string) tea.Cmd { return func() tea.Msg { updates := map[string]interface{}{"title": newTitle} - err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui") + err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui") return issueTitleUpdatedMsg{IssueID: issueID, Err: err} } } -func updateIssueDescriptionCmd(svc *service.Services, issueID, newDescription string) tea.Cmd { +func updateIssueDescriptionCmd(app *service.App, issueID, newDescription string) tea.Cmd { return func() tea.Msg { updates := map[string]interface{}{"description": newDescription} - err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui") + err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui") return issueDescriptionUpdatedMsg{IssueID: issueID, Err: err} } } -func updateIssueStatusCmd(svc *service.Services, issueID, status string) tea.Cmd { +func updateIssueStatusCmd(app *service.App, issueID, status string) tea.Cmd { return func() tea.Msg { updates := map[string]interface{}{"status": status} - err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui") + err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui") return issueStatusUpdatedMsg{IssueID: issueID, Err: err} } } -func createIssueCmd(svc *service.Services, title string) tea.Cmd { +func createIssueCmd(app *service.App, title string) tea.Cmd { return func() tea.Msg { issue := &models.Issue{ Title: title, Status: models.StatusOpen, IssueType: models.TypeTask, } - err := svc.Beads.CreateIssue(context.Background(), issue, "tui") + err := app.Issues.CreateIssue(context.Background(), issue, "tui") return issueCreatedMsg{Issue: issue, Err: err} } } -func deleteIssueCmd(svc *service.Services, issueID string, currentIndex int) tea.Cmd { +func deleteIssueCmd(app *service.App, issueID string, currentIndex int) tea.Cmd { return func() tea.Msg { - err := svc.Beads.DeleteIssue(context.Background(), issueID) + err := app.Issues.DeleteIssue(context.Background(), issueID) return issueDeletedMsg{IssueID: issueID, Err: err, PreviousIndex: currentIndex} } } @@ -91,7 +91,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil { return m, nil } - issues, err := m.svc.Beads.AllIssues(context.Background()) + issues, err := m.app.Issues.AllIssues(context.Background()) if err != nil { return m, nil } @@ -112,7 +112,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil { return m, nil } - issues, err := m.svc.Beads.AllIssues(context.Background()) + issues, err := m.app.Issues.AllIssues(context.Background()) if err != nil { return m, nil } @@ -132,7 +132,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil { return m, nil } - issues, err := m.svc.Beads.AllIssues(context.Background()) + issues, err := m.app.Issues.AllIssues(context.Background()) if err != nil { return m, nil } @@ -158,7 +158,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil || msg.Issue == nil { return m, nil } - issues, err := m.svc.Beads.AllIssues(context.Background()) + issues, err := m.app.Issues.AllIssues(context.Background()) if err != nil { return m, nil } @@ -185,7 +185,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Err != nil { return m, nil } - issues, err := m.svc.Beads.AllIssues(context.Background()) + issues, err := m.app.Issues.AllIssues(context.Background()) if err != nil { return m, nil } @@ -241,7 +241,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { idx := m.deleteConfirmIndex m.confirmingDelete = false m.deleteConfirmID = "" - return m, deleteIssueCmd(m.svc, issueID, idx) + return m, deleteIssueCmd(m.app, issueID, idx) case "n", "N", "esc": m.confirmingDelete = false m.deleteConfirmID = "" @@ -255,17 +255,17 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, updateIssueStatusCmd(m.svc, issueID, string(models.StatusOpen)) + return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusOpen)) case "i": issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, updateIssueStatusCmd(m.svc, issueID, string(models.StatusInProgress)) + return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress)) case "c": issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, updateIssueStatusCmd(m.svc, issueID, string(models.StatusClosed)) + return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusClosed)) case "esc": m.choosingStatus = false m.statusIssueID = "" @@ -277,7 +277,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.String() == "enter" { title := m.createTitleInput.Value() if title != "" { - return m, createIssueCmd(m.svc, title) + return m, createIssueCmd(m.app, title) } } if msg.String() == "esc" { @@ -295,7 +295,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.String() == "enter" { newTitle := m.titleInput.Value() if newTitle != "" { - return m, updateIssueTitleCmd(m.svc, m.editingIssueID, newTitle) + return m, updateIssueTitleCmd(m.app, m.editingIssueID, newTitle) } } if msg.String() == "esc" { @@ -316,7 +316,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.editingDescription = false m.editingDescIssueID = "" m.descriptionInput.Blur() - return m, updateIssueDescriptionCmd(m.svc, issueID, newDesc) + return m, updateIssueDescriptionCmd(m.app, issueID, newDesc) } if msg.String() == "esc" { m.editingDescription = false diff --git a/pkg/tui/views/views.go b/pkg/tui/views/views.go index 04bbea6..f60f508 100644 --- a/pkg/tui/views/views.go +++ b/pkg/tui/views/views.go @@ -6,6 +6,6 @@ import ( "github.com/LazyBachelor/LazyPM/pkg/tui/views/dashboard" ) -func NewDashboardView(svc *service.Services, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *dashboard.Model { - return dashboard.NewDashboard(svc, feedbackChan, quitChan) +func NewDashboardView(app *service.App, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *dashboard.Model { + return dashboard.NewDashboard(app, feedbackChan, quitChan) } diff --git a/pkg/web/handler/comments.go b/pkg/web/handler/comments.go index 8c5db6d..043ea2c 100644 --- a/pkg/web/handler/comments.go +++ b/pkg/web/handler/comments.go @@ -29,7 +29,7 @@ func ListComments(w http.ResponseWriter, r *http.Request) { func CreateComment(w http.ResponseWriter, r *http.Request) { issue := r.Context().Value(issueKey).(*models.Issue) - svc := Services(r) + app := App(r) hx := HTMX(r) form, err := ParseForm[CommentForm](r) @@ -47,7 +47,7 @@ func CreateComment(w http.ResponseWriter, r *http.Request) { return } - comment, err := svc.Beads.AddIssueComment(r.Context(), issue.ID, form.Author, form.Text) + comment, err := app.Issues.AddIssueComment(r.Context(), issue.ID, form.Author, form.Text) if err != nil { http.Error(w, "Failed to create comment: "+err.Error(), http.StatusInternalServerError) return diff --git a/pkg/web/handler/context.go b/pkg/web/handler/context.go index bdb1b51..a7631ed 100644 --- a/pkg/web/handler/context.go +++ b/pkg/web/handler/context.go @@ -11,14 +11,14 @@ import ( type contextKey string const ( - servicesKey contextKey = "services" - htmxKey contextKey = "htmx" + appKey contextKey = "app" + htmxKey contextKey = "htmx" ) -func ServicesMiddleware(svc *service.Services) func(http.Handler) http.Handler { +func AppMiddleware(app *service.App) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := context.WithValue(r.Context(), servicesKey, svc) + ctx := context.WithValue(r.Context(), appKey, app) next.ServeHTTP(w, r.WithContext(ctx)) }) } @@ -33,8 +33,8 @@ func HTMXMiddleware(next http.Handler) http.Handler { }) } -func Services(r *http.Request) *service.Services { - return r.Context().Value(servicesKey).(*service.Services) +func App(r *http.Request) *service.App { + return r.Context().Value(appKey).(*service.App) } func HTMX(r *http.Request) *htmx.Handler { diff --git a/pkg/web/handler/index.go b/pkg/web/handler/index.go index 677a033..98e559b 100644 --- a/pkg/web/handler/index.go +++ b/pkg/web/handler/index.go @@ -8,10 +8,10 @@ import ( ) func IndexHandler(w http.ResponseWriter, r *http.Request) { - svc := Services(r) + app := App(r) hx := HTMX(r) - issues, err := svc.Beads.AllIssues(r.Context()) + issues, err := app.Issues.AllIssues(r.Context()) if err != nil { http.Error(w, "failed to retrieve issues", http.StatusInternalServerError) return diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go index d0bb77a..b9ceace 100644 --- a/pkg/web/handler/issues.go +++ b/pkg/web/handler/issues.go @@ -30,7 +30,7 @@ type UpdateIssueForm struct { } func CreateIssue(w http.ResponseWriter, r *http.Request) { - svc := Services(r) + app := App(r) hx := HTMX(r) form, err := ParseForm[IssueForm](r) @@ -49,7 +49,7 @@ func CreateIssue(w http.ResponseWriter, r *http.Request) { } issue := form.toIssue() - if err := svc.Beads.CreateIssue(r.Context(), &issue, ""); err != nil { + if err := app.Issues.CreateIssue(r.Context(), &issue, ""); err != nil { http.Error(w, "Failed to create issue: "+err.Error(), http.StatusInternalServerError) return } @@ -64,10 +64,10 @@ func CreateIssue(w http.ResponseWriter, r *http.Request) { } func ListIssues(w http.ResponseWriter, r *http.Request) { - svc := Services(r) + app := App(r) hx := HTMX(r) - issues, err := svc.Beads.AllIssues(r.Context()) + issues, err := app.Issues.AllIssues(r.Context()) if err != nil { http.Error(w, "Failed to retrieve issues", http.StatusInternalServerError) return @@ -79,10 +79,10 @@ func ListIssues(w http.ResponseWriter, r *http.Request) { func IssueCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - svc := Services(r) + app := App(r) id := chi.URLParam(r, "id") - issue, err := svc.Beads.GetIssue(r.Context(), id) + issue, err := app.Issues.GetIssue(r.Context(), id) if err != nil { http.Error(w, "Error getting issue: "+err.Error(), http.StatusNotFound) return @@ -92,7 +92,7 @@ func IssueCtx(next http.Handler) http.Handler { return } - comments, err := svc.Beads.GetIssueComments(r.Context(), issue.ID) + comments, err := app.Issues.GetIssueComments(r.Context(), issue.ID) if err != nil { http.Error(w, "Error getting comments: "+err.Error(), http.StatusInternalServerError) return @@ -132,7 +132,7 @@ func GetIssue(w http.ResponseWriter, r *http.Request) { func UpdateIssue(w http.ResponseWriter, r *http.Request) { issue := r.Context().Value(issueKey).(*models.Issue) - svc := Services(r) + app := App(r) hx := HTMX(r) form, err := ParseForm[UpdateIssueForm](r) @@ -152,12 +152,12 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) { changes := form.toChanges() - if err := svc.Beads.UpdateIssue(r.Context(), issue.ID, changes, ""); err != nil { + if err := app.Issues.UpdateIssue(r.Context(), issue.ID, changes, ""); err != nil { http.Error(w, "Failed to update issue", http.StatusInternalServerError) return } - issue, err = svc.Beads.GetIssue(r.Context(), issue.ID) + issue, err = app.Issues.GetIssue(r.Context(), issue.ID) if err != nil { http.Error(w, "Failed to retrieve updated issue", http.StatusInternalServerError) return @@ -170,8 +170,8 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) { func DeleteIssue(w http.ResponseWriter, r *http.Request) { issue := r.Context().Value(issueKey).(*models.Issue) - svc := Services(r) - if err := svc.Beads.DeleteIssue(r.Context(), issue.ID); err != nil { + app := App(r) + if err := app.Issues.DeleteIssue(r.Context(), issue.ID); err != nil { http.Error(w, "Failed to delete issue", http.StatusInternalServerError) return } diff --git a/pkg/web/server/routes.go b/pkg/web/server/routes.go index 1f69bc7..9a2169c 100644 --- a/pkg/web/server/routes.go +++ b/pkg/web/server/routes.go @@ -22,7 +22,7 @@ func (s *Server) RegisterRoutes(assets embed.FS) http.Handler { r.Use(middleware.CleanPath) r.Use(handler.HTMXMiddleware) - r.Use(handler.ServicesMiddleware(s.Services)) + r.Use(handler.AppMiddleware(s.App)) s.handleAssets(r, assets) diff --git a/pkg/web/server/server.go b/pkg/web/server/server.go index da06a1b..7931c5a 100644 --- a/pkg/web/server/server.go +++ b/pkg/web/server/server.go @@ -9,9 +9,9 @@ import ( ) type Server struct { - Address string - Assets embed.FS - Services *service.Services + Address string + Assets embed.FS + App *service.App } // NewServer creates and configures a new HTTP server instance. diff --git a/pkg/web/web.go b/pkg/web/web.go index f9ad4b3..896d3d6 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -3,6 +3,7 @@ package web import ( "context" "embed" + "errors" "fmt" "net/http" "time" @@ -13,7 +14,7 @@ import ( "github.com/LazyBachelor/LazyPM/pkg/web/server" ) -type WebConfig = service.Config +type Config = service.Config type Web struct { feedbackChan chan task.ValidationFeedback @@ -27,8 +28,8 @@ func NewWeb() *Web { //go:embed assets/* var assets embed.FS -func (w *Web) Run(ctx context.Context, config WebConfig) error { - svc, cleanup, err := service.NewServices(ctx, config) +func (w *Web) Run(ctx context.Context, config Config) error { + app, cleanup, err := service.NewServices(ctx, config) if err != nil { return err } @@ -36,16 +37,16 @@ func (w *Web) Run(ctx context.Context, config WebConfig) error { defer cleanup() httpServer := server.NewServer(server.Server{ - Address: config.WebAddress, - Assets: assets, - Services: svc, + Address: config.WebAddress, + Assets: assets, + App: app, }) fmt.Printf("Starting web server on %s...\n", config.WebAddress) serverErr := make(chan error, 1) go func() { - if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { serverErr <- err } }() From 33c8c05ae3b7bedbd1144d1d854334e3f77a7408 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 19:46:55 +0100 Subject: [PATCH 11/20] refactor to decouple commands form entrypoint for reusability and centralizing commands. Increase reusability and composition --- cmd/pm/main.go | 14 ++- cmd/survey/cmd.go | 98 ------------------- cmd/survey/main.go | 21 +++- cmd/survey/runner.go | 57 +++++++++-- cmd/survey/tasks/base.go | 2 +- .../commands/issues}/close.go | 11 +-- .../commands/issues}/completion.go | 2 +- .../commands/issues}/create.go | 24 +++-- .../commands/issues}/delete.go | 12 +-- .../ls.go => internal/commands/issues/list.go | 26 +++-- .../commands/issues}/read.go | 7 +- .../commands/issues}/root.go | 40 ++++---- .../commands/issues}/update.go | 22 ++--- internal/commands/survey/list.go | 17 ++++ internal/commands/survey/root.go | 29 ++++++ internal/commands/survey/start.go | 9 ++ internal/commands/survey/status.go | 30 ++++++ internal/commands/survey/submit.go | 12 +++ internal/service/interfaces.go | 15 ++- pkg/cli/cli.go | 23 +++-- pkg/cli/commands/status.go | 46 --------- pkg/cli/styles/styles.go | 10 -- pkg/{cli => }/repl/completer.go | 0 pkg/{cli => }/repl/executor.go | 10 +- pkg/{cli => }/repl/options.go | 0 pkg/{cli => }/repl/repl.go | 41 +++++--- pkg/{cli => }/repl/suggestions.go | 5 +- pkg/task/runner.go | 4 +- 28 files changed, 305 insertions(+), 282 deletions(-) delete mode 100644 cmd/survey/cmd.go rename {pkg/cli/commands => internal/commands/issues}/close.go (87%) rename {pkg/cli/commands => internal/commands/issues}/completion.go (99%) rename {pkg/cli/commands => internal/commands/issues}/create.go (82%) rename {pkg/cli/commands => internal/commands/issues}/delete.go (92%) rename pkg/cli/commands/ls.go => internal/commands/issues/list.go (63%) rename {pkg/cli/commands => internal/commands/issues}/read.go (88%) rename {pkg/cli/commands => internal/commands/issues}/root.go (69%) rename {pkg/cli/commands => internal/commands/issues}/update.go (80%) create mode 100644 internal/commands/survey/list.go create mode 100644 internal/commands/survey/root.go create mode 100644 internal/commands/survey/start.go create mode 100644 internal/commands/survey/status.go create mode 100644 internal/commands/survey/submit.go delete mode 100644 pkg/cli/commands/status.go delete mode 100644 pkg/cli/styles/styles.go rename pkg/{cli => }/repl/completer.go (100%) rename pkg/{cli => }/repl/executor.go (63%) rename pkg/{cli => }/repl/options.go (100%) rename pkg/{cli => }/repl/repl.go (74%) rename pkg/{cli => }/repl/suggestions.go (97%) diff --git a/cmd/pm/main.go b/cmd/pm/main.go index 3ac39fd..291d10b 100644 --- a/cmd/pm/main.go +++ b/cmd/pm/main.go @@ -3,12 +3,24 @@ package main import ( "context" + issuesCmd "github.com/LazyBachelor/LazyPM/internal/commands/issues" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/cli" ) func main() { - if err := cli.NewCli().Run(context.Background(), service.BaseConfig); err != nil { + if err := cli.NewCli(issuesCmd.RootCmd).Run(context.Background(), service.BaseConfig); err != nil { return } } + +func init() { + issuesCmd.RootCmd.AddCommand(issuesCmd.GetCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.ListCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CloseCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CreateCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.DeleteCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.UpdateCmd) + issuesCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) +} diff --git a/cmd/survey/cmd.go b/cmd/survey/cmd.go deleted file mode 100644 index 6243cc1..0000000 --- a/cmd/survey/cmd.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/LazyBachelor/LazyPM/pkg/task" - "github.com/spf13/cobra" -) - -var interfaceType string -var stage int - -var rootCmd = &cobra.Command{ - Use: "survey", - Long: `Project Management Interface Survey - -Thank you for participating in our survey! - -We are gathering data on how users interact with different task management interfaces to better understand their preferences and compare their usability. -This survey will present you with a series of tasks to complete using various interfaces, including command-line, web-based, and terminal user interfaces. - -Please answer the questions honestly and to the best of your ability. -Your responses will be kept confidential and used solely for research purposes.`} - -var startCmd = &cobra.Command{ - Use: "start", - Short: "Start the user survey", - RunE: runStartCmd, -} - -var submitCmd = &cobra.Command{ - Use: "submit", - Short: "Submit your survey responses", - RunE: func(cmd *cobra.Command, args []string) error { - cmd.Println("Submitting responses and metrics...") - return nil - }, -} - -func runStartCmd(cmd *cobra.Command, args []string) error { - interfaces := initInterfaces() - - svc, cleanup, err := initializeServices(cmd.Context()) - if err != nil { - return returnIfUserQuit(err, "failed to initialize services") - } - defer cleanup() - - surveyTasks := initTasks(svc) - - if cmd.Flags().Changed("interface") { - if _, ok := interfaces[interfaceType]; !ok { - return fmt.Errorf("invalid interface, valid are (tui, repl, web)") - } - interfaces = map[string]task.Interface{ - interfaceType: interfaces[interfaceType], - } - } - - if cmd.Flags().Changed("stage") { - if stage < 1 || stage > len(surveyTasks) { - return fmt.Errorf("invalid stage") - } - if err := runTask(cmd.Context(), surveyTasks[stage-1], interfaces[interfaceType]); err != nil { - return err - } - return nil - } - - if err := newIntroModel().Run(); err != nil { - return returnIfUserQuit(err, "failed to run intro") - } - - if err := taskLoop(cmd.Context(), surveyTasks, interfaces); err != nil { - return returnIfUserQuit(err, "task loop failed") - } - return nil -} - -var listCmd = &cobra.Command{ - Use: "list", - Short: "List available tasks", - RunE: func(cmd *cobra.Command, args []string) error { - for i, name := range task.List() { - cmd.Printf("%d. %s\n", i+1, name) - } - return nil - }, -} - -func init() { - rootCmd.CompletionOptions.DisableDefaultCmd = true - startCmd.Flags().StringVarP(&interfaceType, "interface", "i", "tui", "Specify interface.") - startCmd.Flags().IntVarP(&stage, "stage", "s", 1, "Run stage directly") - rootCmd.AddCommand(startCmd) - rootCmd.AddCommand(submitCmd) - rootCmd.AddCommand(listCmd) -} diff --git a/cmd/survey/main.go b/cmd/survey/main.go index 5ff186a..085b265 100644 --- a/cmd/survey/main.go +++ b/cmd/survey/main.go @@ -3,14 +3,33 @@ package main import ( "context" + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + "github.com/LazyBachelor/LazyPM/internal/commands/survey" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" "github.com/charmbracelet/fang" ) func main() { ctx := context.Background() - if err := fang.Execute(ctx, rootCmd, + if err := fang.Execute(ctx, surveyCmd.RootCmd, fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil { return } } + +func init() { + surveyCmd.StartCmd.RunE = runStartCmd + surveyCmd.RootCmd.AddCommand(surveyCmd.StartCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.SubmitCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.ListCmd) + + task.Register("create_issue", func(app *service.App) task.Tasker { + return tasks.NewCreateIssueTask(app) + }) + task.Register("coding_task", func(app *service.App) task.Tasker { + return tasks.NewCodingTask(app) + }) +} diff --git a/cmd/survey/runner.go b/cmd/survey/runner.go index 3e213f1..6ff6ca0 100644 --- a/cmd/survey/runner.go +++ b/cmd/survey/runner.go @@ -7,28 +7,67 @@ import ( "math/rand" "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/spf13/cobra" ) -func runTask(ctx context.Context, t task.Tasker, i task.Interface) error { - return task.RunTask(ctx, t, i, tasks.InterfaceToType(i)) +func runStartCmd(cmd *cobra.Command, args []string) error { + interfaces := initInterfaces() + + svc, cleanup, err := initializeServices(cmd.Context()) + if err != nil { + return returnIfUserQuit(err, "failed to initialize services") + } + defer cleanup() + + surveyTasks := initTasks(svc) + + if cmd.Flags().Changed("interface") { + if _, ok := interfaces[surveyCmd.InterfaceType]; !ok { + return fmt.Errorf("invalid interface, valid are (tui, repl, web)") + } + interfaces = map[string]task.Interface{ + surveyCmd.InterfaceType: interfaces[surveyCmd.InterfaceType], + } + } + + if cmd.Flags().Changed("stage") { + if surveyCmd.Task < 1 || surveyCmd.Task > len(surveyTasks) { + return fmt.Errorf("invalid stage") + } + if err := task.RunTask(cmd.Context(), surveyTasks[surveyCmd.Task-1], + interfaces[surveyCmd.InterfaceType], tasks.InterfaceToType(interfaces[surveyCmd.InterfaceType])); err != nil { + return err + } + return nil + } + + if err := newIntroModel().Run(); err != nil { + return returnIfUserQuit(err, "failed to run intro") + } + + if err := taskLoop(cmd.Context(), surveyTasks, interfaces); err != nil { + return returnIfUserQuit(err, "task loop failed") + } + return nil } func taskLoop(ctx context.Context, surveyTasks []task.Tasker, interfaces map[string]task.Interface) error { - var ifaceNames []string + var iNames []string for name := range interfaces { - ifaceNames = append(ifaceNames, name) + iNames = append(iNames, name) } - rand.Shuffle(len(ifaceNames), func(i, j int) { - ifaceNames[i], ifaceNames[j] = ifaceNames[j], ifaceNames[i] + rand.Shuffle(len(iNames), func(i, j int) { + iNames[i], iNames[j] = iNames[j], iNames[i] }) for i, t := range surveyTasks { - idx := i % len(ifaceNames) - selected := interfaces[ifaceNames[idx]] + idx := i % len(iNames) + selected := interfaces[iNames[idx]] - if err := runTask(ctx, t, selected); err != nil { + if err := task.RunTask(ctx, t, selected, tasks.InterfaceToType(selected)); err != nil { return err } } diff --git a/cmd/survey/tasks/base.go b/cmd/survey/tasks/base.go index ccc8bbe..3432e5d 100644 --- a/cmd/survey/tasks/base.go +++ b/cmd/survey/tasks/base.go @@ -2,7 +2,7 @@ package tasks import ( "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/repl" "github.com/LazyBachelor/LazyPM/pkg/task" taskui "github.com/LazyBachelor/LazyPM/pkg/task/ui" "github.com/LazyBachelor/LazyPM/pkg/tui" diff --git a/pkg/cli/commands/close.go b/internal/commands/issues/close.go similarity index 87% rename from pkg/cli/commands/close.go rename to internal/commands/issues/close.go index 2bafcbb..3c3dc80 100644 --- a/pkg/cli/commands/close.go +++ b/internal/commands/issues/close.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "fmt" @@ -7,9 +7,9 @@ import ( "github.com/spf13/cobra" ) -// closeCmd represents the close command, +// CloseCmd represents the close command, // which allows users to close an existing issue by its ID. -var closeCmd = &cobra.Command{ +var CloseCmd = &cobra.Command{ Use: "close [id]", Short: "Close an existing issue", Long: `Close an existing issue by its ID.`, @@ -58,8 +58,3 @@ func runCloseCmd(cmd *cobra.Command, args []string) error { return nil } - -// init function to set up the close command and its flags. -func init() { - rootCmd.AddCommand(closeCmd) -} diff --git a/pkg/cli/commands/completion.go b/internal/commands/issues/completion.go similarity index 99% rename from pkg/cli/commands/completion.go rename to internal/commands/issues/completion.go index e164585..7330a28 100644 --- a/pkg/cli/commands/completion.go +++ b/internal/commands/issues/completion.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "context" diff --git a/pkg/cli/commands/create.go b/internal/commands/issues/create.go similarity index 82% rename from pkg/cli/commands/create.go rename to internal/commands/issues/create.go index a270f03..17a9864 100644 --- a/pkg/cli/commands/create.go +++ b/internal/commands/issues/create.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "fmt" @@ -18,8 +18,8 @@ const ( pm create Fix bug --desc "Bug description" --status in_progress --type bug --priority 5` ) -// createCmd represents the create command, which allows users to create a new issue with specified details. -var createCmd = &cobra.Command{ +// CreateCmd represents the create command, which allows users to create a new issue with specified details. +var CreateCmd = &cobra.Command{ Use: "create [title]", Short: "Create a new issue", Long: `Create a new issue with the specified details.`, @@ -105,15 +105,13 @@ func runCreateInteractive() error { // init function to set up the create command and its flags. func init() { - createCmd.Flags().BoolVarP(&createFlags.interactive, "interactive", "i", false, "Create issue interactively") - createCmd.Flags().StringVarP(&createFlags.description, "desc", "d", "", "Issue description") - createCmd.Flags().StringVarP(&createFlags.status, "status", "s", "open", "Issue status(open, closed, in_progress)") - createCmd.Flags().StringVarP(&createFlags.issueType, "type", "t", "task", "Issue type(bug, feature, task)") - createCmd.Flags().IntVarP(&createFlags.priority, "priority", "p", 0, "Issue priority(0-4)") + CreateCmd.Flags().BoolVarP(&createFlags.interactive, "interactive", "i", false, "Create issue interactively") + CreateCmd.Flags().StringVarP(&createFlags.description, "desc", "d", "", "Issue description") + CreateCmd.Flags().StringVarP(&createFlags.status, "status", "s", "open", "Issue status(open, closed, in_progress)") + CreateCmd.Flags().StringVarP(&createFlags.issueType, "type", "t", "task", "Issue type(bug, feature, task)") + CreateCmd.Flags().IntVarP(&createFlags.priority, "priority", "p", 0, "Issue priority(0-4)") - createCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) - createCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) - createCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) - - rootCmd.AddCommand(createCmd) + CreateCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) + CreateCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) + CreateCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) } diff --git a/pkg/cli/commands/delete.go b/internal/commands/issues/delete.go similarity index 92% rename from pkg/cli/commands/delete.go rename to internal/commands/issues/delete.go index 0b66590..8a4ae2e 100644 --- a/pkg/cli/commands/delete.go +++ b/internal/commands/issues/delete.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "context" @@ -17,8 +17,8 @@ var ( deleteInteractive bool ) -// deleteCmd represents the delete command. -var deleteCmd = &cobra.Command{ +// DeleteCmd represents the delete command. +var DeleteCmd = &cobra.Command{ Use: "delete [id]", Short: "Delete an existing issue", Long: `Delete an existing issue by its ID.`, @@ -125,8 +125,6 @@ func runDeleteInteractive(ctx context.Context) error { // init function to set up the delete command and its flags. func init() { - deleteCmd.Flags().BoolVarP(&deleteInteractive, "interactive", "i", false, "Delete issues interactively") - deleteCmd.Flags().BoolVarP(&confirmDelete, "yes", "y", true, "Confirm deletion without prompt") - - rootCmd.AddCommand(deleteCmd) + DeleteCmd.Flags().BoolVarP(&deleteInteractive, "interactive", "i", false, "Delete issues interactively") + DeleteCmd.Flags().BoolVarP(&confirmDelete, "yes", "y", true, "Confirm deletion without prompt") } diff --git a/pkg/cli/commands/ls.go b/internal/commands/issues/list.go similarity index 63% rename from pkg/cli/commands/ls.go rename to internal/commands/issues/list.go index 1eb13cc..79f55e0 100644 --- a/pkg/cli/commands/ls.go +++ b/internal/commands/issues/list.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "strings" @@ -17,8 +17,8 @@ pm list --title "New feature" --desc "feature description" pm list -p 1 -l 10` ) -// getIssuesCmd represents the get issues command. -var getIssuesCmd = &cobra.Command{ +// ListCmd represents the get issues command. +var ListCmd = &cobra.Command{ Use: "list [search query]", Short: "List all issues", Long: `List all issues in the project management system.`, @@ -70,17 +70,15 @@ func runGetIssuesCmd(cmd *cobra.Command, args []string) error { // init function to set up the get issues command and its flags. func init() { - getIssuesCmd.Flags().StringVar(&listFlags.title, "title", "", "Filter issues by title") - getIssuesCmd.Flags().StringVarP(&listFlags.description, "desc", "d", "", "Filter issues by description") - getIssuesCmd.Flags().StringVarP(&listFlags.status, "status", "s", "", "Filter issues by status (open, closed, in_progress)") - getIssuesCmd.Flags().StringVarP(&listFlags.issueType, "type", "t", "", "Filter issues by type (bug, feature, task)") - getIssuesCmd.Flags().IntVarP(&listFlags.priority, "priority", "p", 0, "Filter issues by priority (0-4)") + ListCmd.Flags().StringVar(&listFlags.title, "title", "", "Filter issues by title") + ListCmd.Flags().StringVarP(&listFlags.description, "desc", "d", "", "Filter issues by description") + ListCmd.Flags().StringVarP(&listFlags.status, "status", "s", "", "Filter issues by status (open, closed, in_progress)") + ListCmd.Flags().StringVarP(&listFlags.issueType, "type", "t", "", "Filter issues by type (bug, feature, task)") + ListCmd.Flags().IntVarP(&listFlags.priority, "priority", "p", 0, "Filter issues by priority (0-4)") - getIssuesCmd.Flags().IntVarP(&listFlags.limit, "limit", "l", 25, "Limit the number of issues returned") + ListCmd.Flags().IntVarP(&listFlags.limit, "limit", "l", 25, "Limit the number of issues returned") - getIssuesCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) - getIssuesCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) - getIssuesCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) - - rootCmd.AddCommand(getIssuesCmd) + ListCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) + ListCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) + ListCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) } diff --git a/pkg/cli/commands/read.go b/internal/commands/issues/read.go similarity index 88% rename from pkg/cli/commands/read.go rename to internal/commands/issues/read.go index 40c8c8c..c070fcf 100644 --- a/pkg/cli/commands/read.go +++ b/internal/commands/issues/read.go @@ -1,12 +1,12 @@ -package commands +package issuesCmd import ( "github.com/LazyBachelor/LazyPM/internal/models" "github.com/spf13/cobra" ) -// getIssueCmd represents the get issue command. -var getIssueCmd = &cobra.Command{ +// GetCmd represents the get issue command. +var GetCmd = &cobra.Command{ Use: "describe [issue ID]", Short: "Get issue details", Long: `Get issue details by ID`, @@ -44,5 +44,4 @@ func runGetCmd(cmd *cobra.Command, args []string) error { // init function to set up the get issue command. func init() { - rootCmd.AddCommand(getIssueCmd) } diff --git a/pkg/cli/commands/root.go b/internal/commands/issues/root.go similarity index 69% rename from pkg/cli/commands/root.go rename to internal/commands/issues/root.go index 45b1c45..16c24f2 100644 --- a/pkg/cli/commands/root.go +++ b/internal/commands/issues/root.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "bytes" @@ -29,8 +29,8 @@ type Flags struct { priority int } -// rootCmd is the base command for the CLI application. -var rootCmd = &cobra.Command{ +// RootCmd is the base command for the CLI application. +var RootCmd = &cobra.Command{ Short: "Project Management CLI", Long: `Project Management CLI for managing issues and tasks.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { @@ -45,7 +45,7 @@ var rootCmd = &cobra.Command{ // Must be called before executing any commands to ensure services are available. func SetApp(application *service.App) { app = application - rootCmd.Use = app.Config.RootCmd + RootCmd.Use = app.Config.RootCmd } // AppFromContext retrieves the App from the command context @@ -57,37 +57,37 @@ func AppFromContext(ctx context.Context) *service.App { return app } -// Execute executes the root command using the fang library. -func Execute() error { - return fang.Execute(context.Background(), rootCmd, - fang.WithColorSchemeFunc(fang.AnsiColorScheme)) -} - // ExecuteArgs executes the command with the given arguments using the fang library. func ExecuteArgs(args []string) error { - rootCmd.SetArgs(args) - return fang.Execute(context.Background(), rootCmd, + RootCmd.SetArgs(args) + return fang.Execute(context.Background(), RootCmd, fang.WithColorSchemeFunc(fang.AnsiColorScheme)) } // ExecuteArgsString executes the command with the given arguments and returns the output as a string. // This is useful for testing command outputs and used in the REPL func ExecuteArgsString(args []string) (string, error) { + return ExecuteArgsStringWithContext(context.Background(), args) +} + +// ExecuteArgsStringWithContext executes the command with context and returns the output as a string. +func ExecuteArgsStringWithContext(ctx context.Context, args []string) (string, error) { buf := new(bytes.Buffer) - rootCmd.SetOut(buf) - rootCmd.SetErr(buf) - rootCmd.SetArgs(args) + RootCmd.SetOut(buf) + RootCmd.SetErr(buf) + RootCmd.SetArgs(args) + RootCmd.SetContext(ctx) - err := rootCmd.Execute() + err := RootCmd.Execute() return buf.String(), err } // init function to set up the command hierarchy and options. func init() { - rootCmd.CompletionOptions.DisableDefaultCmd = false - rootCmd.AddGroup(&cobra.Group{ID: "help", Title: "Helping Commands"}) - rootCmd.SetCompletionCommandGroupID("help") - rootCmd.SetHelpCommandGroupID("help") + RootCmd.CompletionOptions.DisableDefaultCmd = false + RootCmd.AddGroup(&cobra.Group{ID: "help", Title: "Helping Commands"}) + RootCmd.SetCompletionCommandGroupID("help") + RootCmd.SetHelpCommandGroupID("help") } diff --git a/pkg/cli/commands/update.go b/internal/commands/issues/update.go similarity index 80% rename from pkg/cli/commands/update.go rename to internal/commands/issues/update.go index f26b23e..92b87a8 100644 --- a/pkg/cli/commands/update.go +++ b/internal/commands/issues/update.go @@ -1,4 +1,4 @@ -package commands +package issuesCmd import ( "fmt" @@ -9,7 +9,7 @@ import ( var updateFlags Flags -var updateCmd = &cobra.Command{ +var UpdateCmd = &cobra.Command{ Use: "update [issue ID]", Short: "Update an existing issue", Long: `Update an existing issue by its ID with the specified details.`, @@ -55,17 +55,15 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error { } func init() { - updateCmd.Flags().StringVar(&updateFlags.title, "title", "", "New issue title") - updateCmd.Flags().StringVarP(&updateFlags.description, "desc", "d", "", "New issue description") - updateCmd.Flags().StringVarP(&updateFlags.status, "status", "s", "", "New issue status(open, closed, in_progress)") - updateCmd.Flags().StringVarP(&updateFlags.issueType, "type", "t", "", "New issue type(bug, feature, task)") - updateCmd.Flags().IntVarP(&updateFlags.priority, "priority", "p", 0, "New issue priority(0-5)") + UpdateCmd.Flags().StringVar(&updateFlags.title, "title", "", "New issue title") + UpdateCmd.Flags().StringVarP(&updateFlags.description, "desc", "d", "", "New issue description") + UpdateCmd.Flags().StringVarP(&updateFlags.status, "status", "s", "", "New issue status(open, closed, in_progress)") + UpdateCmd.Flags().StringVarP(&updateFlags.issueType, "type", "t", "", "New issue type(bug, feature, task)") + UpdateCmd.Flags().IntVarP(&updateFlags.priority, "priority", "p", 0, "New issue priority(0-5)") - updateCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) - updateCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) - updateCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) - - rootCmd.AddCommand(updateCmd) + UpdateCmd.RegisterFlagCompletionFunc("type", completionFunc(typeOptions)) + UpdateCmd.RegisterFlagCompletionFunc("status", completionFunc(statusOptions)) + UpdateCmd.RegisterFlagCompletionFunc("priority", completionFunc(priorityRange)) } func getUpdateValues(cmd *cobra.Command) (map[string]interface{}, error) { diff --git a/internal/commands/survey/list.go b/internal/commands/survey/list.go new file mode 100644 index 0000000..6b4d6fa --- /dev/null +++ b/internal/commands/survey/list.go @@ -0,0 +1,17 @@ +package surveyCmd + +import ( + "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/spf13/cobra" +) + +var ListCmd = &cobra.Command{ + Use: "list", + Short: "List available tasks", + RunE: func(cmd *cobra.Command, args []string) error { + for i, name := range task.List() { + cmd.Printf("%d. %s\n", i+1, name) + } + return nil + }, +} diff --git a/internal/commands/survey/root.go b/internal/commands/survey/root.go new file mode 100644 index 0000000..b5e00a9 --- /dev/null +++ b/internal/commands/survey/root.go @@ -0,0 +1,29 @@ +package surveyCmd + +import ( + "github.com/spf13/cobra" +) + +var ( + InterfaceType string + Task int +) + +// RootCmd is the base command for the survey CLI. +var RootCmd = &cobra.Command{ + Use: "survey", + Long: `Project Management Interface Survey + +Thank you for participating in our survey! + +We are gathering data on how users interact with different task management interfaces to better understand their preferences and compare their usability. +This survey will present you with a series of tasks to complete using various interfaces, including command-line, web-based, and terminal user interfaces. + +Please answer the questions honestly and to the best of your ability. +Your responses will be kept confidential and used solely for research purposes.`} + +func init() { + RootCmd.CompletionOptions.DisableDefaultCmd = true + StartCmd.Flags().StringVarP(&InterfaceType, "interface", "i", "tui", "Specify interface.") + StartCmd.Flags().IntVarP(&Task, "task", "t", 1, "Run task directly") +} diff --git a/internal/commands/survey/start.go b/internal/commands/survey/start.go new file mode 100644 index 0000000..c987941 --- /dev/null +++ b/internal/commands/survey/start.go @@ -0,0 +1,9 @@ +package surveyCmd + +import "github.com/spf13/cobra" + +// StartCmd is the start command - RunE is set in cmd/survey/ +var StartCmd = &cobra.Command{ + Use: "start", + Short: "Start the user survey", +} diff --git a/internal/commands/survey/status.go b/internal/commands/survey/status.go new file mode 100644 index 0000000..8983bff --- /dev/null +++ b/internal/commands/survey/status.go @@ -0,0 +1,30 @@ +package surveyCmd + +import ( + "github.com/LazyBachelor/LazyPM/internal/commands/issues" + "github.com/spf13/cobra" +) + +// StatusCmd displays the current task validation status +var StatusCmd = &cobra.Command{ + Use: "status", + Short: "Check task validation status", + Long: "Displays the current task validation status and feedback.", + RunE: runStatusCmd, +} + +func runStatusCmd(cmd *cobra.Command, args []string) error { + app := issuesCmd.AppFromContext(cmd.Context()) + if app == nil || app.CurrentFeedback == nil { + cmd.Println("No validation status available.") + return nil + } + + if app.CurrentFeedback.Message == "" { + cmd.Println("No validation status available yet.") + return nil + } + + cmd.Print(app.CurrentFeedback.Message) + return nil +} diff --git a/internal/commands/survey/submit.go b/internal/commands/survey/submit.go new file mode 100644 index 0000000..9766e6c --- /dev/null +++ b/internal/commands/survey/submit.go @@ -0,0 +1,12 @@ +package surveyCmd + +import "github.com/spf13/cobra" + +var SubmitCmd = &cobra.Command{ + Use: "submit", + Short: "Submit your survey responses", + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Println("Submitting responses and metrics...") + return nil + }, +} diff --git a/internal/service/interfaces.go b/internal/service/interfaces.go index 33095ba..6275d27 100644 --- a/internal/service/interfaces.go +++ b/internal/service/interfaces.go @@ -8,11 +8,18 @@ import ( "github.com/steveyegge/beads" ) +// ValidationFeedback holds task validation status +type ValidationFeedback struct { + Success bool + Message string +} + type App struct { - Config Config - Issues IssueService - Stats StatsService - Logger *slog.Logger + Config Config + Issues IssueService + Stats StatsService + Logger *slog.Logger + CurrentFeedback *ValidationFeedback } type IssueService interface { diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 420e37d..1b9d01f 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -4,17 +4,23 @@ package cli import ( "context" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "github.com/charmbracelet/fang" + "github.com/spf13/cobra" ) // Config is an alias for service.Config, used to configure the CLI. type Config = service.Config -type CLI struct{} +type CLI struct { + RootCmd *cobra.Command +} -func NewCli() *CLI { - return &CLI{} +func NewCli(rootCmd *cobra.Command) *CLI { + return &CLI{ + RootCmd: rootCmd, + } } // Run initializes the services and executes the CLI commands. @@ -26,9 +32,10 @@ func (c *CLI) Run(ctx context.Context, config Config) error { defer cleanup() - commands.SetApp(app) + issuesCmd.SetApp(app) - if err := commands.Execute(); err != nil { + if err := fang.Execute(ctx, c.RootCmd, + fang.WithColorSchemeFunc(fang.AnsiColorScheme)); err != nil { return err } @@ -44,9 +51,9 @@ func (c *CLI) RunWithArgs(ctx context.Context, config Config, args []string) err defer cleanup() - commands.SetApp(app) + issuesCmd.SetApp(app) - if err := commands.ExecuteArgs(args); err != nil { + if err := issuesCmd.ExecuteArgs(args); err != nil { return err } diff --git a/pkg/cli/commands/status.go b/pkg/cli/commands/status.go deleted file mode 100644 index ad11ec8..0000000 --- a/pkg/cli/commands/status.go +++ /dev/null @@ -1,46 +0,0 @@ -package commands - -import ( - "github.com/LazyBachelor/LazyPM/pkg/task" - "github.com/spf13/cobra" -) - -// replInstance holds a reference to the REPL for accessing validation feedback -var replInstance interface { - GetCurrentFeedback() task.ValidationFeedback -} - -// SetRepl sets the REPL instance for use by commands -func SetRepl(repl interface { - GetCurrentFeedback() task.ValidationFeedback -}) { - replInstance = repl -} - -// StatusCmd displays the current task validation status -var StatusCmd = &cobra.Command{ - Use: "status", - Short: "Check task validation status", - Long: "Displays the current task validation status and feedback.", - RunE: runStatusCmd, -} - -func runStatusCmd(cmd *cobra.Command, args []string) error { - if replInstance == nil { - cmd.Println("No task validation available") - return nil - } - - feedback := replInstance.GetCurrentFeedback() - if feedback.Message == "" { - cmd.Println("No validation status available yet.") - return nil - } - - cmd.Print(feedback.Message) - - return nil -} -func init() { - rootCmd.AddCommand(StatusCmd) -} diff --git a/pkg/cli/styles/styles.go b/pkg/cli/styles/styles.go deleted file mode 100644 index e4bf744..0000000 --- a/pkg/cli/styles/styles.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package styles defines the styling for the CLI output using the lipgloss library. -package styles - -import "github.com/charmbracelet/lipgloss" - -var ( - TitleStyle = lipgloss.NewStyle().Bold(true).Padding(1) - - CommandStyle = lipgloss.NewStyle().Padding(1) -) diff --git a/pkg/cli/repl/completer.go b/pkg/repl/completer.go similarity index 100% rename from pkg/cli/repl/completer.go rename to pkg/repl/completer.go diff --git a/pkg/cli/repl/executor.go b/pkg/repl/executor.go similarity index 63% rename from pkg/cli/repl/executor.go rename to pkg/repl/executor.go index c0c50a7..037528f 100644 --- a/pkg/cli/repl/executor.go +++ b/pkg/repl/executor.go @@ -4,12 +4,10 @@ import ( "os/exec" "strings" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" ) // execute processes the input command and returns the output -// or an error if it occurs. It handles what type of command is being executed, -// whether it's a PM command or a shell command, and routes it accordingly. func execute(input string) (string, error) { if input == "" { return "", nil @@ -29,8 +27,6 @@ func execute(input string) (string, error) { return executeShellCommand(input) } -// executeShellCommand executes a shell command -// and returns its output or an error if it occurs. func executeShellCommand(input string) (string, error) { parts := strings.Fields(input) if len(parts) == 0 { @@ -42,14 +38,12 @@ func executeShellCommand(input string) (string, error) { return string(output), err } -// executePMCommand executes a PM command using the commands package -// and returns its output or an error if it occurs. func executePMCommand(input string) (string, error) { parts := strings.Fields(input) if len(parts) == 0 { return "", nil } - output, err := commands.ExecuteArgsString(parts) + output, err := issuesCmd.ExecuteArgsString(parts) return output, err } diff --git a/pkg/cli/repl/options.go b/pkg/repl/options.go similarity index 100% rename from pkg/cli/repl/options.go rename to pkg/repl/options.go diff --git a/pkg/cli/repl/repl.go b/pkg/repl/repl.go similarity index 74% rename from pkg/cli/repl/repl.go rename to pkg/repl/repl.go index ff49153..bfbf51f 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/repl/repl.go @@ -7,11 +7,13 @@ import ( "os" "strings" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/internal/style" "github.com/LazyBachelor/LazyPM/pkg/cli" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" - "github.com/LazyBachelor/LazyPM/pkg/cli/styles" "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" "github.com/c-bata/go-prompt" "golang.org/x/term" ) @@ -27,6 +29,7 @@ You can also run shell commands directly. Type 'exit' or 'quit' to leave.` type REPL struct { feedbackChan chan task.ValidationFeedback quitChan chan bool + app *service.App currentFeedback task.ValidationFeedback exitRequested bool @@ -55,12 +58,12 @@ func (r *REPL) Run(ctx context.Context, config cli.Config) error { defer cleanup() // Make sure to set app, to ensure they are available. - commands.SetApp(app) + issuesCmd.SetApp(app) - // Set the REPL instance so status command can access it - commands.SetRepl(r) + // Store app reference for updating feedback + r.app = app - fmt.Println(styles.TitleStyle.Render(ReplTitle)) // Print REPL title. + fmt.Println(style.TitleStyle.Render(ReplTitle)) // Print REPL title. // Start goroutine to watch for validation feedback and quit signals if r.feedbackChan != nil && r.quitChan != nil { @@ -102,8 +105,8 @@ func (r *REPL) Run(ctx context.Context, config cli.Config) error { // Add the input to the history for future navigation. history = append(history, input) - output, _ := execute(input) // Ignore errors for now, gives better ux - fmt.Println(styles.CommandStyle.Render(output)) // Print the output of the command in a styled format. + output, _ := execute(input) // Ignore errors for now, gives better ux + fmt.Println(style.TextStyle.Render(output)) // Print the output of the command in a styled format. } return nil @@ -114,6 +117,13 @@ func (r *REPL) watchValidation() { select { case feedback := <-r.feedbackChan: r.currentFeedback = feedback + // Update app's CurrentFeedback so status command can access it + if r.app != nil { + r.app.CurrentFeedback = &service.ValidationFeedback{ + Success: feedback.Success, + Message: feedback.Message, + } + } if feedback.Success { fmt.Printf("\n%s\n", styles.TitleStyle.Render("Task completed successfully!")) fmt.Println("Press Enter to exit...") @@ -127,13 +137,18 @@ func (r *REPL) watchValidation() { } } -// GetCurrentFeedback returns the current validation feedback for the status command -func (r *REPL) GetCurrentFeedback() task.ValidationFeedback { - return r.currentFeedback -} - // SetChannels sets the channels for receiving validation feedback and quit signals from the task interface func (r *REPL) SetChannels(feedbackChan chan task.ValidationFeedback, quitChan chan bool) { r.feedbackChan = feedbackChan r.quitChan = quitChan } + +func init() { + issuesCmd.RootCmd.AddCommand(issuesCmd.GetCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.ListCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CloseCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.CreateCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.DeleteCmd) + issuesCmd.RootCmd.AddCommand(issuesCmd.UpdateCmd) + issuesCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) +} diff --git a/pkg/cli/repl/suggestions.go b/pkg/repl/suggestions.go similarity index 97% rename from pkg/cli/repl/suggestions.go rename to pkg/repl/suggestions.go index e99c681..522afa1 100644 --- a/pkg/cli/repl/suggestions.go +++ b/pkg/repl/suggestions.go @@ -4,7 +4,7 @@ import ( "context" "strings" - "github.com/LazyBachelor/LazyPM/pkg/cli/commands" + "github.com/LazyBachelor/LazyPM/internal/commands/issues" "github.com/c-bata/go-prompt" "github.com/muesli/reflow/truncate" ) @@ -21,6 +21,7 @@ var rootSuggestions = []prompt.Suggest{ // commandSuggestions is a list of prompt suggestions for PM commands. var baseSuggestions = []prompt.Suggest{ {Text: "help", Description: "Show help information"}, + {Text: "status", Description: "Show task status"}, {Text: "delete", Description: "Delete an issue by ID"}, {Text: "close", Description: "Close an issue by ID"}, {Text: "create", Description: "Create a new issue with title"}, @@ -148,7 +149,7 @@ func issueIDSuggestions(partial string, hasCommand bool) []prompt.Suggest { return nil } - issues, _ := commands.GetIssueCompletions(context.Background(), partial) + issues, _ := issuesCmd.GetIssueCompletions(context.Background(), partial) var suggestions []prompt.Suggest for _, issue := range issues { diff --git a/pkg/task/runner.go b/pkg/task/runner.go index d060cdd..3af739f 100644 --- a/pkg/task/runner.go +++ b/pkg/task/runner.go @@ -15,7 +15,7 @@ import ( // 3. Run the interface // 4. Start validation loop in background // 5. Show questionnaire when done -func RunTask(ctx context.Context, t Tasker, i Interface, ifaceType InterfaceType) error { +func RunTask(ctx context.Context, t Tasker, i Interface, iType InterfaceType) error { doneChan := make(chan bool, 1) quitChan := make(chan bool, 1) feedbackChan := make(chan ValidationFeedback, 10) @@ -65,7 +65,7 @@ func RunTask(ctx context.Context, t Tasker, i Interface, ifaceType InterfaceType } // Show questionnaire - questions := t.Questions(ifaceType) + questions := t.Questions(iType) questionare := taskui.NewQuestionnaireModel(questions) model, err = tea.NewProgram(questionare, tea.WithAltScreen()).Run() if err != nil { From 78769396e7c000919d1775c337115fa511f6bd53 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 19:47:17 +0100 Subject: [PATCH 12/20] forgot to commit this --- cmd/survey/init.go | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/cmd/survey/init.go b/cmd/survey/init.go index 6587a51..fc17c95 100644 --- a/cmd/survey/init.go +++ b/cmd/survey/init.go @@ -3,39 +3,16 @@ package main import ( "context" + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/repl" "github.com/LazyBachelor/LazyPM/pkg/task" "github.com/LazyBachelor/LazyPM/pkg/tui" "github.com/LazyBachelor/LazyPM/pkg/web" - "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" _ "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" ) -func init() { - task.Register("create_issue", func(app *service.App) task.Tasker { - return tasks.NewCreateIssueTask(app) - }) - task.Register("coding_task", func(app *service.App) task.Tasker { - return tasks.NewCodingTask(app) - }) -} - -func initTasks(app *service.App) []task.Tasker { - var taskers []task.Tasker - - for _, name := range task.List() { - t, err := task.Get(name, app) - if err != nil { - continue - } - taskers = append(taskers, t) - } - - return taskers -} - func initializeServices(ctx context.Context) (*service.App, func(), error) { return service.NewServices(ctx, tasks.BaseConfig()) } @@ -47,3 +24,17 @@ func initInterfaces() map[string]task.Interface { "web": web.NewWeb(), } } + +func initTasks(app *service.App) []task.Tasker { + var taskList []task.Tasker + + for _, name := range task.List() { + t, err := task.Get(name, app) + if err != nil { + continue + } + taskList = append(taskList, t) + } + + return taskList +} From 235b07f7cec22487fb3e75e34d301103443ee466 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 22 Feb 2026 22:58:04 +0100 Subject: [PATCH 13/20] use registry for interfaces also add list interfaces command --- cmd/survey/init.go | 20 +++++++------- cmd/survey/main.go | 17 +++++++++--- cmd/survey/runner.go | 2 +- internal/commands/survey/list.go | 21 ++++++++++++--- pkg/task/register.go | 45 +++++++++++++++++++++++++------- 5 files changed, 76 insertions(+), 29 deletions(-) diff --git a/cmd/survey/init.go b/cmd/survey/init.go index fc17c95..6c67f65 100644 --- a/cmd/survey/init.go +++ b/cmd/survey/init.go @@ -5,10 +5,7 @@ import ( "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/repl" "github.com/LazyBachelor/LazyPM/pkg/task" - "github.com/LazyBachelor/LazyPM/pkg/tui" - "github.com/LazyBachelor/LazyPM/pkg/web" _ "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" ) @@ -18,18 +15,21 @@ func initializeServices(ctx context.Context) (*service.App, func(), error) { } func initInterfaces() map[string]task.Interface { - return map[string]task.Interface{ - "repl": repl.NewRepl(), - "tui": tui.NewTui(), - "web": web.NewWeb(), + interfaces := make(map[string]task.Interface) + for _, name := range task.ListInterfaces() { + i, err := task.GetInterface(name) + if err != nil { + continue + } + interfaces[name] = i } + return interfaces } func initTasks(app *service.App) []task.Tasker { var taskList []task.Tasker - - for _, name := range task.List() { - t, err := task.Get(name, app) + for _, name := range task.ListTasks() { + t, err := task.GetTasks(name, app) if err != nil { continue } diff --git a/cmd/survey/main.go b/cmd/survey/main.go index 085b265..850a620 100644 --- a/cmd/survey/main.go +++ b/cmd/survey/main.go @@ -4,9 +4,12 @@ import ( "context" "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" - "github.com/LazyBachelor/LazyPM/internal/commands/survey" + surveyCmd "github.com/LazyBachelor/LazyPM/internal/commands/survey" "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/repl" "github.com/LazyBachelor/LazyPM/pkg/task" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" "github.com/charmbracelet/fang" ) @@ -20,16 +23,22 @@ func main() { } func init() { + task.RegisterInterface("tui", tui.NewTui()) + task.RegisterInterface("web", web.NewWeb()) + task.RegisterInterface("repl", repl.NewRepl()) + surveyCmd.StartCmd.RunE = runStartCmd surveyCmd.RootCmd.AddCommand(surveyCmd.StartCmd) surveyCmd.RootCmd.AddCommand(surveyCmd.SubmitCmd) surveyCmd.RootCmd.AddCommand(surveyCmd.StatusCmd) - surveyCmd.RootCmd.AddCommand(surveyCmd.ListCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.ListTasksCmd) + surveyCmd.RootCmd.AddCommand(surveyCmd.ListInterfacesCmd) - task.Register("create_issue", func(app *service.App) task.Tasker { + task.RegisterTask("create_issue", func(app *service.App) task.Tasker { return tasks.NewCreateIssueTask(app) }) - task.Register("coding_task", func(app *service.App) task.Tasker { + task.RegisterTask("coding_task", func(app *service.App) task.Tasker { return tasks.NewCodingTask(app) }) + } diff --git a/cmd/survey/runner.go b/cmd/survey/runner.go index 6ff6ca0..3b8fd1b 100644 --- a/cmd/survey/runner.go +++ b/cmd/survey/runner.go @@ -25,7 +25,7 @@ func runStartCmd(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("interface") { if _, ok := interfaces[surveyCmd.InterfaceType]; !ok { - return fmt.Errorf("invalid interface, valid are (tui, repl, web)") + return fmt.Errorf("invalid interface, valid are %v", task.ListInterfaces()) } interfaces = map[string]task.Interface{ surveyCmd.InterfaceType: interfaces[surveyCmd.InterfaceType], diff --git a/internal/commands/survey/list.go b/internal/commands/survey/list.go index 6b4d6fa..80629a2 100644 --- a/internal/commands/survey/list.go +++ b/internal/commands/survey/list.go @@ -5,11 +5,24 @@ import ( "github.com/spf13/cobra" ) -var ListCmd = &cobra.Command{ - Use: "list", - Short: "List available tasks", +var ListTasksCmd = &cobra.Command{ + Use: "list-tasks", + Aliases: []string{"ls-t"}, + Short: "List available tasks", RunE: func(cmd *cobra.Command, args []string) error { - for i, name := range task.List() { + for i, name := range task.ListTasks() { + cmd.Printf("%d. %s\n", i+1, name) + } + return nil + }, +} + +var ListInterfacesCmd = &cobra.Command{ + Use: "list-interfaces", + Aliases: []string{"ls-i"}, + Short: "List available interfaces", + RunE: func(cmd *cobra.Command, args []string) error { + for i, name := range task.ListInterfaces() { cmd.Printf("%d. %s\n", i+1, name) } return nil diff --git a/pkg/task/register.go b/pkg/task/register.go index 4f57ae4..c98d75e 100644 --- a/pkg/task/register.go +++ b/pkg/task/register.go @@ -6,26 +6,51 @@ import ( "github.com/LazyBachelor/LazyPM/internal/service" ) -var registry = make(map[string]func(*service.App) Tasker) +var interfaceRegistry = make(map[string]Interface) -func Register(name string, constructor func(*service.App) Tasker) { - if _, exists := registry[name]; exists { - panic(fmt.Sprintf("task %q already registered", name)) +func RegisterInterface(name string, iface Interface) { + if _, exists := interfaceRegistry[name]; exists { + panic(fmt.Sprintf("interface %q already registered", name)) } - registry[name] = constructor + interfaceRegistry[name] = iface } -func Get(name string, app *service.App) (Tasker, error) { - constructor, ok := registry[name] +func GetInterface(name string) (Interface, error) { + iface, ok := interfaceRegistry[name] + if !ok { + return nil, fmt.Errorf("interface %q not found", name) + } + return iface, nil +} + +func ListInterfaces() []string { + names := make([]string, 0, len(interfaceRegistry)) + for name := range interfaceRegistry { + names = append(names, name) + } + return names +} + +var taskRegistry = make(map[string]func(*service.App) Tasker) + +func RegisterTask(name string, constructor func(*service.App) Tasker) { + if _, exists := taskRegistry[name]; exists { + panic(fmt.Sprintf("task %q already registered", name)) + } + taskRegistry[name] = constructor +} + +func GetTasks(name string, app *service.App) (Tasker, error) { + constructor, ok := taskRegistry[name] if !ok { return nil, fmt.Errorf("task %q not found", name) } return constructor(app), nil } -func List() []string { - names := make([]string, 0, len(registry)) - for name := range registry { +func ListTasks() []string { + names := make([]string, 0, len(taskRegistry)) + for name := range taskRegistry { names = append(names, name) } return names From d1ef4e94913d8a039239feb2e303f0b1642d286a Mon Sep 17 00:00:00 2001 From: Ine Maria Nilssen Aanonsen Date: Mon, 23 Feb 2026 13:51:42 +0100 Subject: [PATCH 14/20] READ ISSUES - WEB --- internal/service/beads.go | 43 ++++++++++++++-- pkg/web/components/search.templ | 26 ++++++++++ pkg/web/components/search_templ.go | 53 ++++++++++++++++++++ pkg/web/handler/index.go | 8 ++- pkg/web/routes/index.templ | 5 +- pkg/web/routes/index_templ.go | 5 +- pkg/web/routes/issue_detail.templ | 2 +- pkg/web/routes/issue_detail_templ.go | 2 +- pkg/web/routes/layout.templ | 75 +++++++++++++--------------- pkg/web/routes/layout_templ.go | 75 +++++++++++++--------------- 10 files changed, 202 insertions(+), 92 deletions(-) create mode 100644 pkg/web/components/search.templ create mode 100644 pkg/web/components/search_templ.go diff --git a/internal/service/beads.go b/internal/service/beads.go index 1a9bc66..803f182 100644 --- a/internal/service/beads.go +++ b/internal/service/beads.go @@ -28,18 +28,51 @@ func NewBeadsService(ctx context.Context, storage beads.Storage, prefix string) } func (s *BeadsService) AllIssues(ctx context.Context) ([]models.Issue, error) { - issuesPtr, err := s.Storage.SearchIssues(ctx, "", models.IssueFilter{}) + issuesPtr, err := s.SearchIssues(ctx, "", models.IssueFilter{}) if err != nil { return nil, err } + return models.IssuesPtrToIssues(issuesPtr), nil +} - if len(issuesPtr) == 0 { - return []models.Issue{}, nil +// SearchIssues returns issues matching the query (title, description, id, assignee) and optional filter. +// The query searches across title, description, and id (LIKE %query%). +// Assignee exact match is also searched when query is non-empty. +func (s *BeadsService) SearchIssues(ctx context.Context, query string, filter models.IssueFilter) ([]*models.Issue, error) { + seen := make(map[string]bool) + var merged []*models.Issue + + // Search by query (title, description, id) + issuesPtr, err := s.Storage.SearchIssues(ctx, query, filter) + if err != nil { + return nil, err + } + for _, issue := range issuesPtr { + if issue != nil && !seen[issue.ID] { + seen[issue.ID] = true + merged = append(merged, issue) + } } - issues := models.IssuesPtrToIssues(issuesPtr) + // Also search by assignee when query is non-empty (exact match) + if query != "" && filter.Assignee == nil { + assigneeFilter := filter + assigneeFilter.Assignee = &query + assigneeFilter.TitleSearch = "" + assigneeFilter.DescriptionContains = "" + assigneePtr, err := s.Storage.SearchIssues(ctx, "", assigneeFilter) + if err != nil { + return nil, err + } + for _, issue := range assigneePtr { + if issue != nil && !seen[issue.ID] { + seen[issue.ID] = true + merged = append(merged, issue) + } + } + } - return issues, nil + return merged, nil } func (s *BeadsService) DeleteIssues() error { diff --git a/pkg/web/components/search.templ b/pkg/web/components/search.templ new file mode 100644 index 0000000..602dc21 --- /dev/null +++ b/pkg/web/components/search.templ @@ -0,0 +1,26 @@ +package components + +templ SearchForm(searchQuery string) { +
+ + +
+} diff --git a/pkg/web/components/search_templ.go b/pkg/web/components/search_templ.go new file mode 100644 index 0000000..cd0b4ec --- /dev/null +++ b/pkg/web/components/search_templ.go @@ -0,0 +1,53 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func SearchForm(searchQuery string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/handler/index.go b/pkg/web/handler/index.go index 677a033..f9bbfcb 100644 --- a/pkg/web/handler/index.go +++ b/pkg/web/handler/index.go @@ -2,7 +2,9 @@ package handler import ( "net/http" + "strings" + "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/web/components" "github.com/LazyBachelor/LazyPM/pkg/web/routes" ) @@ -11,16 +13,20 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { svc := Services(r) hx := HTMX(r) - issues, err := svc.Beads.AllIssues(r.Context()) + query := strings.TrimSpace(r.URL.Query().Get("q")) + filter := models.IssueFilter{Limit: 100} + issuesPtr, err := svc.Beads.SearchIssues(r.Context(), query, filter) if err != nil { http.Error(w, "failed to retrieve issues", http.StatusInternalServerError) return } + issues := models.IssuesPtrToIssues(issuesPtr) props := routes.IndexProps{ IssueTable: components.IssueTableProps{ Issues: issues, }, + SearchQuery: query, } if hx.IsHxRequest() { diff --git a/pkg/web/routes/index.templ b/pkg/web/routes/index.templ index 3d3157d..3c66dde 100644 --- a/pkg/web/routes/index.templ +++ b/pkg/web/routes/index.templ @@ -3,7 +3,8 @@ package routes import "github.com/LazyBachelor/LazyPM/pkg/web/components" type IndexProps struct { - IssueTable components.IssueTableProps + IssueTable components.IssueTableProps + SearchQuery string } templ IndexContent(props IndexProps) { @@ -24,7 +25,7 @@ templ IndexContent(props IndexProps) { } templ Index(props IndexProps) { - @BaseLayout() { + @BaseLayout(BaseLayoutProps{SearchQuery: props.SearchQuery}) { @IndexContent(props) } } diff --git a/pkg/web/routes/index_templ.go b/pkg/web/routes/index_templ.go index 31ab021..2fd6d17 100644 --- a/pkg/web/routes/index_templ.go +++ b/pkg/web/routes/index_templ.go @@ -11,7 +11,8 @@ import templruntime "github.com/a-h/templ/runtime" import "github.com/LazyBachelor/LazyPM/pkg/web/components" type IndexProps struct { - IssueTable components.IssueTableProps + IssueTable components.IssueTableProps + SearchQuery string } func IndexContent(props IndexProps) templ.Component { @@ -103,7 +104,7 @@ func Index(props IndexProps) templ.Component { } return nil }) - templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = BaseLayout(BaseLayoutProps{SearchQuery: props.SearchQuery}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/web/routes/issue_detail.templ b/pkg/web/routes/issue_detail.templ index 606f006..92b34a7 100644 --- a/pkg/web/routes/issue_detail.templ +++ b/pkg/web/routes/issue_detail.templ @@ -77,7 +77,7 @@ templ IssueDetailContent(props IssueDetailProps) { } templ IssueDetail(props IssueDetailProps) { - @BaseLayout() { + @BaseLayout(BaseLayoutProps{SearchQuery: ""}) { @IssueDetailContent(props) } } diff --git a/pkg/web/routes/issue_detail_templ.go b/pkg/web/routes/issue_detail_templ.go index 73e3cef..79e00d3 100644 --- a/pkg/web/routes/issue_detail_templ.go +++ b/pkg/web/routes/issue_detail_templ.go @@ -239,7 +239,7 @@ func IssueDetail(props IssueDetailProps) templ.Component { } return nil }) - templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = BaseLayout(BaseLayoutProps{SearchQuery: ""}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/web/routes/layout.templ b/pkg/web/routes/layout.templ index 1bf7c63..45f079a 100644 --- a/pkg/web/routes/layout.templ +++ b/pkg/web/routes/layout.templ @@ -1,49 +1,44 @@ package routes import "github.com/LazyBachelor/LazyPM/pkg/web/components" -import "github.com/LazyBachelor/LazyPM/pkg/web/components/base" -var baseLayout = components.LayoutProps{ - Title: "Beads Test Application", - Description: "A sample application using Beads storage service", - Head: components.HeadProps{ - Links: []components.Link{ - {Href: "/assets/css/styles.css", Rel: "preload", As: "style"}, - {Href: "/assets/css/styles.css", Rel: "stylesheet"}, - {Href: "/assets/manifest.json", Rel: "manifest"}, - }, - Scripts: []components.Script{ - {Src: "/assets/js/htmx.min.js", Defer: true}, - {Src: "/assets/js/ajax.min.js", Defer: true}, - }, - }, - - Routes: []components.NavRoutes{ - {Name: "Dashboard", Path: "/", Icon: components.IconDashboard()}, - {Name: "Issues", Path: "/", Icon: components.IconIssues()}, - {Name: "Agile Board", Path: "/board", Icon: components.IconAgileBoard()}, - {Name: "Backlog", Path: "/backlog", Icon: components.IconBacklog()}, - {Name: "Reports", Path: "/reports", Icon: components.IconReports()}, - {Name: "Settings", Path: "/settings", Icon: components.IconSettings()}, - }, - - Header: components.HeaderProps{ - Title: "LazyPM Dashboard", - NavbarCenter: base.Input(base.InputProps{ - Name: "search-issue", - Type: "search", - Placeholder: "Search issues...", - Class: "", - Attrs: templ.Attributes{ - "class": "text-base-content", - }, - }), - NavbarEnd: components.Status(), - }, +type BaseLayoutProps struct { + SearchQuery string } -templ BaseLayout() { - @components.Layout(baseLayout) { +func buildLayoutProps(searchQuery string) components.LayoutProps { + return components.LayoutProps{ + Title: "Beads Test Application", + Description: "A sample application using Beads storage service", + Head: components.HeadProps{ + Links: []components.Link{ + {Href: "/assets/css/styles.css", Rel: "preload", As: "style"}, + {Href: "/assets/css/styles.css", Rel: "stylesheet"}, + {Href: "/assets/manifest.json", Rel: "manifest"}, + }, + Scripts: []components.Script{ + {Src: "/assets/js/htmx.min.js", Defer: true}, + {Src: "/assets/js/ajax.min.js", Defer: true}, + }, + }, + Routes: []components.NavRoutes{ + {Name: "Dashboard", Path: "/", Icon: components.IconDashboard()}, + {Name: "Issues", Path: "/", Icon: components.IconIssues()}, + {Name: "Agile Board", Path: "/board", Icon: components.IconAgileBoard()}, + {Name: "Backlog", Path: "/backlog", Icon: components.IconBacklog()}, + {Name: "Reports", Path: "/reports", Icon: components.IconReports()}, + {Name: "Settings", Path: "/settings", Icon: components.IconSettings()}, + }, + Header: components.HeaderProps{ + Title: "LazyPM Dashboard", + NavbarCenter: components.SearchForm(searchQuery), + NavbarEnd: components.Status(), + }, + } +} + +templ BaseLayout(props BaseLayoutProps) { + @components.Layout(buildLayoutProps(props.SearchQuery)) { { children... } } } diff --git a/pkg/web/routes/layout_templ.go b/pkg/web/routes/layout_templ.go index d6a5555..322dacb 100644 --- a/pkg/web/routes/layout_templ.go +++ b/pkg/web/routes/layout_templ.go @@ -9,48 +9,43 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import "github.com/LazyBachelor/LazyPM/pkg/web/components" -import "github.com/LazyBachelor/LazyPM/pkg/web/components/base" -var baseLayout = components.LayoutProps{ - Title: "Beads Test Application", - Description: "A sample application using Beads storage service", - Head: components.HeadProps{ - Links: []components.Link{ - {Href: "/assets/css/styles.css", Rel: "preload", As: "style"}, - {Href: "/assets/css/styles.css", Rel: "stylesheet"}, - {Href: "/assets/manifest.json", Rel: "manifest"}, - }, - Scripts: []components.Script{ - {Src: "/assets/js/htmx.min.js", Defer: true}, - {Src: "/assets/js/ajax.min.js", Defer: true}, - }, - }, - - Routes: []components.NavRoutes{ - {Name: "Dashboard", Path: "/", Icon: components.IconDashboard()}, - {Name: "Issues", Path: "/", Icon: components.IconIssues()}, - {Name: "Agile Board", Path: "/board", Icon: components.IconAgileBoard()}, - {Name: "Backlog", Path: "/backlog", Icon: components.IconBacklog()}, - {Name: "Reports", Path: "/reports", Icon: components.IconReports()}, - {Name: "Settings", Path: "/settings", Icon: components.IconSettings()}, - }, - - Header: components.HeaderProps{ - Title: "LazyPM Dashboard", - NavbarCenter: base.Input(base.InputProps{ - Name: "search-issue", - Type: "search", - Placeholder: "Search issues...", - Class: "", - Attrs: templ.Attributes{ - "class": "text-base-content", - }, - }), - NavbarEnd: components.Status(), - }, +type BaseLayoutProps struct { + SearchQuery string } -func BaseLayout() templ.Component { +func buildLayoutProps(searchQuery string) components.LayoutProps { + return components.LayoutProps{ + Title: "Beads Test Application", + Description: "A sample application using Beads storage service", + Head: components.HeadProps{ + Links: []components.Link{ + {Href: "/assets/css/styles.css", Rel: "preload", As: "style"}, + {Href: "/assets/css/styles.css", Rel: "stylesheet"}, + {Href: "/assets/manifest.json", Rel: "manifest"}, + }, + Scripts: []components.Script{ + {Src: "/assets/js/htmx.min.js", Defer: true}, + {Src: "/assets/js/ajax.min.js", Defer: true}, + }, + }, + Routes: []components.NavRoutes{ + {Name: "Dashboard", Path: "/", Icon: components.IconDashboard()}, + {Name: "Issues", Path: "/", Icon: components.IconIssues()}, + {Name: "Agile Board", Path: "/board", Icon: components.IconAgileBoard()}, + {Name: "Backlog", Path: "/backlog", Icon: components.IconBacklog()}, + {Name: "Reports", Path: "/reports", Icon: components.IconReports()}, + {Name: "Settings", Path: "/settings", Icon: components.IconSettings()}, + }, + Header: components.HeaderProps{ + Title: "LazyPM Dashboard", + NavbarCenter: components.SearchForm(searchQuery), + NavbarEnd: components.Status(), + }, + } +} + +func BaseLayout(props BaseLayoutProps) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -89,7 +84,7 @@ func BaseLayout() templ.Component { } return nil }) - templ_7745c5c3_Err = components.Layout(baseLayout).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = components.Layout(buildLayoutProps(props.SearchQuery)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } From 1adf2ec9405ad6c4e0766418e94c8d49f76f971c Mon Sep 17 00:00:00 2001 From: Ine Maria Nilssen Aanonsen Date: Mon, 23 Feb 2026 13:59:14 +0100 Subject: [PATCH 15/20] READ ISSUES - WEB --- internal/service/beads.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/service/beads.go b/internal/service/beads.go index 803f182..f2e55fe 100644 --- a/internal/service/beads.go +++ b/internal/service/beads.go @@ -35,14 +35,11 @@ func (s *BeadsService) AllIssues(ctx context.Context) ([]models.Issue, error) { return models.IssuesPtrToIssues(issuesPtr), nil } -// SearchIssues returns issues matching the query (title, description, id, assignee) and optional filter. -// The query searches across title, description, and id (LIKE %query%). -// Assignee exact match is also searched when query is non-empty. + func (s *BeadsService) SearchIssues(ctx context.Context, query string, filter models.IssueFilter) ([]*models.Issue, error) { seen := make(map[string]bool) var merged []*models.Issue - // Search by query (title, description, id) issuesPtr, err := s.Storage.SearchIssues(ctx, query, filter) if err != nil { return nil, err @@ -54,7 +51,6 @@ func (s *BeadsService) SearchIssues(ctx context.Context, query string, filter mo } } - // Also search by assignee when query is non-empty (exact match) if query != "" && filter.Assignee == nil { assigneeFilter := filter assigneeFilter.Assignee = &query From 3896ddd7dde98e78471de3dedae5de95aa0b39bc Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 23 Feb 2026 14:21:15 +0100 Subject: [PATCH 16/20] rollback as the new addition was unused and unnessesary --- internal/service/beads.go | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/internal/service/beads.go b/internal/service/beads.go index f2e55fe..1a9bc66 100644 --- a/internal/service/beads.go +++ b/internal/service/beads.go @@ -28,47 +28,18 @@ func NewBeadsService(ctx context.Context, storage beads.Storage, prefix string) } func (s *BeadsService) AllIssues(ctx context.Context) ([]models.Issue, error) { - issuesPtr, err := s.SearchIssues(ctx, "", models.IssueFilter{}) + issuesPtr, err := s.Storage.SearchIssues(ctx, "", models.IssueFilter{}) if err != nil { return nil, err } - return models.IssuesPtrToIssues(issuesPtr), nil -} - -func (s *BeadsService) SearchIssues(ctx context.Context, query string, filter models.IssueFilter) ([]*models.Issue, error) { - seen := make(map[string]bool) - var merged []*models.Issue - - issuesPtr, err := s.Storage.SearchIssues(ctx, query, filter) - if err != nil { - return nil, err - } - for _, issue := range issuesPtr { - if issue != nil && !seen[issue.ID] { - seen[issue.ID] = true - merged = append(merged, issue) - } + if len(issuesPtr) == 0 { + return []models.Issue{}, nil } - if query != "" && filter.Assignee == nil { - assigneeFilter := filter - assigneeFilter.Assignee = &query - assigneeFilter.TitleSearch = "" - assigneeFilter.DescriptionContains = "" - assigneePtr, err := s.Storage.SearchIssues(ctx, "", assigneeFilter) - if err != nil { - return nil, err - } - for _, issue := range assigneePtr { - if issue != nil && !seen[issue.ID] { - seen[issue.ID] = true - merged = append(merged, issue) - } - } - } + issues := models.IssuesPtrToIssues(issuesPtr) - return merged, nil + return issues, nil } func (s *BeadsService) DeleteIssues() error { From 4b3f4b00ab94169182c4ee5a2294fb0ce2d48f33 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 23 Feb 2026 14:36:51 +0100 Subject: [PATCH 17/20] dont think those are needed --- pkg/web/components/search.templ | 7 +------ pkg/web/components/search_templ.go | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pkg/web/components/search.templ b/pkg/web/components/search.templ index 602dc21..9e5c912 100644 --- a/pkg/web/components/search.templ +++ b/pkg/web/components/search.templ @@ -3,8 +3,6 @@ package components templ SearchForm(searchQuery string) {
-
} diff --git a/pkg/web/components/search_templ.go b/pkg/web/components/search_templ.go index cd0b4ec..78612d3 100644 --- a/pkg/web/components/search_templ.go +++ b/pkg/web/components/search_templ.go @@ -29,20 +29,20 @@ func SearchForm(searchQuery string) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" placeholder=\"Search by title, description, or assignee...\" class=\"input input-md input-bordered text-base-content w-48 md:w-64\" aria-label=\"Search issues\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } From 69329d5274f8f05eba39ecec7f5c5829e1a3a4b1 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:40:29 +0100 Subject: [PATCH 18/20] Remove cron schedule from docker-publish.yml Removed scheduled cron job from Docker publish workflow. --- .github/workflows/docker-publish.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index c63a19f..98bd621 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -6,8 +6,6 @@ name: Docker # documentation. on: - schedule: - - cron: '45 15 * * *' push: branches: [ "main" ] # Publish semver tags as releases. From 96291b7a942a4308bf5f9dd0a5209e4408d091ef Mon Sep 17 00:00:00 2001 From: Moira Daniella A Sebastian Date: Mon, 23 Feb 2026 19:48:10 +0100 Subject: [PATCH 19/20] Added issues dashboard --- pkg/web/assets/css/styles.css | 4 +- pkg/web/components/base/table.templ | 2 +- pkg/web/components/base/table_templ.go | 2 +- pkg/web/components/dashboard.templ | 102 +++++ pkg/web/components/dashboard_templ.go | 360 +++++++++++++++++ pkg/web/components/issue_detail_card.templ | 103 +++++ pkg/web/components/issue_detail_card_templ.go | 379 ++++++++++++++++++ pkg/web/handler/dashboard.go | 95 +++++ pkg/web/handler/issues.go | 15 +- pkg/web/input.css | 10 +- pkg/web/routes/dashboard.templ | 32 ++ pkg/web/routes/dashboard_templ.go | 83 ++++ pkg/web/routes/issue_detail.templ | 103 +---- pkg/web/routes/issue_detail_templ.go | 360 +---------------- pkg/web/routes/layout.templ | 2 +- pkg/web/routes/layout_templ.go | 2 +- pkg/web/server/routes.go | 1 + 17 files changed, 1199 insertions(+), 456 deletions(-) create mode 100644 pkg/web/components/dashboard.templ create mode 100644 pkg/web/components/dashboard_templ.go create mode 100644 pkg/web/components/issue_detail_card.templ create mode 100644 pkg/web/components/issue_detail_card_templ.go create mode 100644 pkg/web/handler/dashboard.go create mode 100644 pkg/web/routes/dashboard.templ create mode 100644 pkg/web/routes/dashboard_templ.go diff --git a/pkg/web/assets/css/styles.css b/pkg/web/assets/css/styles.css index 0ea9f8f..761ed25 100644 --- a/pkg/web/assets/css/styles.css +++ b/pkg/web/assets/css/styles.css @@ -1,2 +1,2 @@ -/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-black:#000;--spacing:.25rem;--container-xs:20rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--radius-lg:.5rem;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(98% 0 0);--color-base-300:oklch(95% 0 0);--color-base-content:oklch(21% .006 285.885);--color-primary:oklch(45% .24 277.023);--color-primary-content:oklch(93% .034 272.788);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}@media (prefers-color-scheme:dark){:root:not([data-theme]){color-scheme:dark;--color-base-100:oklch(25.33% .016 252.42);--color-base-200:oklch(23.26% .014 253.1);--color-base-300:oklch(21.15% .012 254.09);--color-base-content:oklch(97.807% .029 256.847);--color-primary:oklch(58% .233 277.117);--color-primary-content:oklch(96% .018 272.314);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}}:root:has(input.theme-controller[value=light]:checked),[data-theme=light]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(98% 0 0);--color-base-300:oklch(95% 0 0);--color-base-content:oklch(21% .006 285.885);--color-primary:oklch(45% .24 277.023);--color-primary-content:oklch(93% .034 272.788);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=dark]:checked),[data-theme=dark]{color-scheme:dark;--color-base-100:oklch(25.33% .016 252.42);--color-base-200:oklch(23.26% .014 253.1);--color-base-300:oklch(21.15% .012 254.09);--color-base-content:oklch(97.807% .029 256.847);--color-primary:oklch(58% .233 277.117);--color-primary-content:oklch(96% .018 272.314);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=cupcake]:checked),[data-theme=cupcake]{color-scheme:light;--color-base-100:oklch(97.788% .004 56.375);--color-base-200:oklch(93.982% .007 61.449);--color-base-300:oklch(91.586% .006 53.44);--color-base-content:oklch(23.574% .066 313.189);--color-primary:oklch(85% .138 181.071);--color-primary-content:oklch(43% .078 188.216);--color-secondary:oklch(89% .061 343.231);--color-secondary-content:oklch(45% .187 3.815);--color-accent:oklch(90% .076 70.697);--color-accent-content:oklch(47% .157 37.304);--color-neutral:oklch(27% .006 286.033);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(68% .169 237.323);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(69% .17 162.48);--color-success-content:oklch(26% .051 172.552);--color-warning:oklch(79% .184 86.047);--color-warning-content:oklch(28% .066 53.813);--color-error:oklch(64% .246 16.439);--color-error-content:oklch(27% .105 12.094);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:1;--noise:0}:root:has(input.theme-controller[value=bumblebee]:checked),[data-theme=bumblebee]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97% 0 0);--color-base-300:oklch(92% 0 0);--color-base-content:oklch(20% 0 0);--color-primary:oklch(85% .199 91.936);--color-primary-content:oklch(42% .095 57.708);--color-secondary:oklch(75% .183 55.934);--color-secondary-content:oklch(40% .123 38.172);--color-accent:oklch(0% 0 0);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(37% .01 67.558);--color-neutral-content:oklch(92% .003 48.717);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(39% .09 240.876);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(39% .141 25.723);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=emerald]:checked),[data-theme=emerald]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(93% 0 0);--color-base-300:oklch(86% 0 0);--color-base-content:oklch(35.519% .032 262.988);--color-primary:oklch(76.662% .135 153.45);--color-primary-content:oklch(33.387% .04 162.24);--color-secondary:oklch(61.302% .202 261.294);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(72.772% .149 33.2);--color-accent-content:oklch(0% 0 0);--color-neutral:oklch(35.519% .032 262.988);--color-neutral-content:oklch(98.462% .001 247.838);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=corporate]:checked),[data-theme=corporate]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(93% 0 0);--color-base-300:oklch(86% 0 0);--color-base-content:oklch(22.389% .031 278.072);--color-primary:oklch(58% .158 241.966);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(55% .046 257.417);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(60% .118 184.704);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(0% 0 0);--color-neutral-content:oklch(100% 0 0);--color-info:oklch(60% .126 221.723);--color-info-content:oklch(100% 0 0);--color-success:oklch(62% .194 149.214);--color-success-content:oklch(100% 0 0);--color-warning:oklch(85% .199 91.936);--color-warning-content:oklch(0% 0 0);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(0% 0 0);--radius-selector:.25rem;--radius-field:.25rem;--radius-box:.25rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=synthwave]:checked),[data-theme=synthwave]{color-scheme:dark;--color-base-100:oklch(15% .09 281.288);--color-base-200:oklch(20% .09 281.288);--color-base-300:oklch(25% .09 281.288);--color-base-content:oklch(78% .115 274.713);--color-primary:oklch(71% .202 349.761);--color-primary-content:oklch(28% .109 3.907);--color-secondary:oklch(82% .111 230.318);--color-secondary-content:oklch(29% .066 243.157);--color-accent:oklch(75% .183 55.934);--color-accent-content:oklch(26% .079 36.259);--color-neutral:oklch(45% .24 277.023);--color-neutral-content:oklch(87% .065 274.039);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(77% .152 181.912);--color-success-content:oklch(27% .046 192.524);--color-warning:oklch(90% .182 98.111);--color-warning-content:oklch(42% .095 57.708);--color-error:oklch(73.7% .121 32.639);--color-error-content:oklch(23.501% .096 290.329);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=retro]:checked),[data-theme=retro]{color-scheme:light;--color-base-100:oklch(91.637% .034 90.515);--color-base-200:oklch(88.272% .049 91.774);--color-base-300:oklch(84.133% .065 90.856);--color-base-content:oklch(41% .112 45.904);--color-primary:oklch(80% .114 19.571);--color-primary-content:oklch(39% .141 25.723);--color-secondary:oklch(92% .084 155.995);--color-secondary-content:oklch(44% .119 151.328);--color-accent:oklch(68% .162 75.834);--color-accent-content:oklch(41% .112 45.904);--color-neutral:oklch(44% .011 73.639);--color-neutral-content:oklch(86% .005 56.366);--color-info:oklch(58% .158 241.966);--color-info-content:oklch(96% .059 95.617);--color-success:oklch(51% .096 186.391);--color-success-content:oklch(96% .059 95.617);--color-warning:oklch(64% .222 41.116);--color-warning-content:oklch(96% .059 95.617);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(40% .123 38.172);--radius-selector:.25rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=cyberpunk]:checked),[data-theme=cyberpunk]{color-scheme:light;--color-base-100:oklch(94.51% .179 104.32);--color-base-200:oklch(91.51% .179 104.32);--color-base-300:oklch(85.51% .179 104.32);--color-base-content:oklch(0% 0 0);--color-primary:oklch(74.22% .209 6.35);--color-primary-content:oklch(14.844% .041 6.35);--color-secondary:oklch(83.33% .184 204.72);--color-secondary-content:oklch(16.666% .036 204.72);--color-accent:oklch(71.86% .217 310.43);--color-accent-content:oklch(14.372% .043 310.43);--color-neutral:oklch(23.04% .065 269.31);--color-neutral-content:oklch(94.51% .179 104.32);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:0rem;--radius-field:0rem;--radius-box:0rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=valentine]:checked),[data-theme=valentine]{color-scheme:light;--color-base-100:oklch(97% .014 343.198);--color-base-200:oklch(94% .028 342.258);--color-base-300:oklch(89% .061 343.231);--color-base-content:oklch(52% .223 3.958);--color-primary:oklch(65% .241 354.308);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(62% .265 303.9);--color-secondary-content:oklch(97% .014 308.299);--color-accent:oklch(82% .111 230.318);--color-accent-content:oklch(39% .09 240.876);--color-neutral:oklch(40% .153 2.432);--color-neutral-content:oklch(89% .061 343.231);--color-info:oklch(86% .127 207.078);--color-info-content:oklch(44% .11 240.79);--color-success:oklch(84% .143 164.978);--color-success-content:oklch(43% .095 166.913);--color-warning:oklch(75% .183 55.934);--color-warning-content:oklch(26% .079 36.259);--color-error:oklch(63% .237 25.331);--color-error-content:oklch(97% .013 17.38);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=halloween]:checked),[data-theme=halloween]{color-scheme:dark;--color-base-100:oklch(21% .006 56.043);--color-base-200:oklch(14% .004 49.25);--color-base-300:oklch(0% 0 0);--color-base-content:oklch(84.955% 0 0);--color-primary:oklch(77.48% .204 60.62);--color-primary-content:oklch(19.693% .004 196.779);--color-secondary:oklch(45.98% .248 305.03);--color-secondary-content:oklch(89.196% .049 305.03);--color-accent:oklch(64.8% .223 136.073);--color-accent-content:oklch(0% 0 0);--color-neutral:oklch(24.371% .046 65.681);--color-neutral-content:oklch(84.874% .009 65.681);--color-info:oklch(54.615% .215 262.88);--color-info-content:oklch(90.923% .043 262.88);--color-success:oklch(62.705% .169 149.213);--color-success-content:oklch(12.541% .033 149.213);--color-warning:oklch(66.584% .157 58.318);--color-warning-content:oklch(13.316% .031 58.318);--color-error:oklch(65.72% .199 27.33);--color-error-content:oklch(13.144% .039 27.33);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=garden]:checked),[data-theme=garden]{color-scheme:light;--color-base-100:oklch(92.951% .002 17.197);--color-base-200:oklch(86.445% .002 17.197);--color-base-300:oklch(79.938% .001 17.197);--color-base-content:oklch(16.961% .001 17.32);--color-primary:oklch(62.45% .278 3.836);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(48.495% .11 355.095);--color-secondary-content:oklch(89.699% .022 355.095);--color-accent:oklch(56.273% .054 154.39);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(24.155% .049 89.07);--color-neutral-content:oklch(92.951% .002 17.197);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=forest]:checked),[data-theme=forest]{color-scheme:dark;--color-base-100:oklch(20.84% .008 17.911);--color-base-200:oklch(18.522% .007 17.911);--color-base-300:oklch(16.203% .007 17.911);--color-base-content:oklch(83.768% .001 17.911);--color-primary:oklch(68.628% .185 148.958);--color-primary-content:oklch(0% 0 0);--color-secondary:oklch(69.776% .135 168.327);--color-secondary-content:oklch(13.955% .027 168.327);--color-accent:oklch(70.628% .119 185.713);--color-accent-content:oklch(14.125% .023 185.713);--color-neutral:oklch(30.698% .039 171.364);--color-neutral-content:oklch(86.139% .007 171.364);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=aqua]:checked),[data-theme=aqua]{color-scheme:dark;--color-base-100:oklch(37% .146 265.522);--color-base-200:oklch(28% .091 267.935);--color-base-300:oklch(22% .091 267.935);--color-base-content:oklch(90% .058 230.902);--color-primary:oklch(85.661% .144 198.645);--color-primary-content:oklch(40.124% .068 197.603);--color-secondary:oklch(60.682% .108 309.782);--color-secondary-content:oklch(96% .016 293.756);--color-accent:oklch(93.426% .102 94.555);--color-accent-content:oklch(18.685% .02 94.555);--color-neutral:oklch(27% .146 265.522);--color-neutral-content:oklch(80% .146 265.522);--color-info:oklch(54.615% .215 262.88);--color-info-content:oklch(90.923% .043 262.88);--color-success:oklch(62.705% .169 149.213);--color-success-content:oklch(12.541% .033 149.213);--color-warning:oklch(66.584% .157 58.318);--color-warning-content:oklch(27% .077 45.635);--color-error:oklch(73.95% .19 27.33);--color-error-content:oklch(14.79% .038 27.33);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=lofi]:checked),[data-theme=lofi]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97% 0 0);--color-base-300:oklch(94% 0 0);--color-base-content:oklch(0% 0 0);--color-primary:oklch(15.906% 0 0);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(21.455% .001 17.278);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(26.861% 0 0);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(0% 0 0);--color-neutral-content:oklch(100% 0 0);--color-info:oklch(79.54% .103 205.9);--color-info-content:oklch(15.908% .02 205.9);--color-success:oklch(90.13% .153 164.14);--color-success-content:oklch(18.026% .03 164.14);--color-warning:oklch(88.37% .135 79.94);--color-warning-content:oklch(17.674% .027 79.94);--color-error:oklch(78.66% .15 28.47);--color-error-content:oklch(15.732% .03 28.47);--radius-selector:2rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=pastel]:checked),[data-theme=pastel]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(98.462% .001 247.838);--color-base-300:oklch(92.462% .001 247.838);--color-base-content:oklch(20% 0 0);--color-primary:oklch(90% .063 306.703);--color-primary-content:oklch(49% .265 301.924);--color-secondary:oklch(89% .058 10.001);--color-secondary-content:oklch(51% .222 16.935);--color-accent:oklch(90% .093 164.15);--color-accent-content:oklch(50% .118 165.612);--color-neutral:oklch(55% .046 257.417);--color-neutral-content:oklch(92% .013 255.508);--color-info:oklch(86% .127 207.078);--color-info-content:oklch(52% .105 223.128);--color-success:oklch(87% .15 154.449);--color-success-content:oklch(52% .154 150.069);--color-warning:oklch(83% .128 66.29);--color-warning-content:oklch(55% .195 38.402);--color-error:oklch(80% .114 19.571);--color-error-content:oklch(50% .213 27.518);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:0;--noise:0}:root:has(input.theme-controller[value=fantasy]:checked),[data-theme=fantasy]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(93% 0 0);--color-base-300:oklch(86% 0 0);--color-base-content:oklch(27.807% .029 256.847);--color-primary:oklch(37.45% .189 325.02);--color-primary-content:oklch(87.49% .037 325.02);--color-secondary:oklch(53.92% .162 241.36);--color-secondary-content:oklch(90.784% .032 241.36);--color-accent:oklch(75.98% .204 56.72);--color-accent-content:oklch(15.196% .04 56.72);--color-neutral:oklch(27.807% .029 256.847);--color-neutral-content:oklch(85.561% .005 256.847);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=wireframe]:checked),[data-theme=wireframe]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97% 0 0);--color-base-300:oklch(94% 0 0);--color-base-content:oklch(20% 0 0);--color-primary:oklch(87% 0 0);--color-primary-content:oklch(26% 0 0);--color-secondary:oklch(87% 0 0);--color-secondary-content:oklch(26% 0 0);--color-accent:oklch(87% 0 0);--color-accent-content:oklch(26% 0 0);--color-neutral:oklch(87% 0 0);--color-neutral-content:oklch(26% 0 0);--color-info:oklch(44% .11 240.79);--color-info-content:oklch(90% .058 230.902);--color-success:oklch(43% .095 166.913);--color-success-content:oklch(90% .093 164.15);--color-warning:oklch(47% .137 46.201);--color-warning-content:oklch(92% .12 95.746);--color-error:oklch(44% .177 26.899);--color-error-content:oklch(88% .062 18.334);--radius-selector:0rem;--radius-field:.25rem;--radius-box:.25rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=black]:checked),[data-theme=black]{color-scheme:dark;--color-base-100:oklch(0% 0 0);--color-base-200:oklch(19% 0 0);--color-base-300:oklch(22% 0 0);--color-base-content:oklch(87.609% 0 0);--color-primary:oklch(35% 0 0);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(35% 0 0);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(35% 0 0);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(35% 0 0);--color-neutral-content:oklch(100% 0 0);--color-info:oklch(45.201% .313 264.052);--color-info-content:oklch(89.04% .062 264.052);--color-success:oklch(51.975% .176 142.495);--color-success-content:oklch(90.395% .035 142.495);--color-warning:oklch(96.798% .211 109.769);--color-warning-content:oklch(19.359% .042 109.769);--color-error:oklch(62.795% .257 29.233);--color-error-content:oklch(12.559% .051 29.233);--radius-selector:0rem;--radius-field:0rem;--radius-box:0rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=luxury]:checked),[data-theme=luxury]{color-scheme:dark;--color-base-100:oklch(14.076% .004 285.822);--color-base-200:oklch(20.219% .004 308.229);--color-base-300:oklch(23.219% .004 308.229);--color-base-content:oklch(75.687% .123 76.89);--color-primary:oklch(100% 0 0);--color-primary-content:oklch(20% 0 0);--color-secondary:oklch(27.581% .064 261.069);--color-secondary-content:oklch(85.516% .012 261.069);--color-accent:oklch(36.674% .051 338.825);--color-accent-content:oklch(87.334% .01 338.825);--color-neutral:oklch(24.27% .057 59.825);--color-neutral-content:oklch(93.203% .089 90.861);--color-info:oklch(79.061% .121 237.133);--color-info-content:oklch(15.812% .024 237.133);--color-success:oklch(78.119% .192 132.154);--color-success-content:oklch(15.623% .038 132.154);--color-warning:oklch(86.127% .136 102.891);--color-warning-content:oklch(17.225% .027 102.891);--color-error:oklch(71.753% .176 22.568);--color-error-content:oklch(14.35% .035 22.568);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=dracula]:checked),[data-theme=dracula]{color-scheme:dark;--color-base-100:oklch(28.822% .022 277.508);--color-base-200:oklch(26.805% .02 277.508);--color-base-300:oklch(24.787% .019 277.508);--color-base-content:oklch(97.747% .007 106.545);--color-primary:oklch(75.461% .183 346.812);--color-primary-content:oklch(15.092% .036 346.812);--color-secondary:oklch(74.202% .148 301.883);--color-secondary-content:oklch(14.84% .029 301.883);--color-accent:oklch(83.392% .124 66.558);--color-accent-content:oklch(16.678% .024 66.558);--color-neutral:oklch(39.445% .032 275.524);--color-neutral-content:oklch(87.889% .006 275.524);--color-info:oklch(88.263% .093 212.846);--color-info-content:oklch(17.652% .018 212.846);--color-success:oklch(87.099% .219 148.024);--color-success-content:oklch(17.419% .043 148.024);--color-warning:oklch(95.533% .134 112.757);--color-warning-content:oklch(19.106% .026 112.757);--color-error:oklch(68.22% .206 24.43);--color-error-content:oklch(13.644% .041 24.43);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=cmyk]:checked),[data-theme=cmyk]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(95% 0 0);--color-base-300:oklch(90% 0 0);--color-base-content:oklch(20% 0 0);--color-primary:oklch(71.772% .133 239.443);--color-primary-content:oklch(14.354% .026 239.443);--color-secondary:oklch(64.476% .202 359.339);--color-secondary-content:oklch(12.895% .04 359.339);--color-accent:oklch(94.228% .189 105.306);--color-accent-content:oklch(18.845% .037 105.306);--color-neutral:oklch(21.778% 0 0);--color-neutral-content:oklch(84.355% 0 0);--color-info:oklch(68.475% .094 217.284);--color-info-content:oklch(13.695% .018 217.284);--color-success:oklch(46.949% .162 321.406);--color-success-content:oklch(89.389% .032 321.406);--color-warning:oklch(71.236% .159 52.023);--color-warning-content:oklch(14.247% .031 52.023);--color-error:oklch(62.013% .208 28.717);--color-error-content:oklch(12.402% .041 28.717);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=autumn]:checked),[data-theme=autumn]{color-scheme:light;--color-base-100:oklch(95.814% 0 0);--color-base-200:oklch(89.107% 0 0);--color-base-300:oklch(82.4% 0 0);--color-base-content:oklch(19.162% 0 0);--color-primary:oklch(40.723% .161 17.53);--color-primary-content:oklch(88.144% .032 17.53);--color-secondary:oklch(61.676% .169 23.865);--color-secondary-content:oklch(12.335% .033 23.865);--color-accent:oklch(73.425% .094 60.729);--color-accent-content:oklch(14.685% .018 60.729);--color-neutral:oklch(54.367% .037 51.902);--color-neutral-content:oklch(90.873% .007 51.902);--color-info:oklch(69.224% .097 207.284);--color-info-content:oklch(13.844% .019 207.284);--color-success:oklch(60.995% .08 174.616);--color-success-content:oklch(12.199% .016 174.616);--color-warning:oklch(70.081% .164 56.844);--color-warning-content:oklch(14.016% .032 56.844);--color-error:oklch(53.07% .241 24.16);--color-error-content:oklch(90.614% .048 24.16);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=business]:checked),[data-theme=business]{color-scheme:dark;--color-base-100:oklch(24.353% 0 0);--color-base-200:oklch(22.648% 0 0);--color-base-300:oklch(20.944% 0 0);--color-base-content:oklch(84.87% 0 0);--color-primary:oklch(41.703% .099 251.473);--color-primary-content:oklch(88.34% .019 251.473);--color-secondary:oklch(64.092% .027 229.389);--color-secondary-content:oklch(12.818% .005 229.389);--color-accent:oklch(67.271% .167 35.791);--color-accent-content:oklch(13.454% .033 35.791);--color-neutral:oklch(27.441% .013 253.041);--color-neutral-content:oklch(85.488% .002 253.041);--color-info:oklch(62.616% .143 240.033);--color-info-content:oklch(12.523% .028 240.033);--color-success:oklch(70.226% .094 156.596);--color-success-content:oklch(14.045% .018 156.596);--color-warning:oklch(77.482% .115 81.519);--color-warning-content:oklch(15.496% .023 81.519);--color-error:oklch(51.61% .146 29.674);--color-error-content:oklch(90.322% .029 29.674);--radius-selector:0rem;--radius-field:.25rem;--radius-box:.25rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=acid]:checked),[data-theme=acid]{color-scheme:light;--color-base-100:oklch(98% 0 0);--color-base-200:oklch(95% 0 0);--color-base-300:oklch(91% 0 0);--color-base-content:oklch(0% 0 0);--color-primary:oklch(71.9% .357 330.759);--color-primary-content:oklch(14.38% .071 330.759);--color-secondary:oklch(73.37% .224 48.25);--color-secondary-content:oklch(14.674% .044 48.25);--color-accent:oklch(92.78% .264 122.962);--color-accent-content:oklch(18.556% .052 122.962);--color-neutral:oklch(21.31% .128 278.68);--color-neutral-content:oklch(84.262% .025 278.68);--color-info:oklch(60.72% .227 252.05);--color-info-content:oklch(12.144% .045 252.05);--color-success:oklch(85.72% .266 158.53);--color-success-content:oklch(17.144% .053 158.53);--color-warning:oklch(91.01% .212 100.5);--color-warning-content:oklch(18.202% .042 100.5);--color-error:oklch(64.84% .293 29.349);--color-error-content:oklch(12.968% .058 29.349);--radius-selector:1rem;--radius-field:1rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=lemonade]:checked),[data-theme=lemonade]{color-scheme:light;--color-base-100:oklch(98.71% .02 123.72);--color-base-200:oklch(91.8% .018 123.72);--color-base-300:oklch(84.89% .017 123.72);--color-base-content:oklch(19.742% .004 123.72);--color-primary:oklch(58.92% .199 134.6);--color-primary-content:oklch(11.784% .039 134.6);--color-secondary:oklch(77.75% .196 111.09);--color-secondary-content:oklch(15.55% .039 111.09);--color-accent:oklch(85.39% .201 100.73);--color-accent-content:oklch(17.078% .04 100.73);--color-neutral:oklch(30.98% .075 108.6);--color-neutral-content:oklch(86.196% .015 108.6);--color-info:oklch(86.19% .047 224.14);--color-info-content:oklch(17.238% .009 224.14);--color-success:oklch(86.19% .047 157.85);--color-success-content:oklch(17.238% .009 157.85);--color-warning:oklch(86.19% .047 102.15);--color-warning-content:oklch(17.238% .009 102.15);--color-error:oklch(86.19% .047 25.85);--color-error-content:oklch(17.238% .009 25.85);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=night]:checked),[data-theme=night]{color-scheme:dark;--color-base-100:oklch(20.768% .039 265.754);--color-base-200:oklch(19.314% .037 265.754);--color-base-300:oklch(17.86% .034 265.754);--color-base-content:oklch(84.153% .007 265.754);--color-primary:oklch(75.351% .138 232.661);--color-primary-content:oklch(15.07% .027 232.661);--color-secondary:oklch(68.011% .158 276.934);--color-secondary-content:oklch(13.602% .031 276.934);--color-accent:oklch(72.36% .176 350.048);--color-accent-content:oklch(14.472% .035 350.048);--color-neutral:oklch(27.949% .036 260.03);--color-neutral-content:oklch(85.589% .007 260.03);--color-info:oklch(68.455% .148 237.251);--color-info-content:oklch(0% 0 0);--color-success:oklch(78.452% .132 181.911);--color-success-content:oklch(15.69% .026 181.911);--color-warning:oklch(83.242% .139 82.95);--color-warning-content:oklch(16.648% .027 82.95);--color-error:oklch(71.785% .17 13.118);--color-error-content:oklch(14.357% .034 13.118);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=coffee]:checked),[data-theme=coffee]{color-scheme:dark;--color-base-100:oklch(24% .023 329.708);--color-base-200:oklch(21% .021 329.708);--color-base-300:oklch(16% .019 329.708);--color-base-content:oklch(72.354% .092 79.129);--color-primary:oklch(71.996% .123 62.756);--color-primary-content:oklch(14.399% .024 62.756);--color-secondary:oklch(34.465% .029 199.194);--color-secondary-content:oklch(86.893% .005 199.194);--color-accent:oklch(42.621% .074 224.389);--color-accent-content:oklch(88.524% .014 224.389);--color-neutral:oklch(16.51% .015 326.261);--color-neutral-content:oklch(83.302% .003 326.261);--color-info:oklch(79.49% .063 184.558);--color-info-content:oklch(15.898% .012 184.558);--color-success:oklch(74.722% .072 131.116);--color-success-content:oklch(14.944% .014 131.116);--color-warning:oklch(88.15% .14 87.722);--color-warning-content:oklch(17.63% .028 87.722);--color-error:oklch(77.318% .128 31.871);--color-error-content:oklch(15.463% .025 31.871);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=winter]:checked),[data-theme=winter]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97.466% .011 259.822);--color-base-300:oklch(93.268% .016 262.751);--color-base-content:oklch(41.886% .053 255.824);--color-primary:oklch(56.86% .255 257.57);--color-primary-content:oklch(91.372% .051 257.57);--color-secondary:oklch(42.551% .161 282.339);--color-secondary-content:oklch(88.51% .032 282.339);--color-accent:oklch(59.939% .191 335.171);--color-accent-content:oklch(11.988% .038 335.171);--color-neutral:oklch(19.616% .063 257.651);--color-neutral-content:oklch(83.923% .012 257.651);--color-info:oklch(88.127% .085 214.515);--color-info-content:oklch(17.625% .017 214.515);--color-success:oklch(80.494% .077 197.823);--color-success-content:oklch(16.098% .015 197.823);--color-warning:oklch(89.172% .045 71.47);--color-warning-content:oklch(17.834% .009 71.47);--color-error:oklch(73.092% .11 20.076);--color-error-content:oklch(14.618% .022 20.076);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=dim]:checked),[data-theme=dim]{color-scheme:dark;--color-base-100:oklch(30.857% .023 264.149);--color-base-200:oklch(28.036% .019 264.182);--color-base-300:oklch(26.346% .018 262.177);--color-base-content:oklch(82.901% .031 222.959);--color-primary:oklch(86.133% .141 139.549);--color-primary-content:oklch(17.226% .028 139.549);--color-secondary:oklch(73.375% .165 35.353);--color-secondary-content:oklch(14.675% .033 35.353);--color-accent:oklch(74.229% .133 311.379);--color-accent-content:oklch(14.845% .026 311.379);--color-neutral:oklch(24.731% .02 264.094);--color-neutral-content:oklch(82.901% .031 222.959);--color-info:oklch(86.078% .142 206.182);--color-info-content:oklch(17.215% .028 206.182);--color-success:oklch(86.171% .142 166.534);--color-success-content:oklch(17.234% .028 166.534);--color-warning:oklch(86.163% .142 94.818);--color-warning-content:oklch(17.232% .028 94.818);--color-error:oklch(82.418% .099 33.756);--color-error-content:oklch(16.483% .019 33.756);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=nord]:checked),[data-theme=nord]{color-scheme:light;--color-base-100:oklch(95.127% .007 260.731);--color-base-200:oklch(93.299% .01 261.788);--color-base-300:oklch(89.925% .016 262.749);--color-base-content:oklch(32.437% .022 264.182);--color-primary:oklch(59.435% .077 254.027);--color-primary-content:oklch(11.887% .015 254.027);--color-secondary:oklch(69.651% .059 248.687);--color-secondary-content:oklch(13.93% .011 248.687);--color-accent:oklch(77.464% .062 217.469);--color-accent-content:oklch(15.492% .012 217.469);--color-neutral:oklch(45.229% .035 264.131);--color-neutral-content:oklch(89.925% .016 262.749);--color-info:oklch(69.207% .062 332.664);--color-info-content:oklch(13.841% .012 332.664);--color-success:oklch(76.827% .074 131.063);--color-success-content:oklch(15.365% .014 131.063);--color-warning:oklch(85.486% .089 84.093);--color-warning-content:oklch(17.097% .017 84.093);--color-error:oklch(60.61% .12 15.341);--color-error-content:oklch(12.122% .024 15.341);--radius-selector:1rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=sunset]:checked),[data-theme=sunset]{color-scheme:dark;--color-base-100:oklch(22% .019 237.69);--color-base-200:oklch(20% .019 237.69);--color-base-300:oklch(18% .019 237.69);--color-base-content:oklch(77.383% .043 245.096);--color-primary:oklch(74.703% .158 39.947);--color-primary-content:oklch(14.94% .031 39.947);--color-secondary:oklch(72.537% .177 2.72);--color-secondary-content:oklch(14.507% .035 2.72);--color-accent:oklch(71.294% .166 299.844);--color-accent-content:oklch(14.258% .033 299.844);--color-neutral:oklch(26% .019 237.69);--color-neutral-content:oklch(70% .019 237.69);--color-info:oklch(85.559% .085 206.015);--color-info-content:oklch(17.111% .017 206.015);--color-success:oklch(85.56% .085 144.778);--color-success-content:oklch(17.112% .017 144.778);--color-warning:oklch(85.569% .084 74.427);--color-warning-content:oklch(17.113% .016 74.427);--color-error:oklch(85.511% .078 16.886);--color-error-content:oklch(17.102% .015 16.886);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=caramellatte]:checked),[data-theme=caramellatte]{color-scheme:light;--color-base-100:oklch(98% .016 73.684);--color-base-200:oklch(95% .038 75.164);--color-base-300:oklch(90% .076 70.697);--color-base-content:oklch(40% .123 38.172);--color-primary:oklch(0% 0 0);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(22.45% .075 37.85);--color-secondary-content:oklch(90% .076 70.697);--color-accent:oklch(46.44% .111 37.85);--color-accent-content:oklch(90% .076 70.697);--color-neutral:oklch(55% .195 38.402);--color-neutral-content:oklch(98% .016 73.684);--color-info:oklch(42% .199 265.638);--color-info-content:oklch(90% .076 70.697);--color-success:oklch(43% .095 166.913);--color-success-content:oklch(90% .076 70.697);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(39% .141 25.723);--radius-selector:2rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:1;--noise:1}:root:has(input.theme-controller[value=abyss]:checked),[data-theme=abyss]{color-scheme:dark;--color-base-100:oklch(20% .08 209);--color-base-200:oklch(15% .08 209);--color-base-300:oklch(10% .08 209);--color-base-content:oklch(90% .076 70.697);--color-primary:oklch(92% .2653 125);--color-primary-content:oklch(50% .2653 125);--color-secondary:oklch(83.27% .0764 298.3);--color-secondary-content:oklch(43.27% .0764 298.3);--color-accent:oklch(43% 0 0);--color-accent-content:oklch(98% 0 0);--color-neutral:oklch(30% .08 209);--color-neutral-content:oklch(90% .076 70.697);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(79% .209 151.711);--color-success-content:oklch(26% .065 152.934);--color-warning:oklch(84.8% .1962 84.62);--color-warning-content:oklch(44.8% .1962 84.62);--color-error:oklch(65% .1985 24.22);--color-error-content:oklch(27% .1985 24.22);--radius-selector:2rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=silk]:checked),[data-theme=silk]{color-scheme:light;--color-base-100:oklch(97% .0035 67.78);--color-base-200:oklch(95% .0081 61.42);--color-base-300:oklch(90% .0081 61.42);--color-base-content:oklch(40% .0081 61.42);--color-primary:oklch(23.27% .0249 284.3);--color-primary-content:oklch(94.22% .2505 117.44);--color-secondary:oklch(23.27% .0249 284.3);--color-secondary-content:oklch(73.92% .2135 50.94);--color-accent:oklch(23.27% .0249 284.3);--color-accent-content:oklch(88.92% .2061 189.9);--color-neutral:oklch(20% 0 0);--color-neutral-content:oklch(80% .0081 61.42);--color-info:oklch(80.39% .1148 241.68);--color-info-content:oklch(30.39% .1148 241.68);--color-success:oklch(83.92% .0901 136.87);--color-success-content:oklch(23.92% .0901 136.87);--color-warning:oklch(83.92% .1085 80);--color-warning-content:oklch(43.92% .1085 80);--color-error:oklch(75.1% .1814 22.37);--color-error-content:oklch(35.1% .1814 22.37);--radius-selector:2rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:1;--noise:0}:root{--fx-noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E");scrollbar-color:currentColor #0000}@supports (color:color-mix(in lab, red, red)){:root{scrollbar-color:color-mix(in oklch,currentColor 35%,#0000)#0000}}@property --radialprogress{syntax: ""; inherits: true; initial-value: 0%;}:root:not(span){overflow:var(--page-overflow)}:root{background:var(--page-scroll-bg,var(--root-bg));--page-scroll-bg-on:linear-gradient(var(--root-bg,#0000),var(--root-bg,#0000))var(--root-bg,#0000)}@supports (color:color-mix(in lab, red, red)){:root{--page-scroll-bg-on:linear-gradient(var(--root-bg,#0000),var(--root-bg,#0000))color-mix(in srgb,var(--root-bg,#0000),oklch(0% 0 0) calc(var(--page-has-backdrop,0)*40%))}}:root{--page-scroll-transition-on:background-color .3s ease-out;transition:var(--page-scroll-transition);scrollbar-gutter:var(--page-scroll-gutter,unset);scrollbar-gutter:if(style(--page-has-scroll: 1): var(--page-scroll-gutter,unset); else: unset)}@keyframes set-page-has-scroll{0%,to{--page-has-scroll:1}}:root,[data-theme]{background:var(--page-scroll-bg,var(--root-bg));color:var(--color-base-content)}:where(:root,[data-theme]){--root-bg:var(--color-base-100)}}@layer components;@layer utilities{@layer daisyui.l1.l2.l3{.tooltip{--tt-bg:var(--color-neutral);--tt-off:calc(100% + .5rem);--tt-tail:calc(100% + 1px + .25rem);display:inline-block;position:relative}.tooltip>.tooltip-content,.tooltip[data-tip]:before{border-radius:var(--radius-field);text-align:center;white-space:normal;max-width:20rem;color:var(--color-neutral-content);opacity:0;background-color:var(--tt-bg);pointer-events:none;z-index:2;--tw-content:attr(data-tip);content:var(--tw-content);width:max-content;padding-block:.25rem;padding-inline:.5rem;font-size:.875rem;line-height:1.25;position:absolute}.tooltip:after{opacity:0;background-color:var(--tt-bg);content:"";pointer-events:none;--mask-tooltip:url("data:image/svg+xml,%3Csvg width='10' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.500009 1C3.5 1 3.00001 4 5.00001 4C7 4 6.5 1 9.5 1C10 1 10 0.499897 10 0H0C-1.99338e-08 0.5 0 1 0.500009 1Z' fill='black'/%3E%3C/svg%3E%0A");width:.625rem;height:.25rem;-webkit-mask-position:-1px 0;mask-position:-1px 0;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-image:var(--mask-tooltip);-webkit-mask-image:var(--mask-tooltip);mask-image:var(--mask-tooltip);display:block;position:absolute}@media (prefers-reduced-motion:no-preference){.tooltip>.tooltip-content,.tooltip[data-tip]:before,.tooltip:after{transition:opacity .2s cubic-bezier(.4,0,.2,1) 75ms,transform .2s cubic-bezier(.4,0,.2,1) 75ms}}:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))>.tooltip-content,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))[data-tip]:before,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible)):after{opacity:1;--tt-pos:0rem}@media (prefers-reduced-motion:no-preference){:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))>.tooltip-content,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))[data-tip]:before,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible)):after{transition:opacity .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1)}}.menu{--menu-active-fg:var(--color-neutral-content);--menu-active-bg:var(--color-neutral);flex-flow:column wrap;width:fit-content;padding:.5rem;font-size:.875rem;display:flex}.menu :where(li ul){white-space:nowrap;margin-inline-start:1rem;padding-inline-start:.5rem;position:relative}.menu :where(li ul):before{background-color:var(--color-base-content);opacity:.1;width:var(--border);content:"";inset-inline-start:0;position:absolute;top:.75rem;bottom:.75rem}.menu :where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}.menu :where(li:not(.menu-title)>:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--radius-field);text-align:start;text-wrap:balance;-webkit-user-select:none;user-select:none;grid-auto-columns:minmax(auto,max-content) auto max-content;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:.5rem;padding-block:.375rem;padding-inline:.75rem;transition-property:color,background-color,box-shadow;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);display:grid}.menu :where(li>details>summary){--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li>details>summary){outline-offset:2px;outline:2px solid #0000}}.menu :where(li>details>summary)::-webkit-details-marker{display:none}:is(.menu :where(li>details>summary),.menu :where(li>.menu-dropdown-toggle)):after{content:"";transform-origin:50%;pointer-events:none;justify-self:flex-end;width:.375rem;height:.375rem;transition-property:rotate,translate;transition-duration:.2s;display:block;translate:0 -1px;rotate:-135deg;box-shadow:inset 2px 2px}.menu details{interpolate-size:allow-keywords;overflow:hidden}.menu details::details-content{block-size:0}@media (prefers-reduced-motion:no-preference){.menu details::details-content{transition-behavior:allow-discrete;transition-property:block-size,content-visibility;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}}.menu details[open]::details-content{block-size:auto}.menu :where(li>details[open]>summary):after,.menu :where(li>.menu-dropdown-toggle.menu-dropdown-show):after{translate:0 1px;rotate:45deg}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{color:var(--color-base-content);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{outline-offset:2px;outline:2px solid #0000}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){outline-offset:2px;outline:2px solid #0000}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){box-shadow:inset 0 1px oklch(0% 0 0/.01),inset 0 -1px oklch(100% 0 0/.01)}.menu :where(li:empty){background-color:var(--color-base-content);opacity:.1;height:1px;margin:.5rem 1rem}.menu :where(li){flex-flow:column wrap;flex-shrink:0;align-items:stretch;display:flex;position:relative}.menu :where(li) .badge{justify-self:flex-end}.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{outline-offset:2px;outline:2px solid #0000}}.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{color:var(--menu-active-fg);background-color:var(--menu-active-bg);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise)}:is(.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active):not(:is(.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active):active){box-shadow:0 2px calc(var(--depth)*3px)-2px var(--menu-active-bg)}.menu :where(li).menu-disabled{pointer-events:none;color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li).menu-disabled{color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.menu .dropdown:focus-within .menu-dropdown-toggle:after{translate:0 1px;rotate:45deg}.menu .dropdown-content{margin-top:.5rem;padding:.5rem}.menu .dropdown-content:before{display:none}.dropdown{position-area:var(--anchor-v,bottom)var(--anchor-h,span-right);display:inline-block;position:relative}.dropdown>:not(:has(~[class*=dropdown-content])):focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.dropdown>:not(:has(~[class*=dropdown-content])):focus{outline-offset:2px;outline:2px solid #0000}}.dropdown .dropdown-content{position:absolute}.dropdown.dropdown-close .dropdown-content,.dropdown:not(details,.dropdown-open,.dropdown-hover:hover,:focus-within) .dropdown-content,.dropdown.dropdown-hover:not(:hover) [tabindex]:first-child:focus:not(:focus-visible)~.dropdown-content{transform-origin:top;opacity:0;display:none;scale:95%}.dropdown[popover],.dropdown .dropdown-content{z-index:999}@media (prefers-reduced-motion:no-preference){.dropdown[popover],.dropdown .dropdown-content{transition-behavior:allow-discrete;transition-property:opacity,scale,display;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);animation:.2s dropdown}}@starting-style{.dropdown[popover],.dropdown .dropdown-content{opacity:0;scale:95%}}:is(.dropdown:not(.dropdown-close).dropdown-open,.dropdown:not(.dropdown-close):not(.dropdown-hover):focus,.dropdown:not(.dropdown-close):focus-within)>[tabindex]:first-child{pointer-events:none}:is(.dropdown:not(.dropdown-close).dropdown-open,.dropdown:not(.dropdown-close):not(.dropdown-hover):focus,.dropdown:not(.dropdown-close):focus-within) .dropdown-content,.dropdown:not(.dropdown-close).dropdown-hover:hover .dropdown-content{opacity:1;scale:100%}.dropdown:is(details) summary::-webkit-details-marker{display:none}.dropdown:where([popover]){background:0 0}.dropdown[popover]{color:inherit;position:fixed}@supports not (position-area:bottom){.dropdown[popover]{margin:auto}.dropdown[popover].dropdown-close{transform-origin:top;opacity:0;display:none;scale:95%}.dropdown[popover].dropdown-open:not(:popover-open){transform-origin:top;opacity:0;display:none;scale:95%}.dropdown[popover]::backdrop{background-color:oklab(0% none none/.3)}}:is(.dropdown[popover].dropdown-close,.dropdown[popover]:not(.dropdown-open,:popover-open)){transform-origin:top;opacity:0;display:none;scale:95%}:where(.btn){width:unset}.btn{cursor:pointer;text-align:center;vertical-align:middle;outline-offset:2px;webkit-user-select:none;-webkit-user-select:none;user-select:none;padding-inline:var(--btn-p);color:var(--btn-fg);--tw-prose-links:var(--btn-fg);height:var(--size);font-size:var(--fontsize,.875rem);outline-color:var(--btn-color,var(--color-base-content));background-color:var(--btn-bg);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--btn-noise);border-width:var(--border);border-style:solid;border-color:var(--btn-border);text-shadow:0 .5px oklch(100% 0 0/calc(var(--depth)*.15));touch-action:manipulation;box-shadow:0 .5px 0 .5px oklch(100% 0 0/calc(var(--depth)*6%))inset,var(--btn-shadow);--size:calc(var(--size-field,.25rem)*10);--btn-bg:var(--btn-color,var(--color-base-200));--btn-fg:var(--color-base-content);--btn-p:1rem;--btn-border:var(--btn-bg);border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-wrap:nowrap;flex-shrink:0;justify-content:center;align-items:center;gap:.375rem;font-weight:600;transition-property:color,background-color,border-color,box-shadow;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn{--btn-border:color-mix(in oklab,var(--btn-bg),#000 calc(var(--depth)*5%))}}.btn{--btn-shadow:0 3px 2px -2px var(--btn-bg),0 4px 3px -2px var(--btn-bg)}@supports (color:color-mix(in lab, red, red)){.btn{--btn-shadow:0 3px 2px -2px color-mix(in oklab,var(--btn-bg)calc(var(--depth)*30%),#0000),0 4px 3px -2px color-mix(in oklab,var(--btn-bg)calc(var(--depth)*30%),#0000)}}.btn{--btn-noise:var(--fx-noise)}@media (hover:hover){.btn:hover{--btn-bg:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn:hover{--btn-bg:color-mix(in oklab,var(--btn-color,var(--color-base-200)),#000 7%)}}}.btn:focus-visible,.btn:has(:focus-visible){isolation:isolate;outline-width:2px;outline-style:solid}.btn:active:not(.btn-active){--btn-bg:var(--btn-color,var(--color-base-200));translate:0 .5px}@supports (color:color-mix(in lab, red, red)){.btn:active:not(.btn-active){--btn-bg:color-mix(in oklab,var(--btn-color,var(--color-base-200)),#000 5%)}}.btn:active:not(.btn-active){--btn-border:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn:active:not(.btn-active){--btn-border:color-mix(in oklab,var(--btn-color,var(--color-base-200)),#000 7%)}}.btn:active:not(.btn-active){--btn-shadow:0 0 0 0 oklch(0% 0 0/0),0 0 0 0 oklch(0% 0 0/0)}.btn:is(input[type=checkbox],input[type=radio]){appearance:none}.btn:is(input[type=checkbox],input[type=radio])[aria-label]:after{--tw-content:attr(aria-label);content:var(--tw-content)}.btn:where(input:checked:not(.filter .btn)){--btn-color:var(--color-primary);--btn-fg:var(--color-primary-content);isolation:isolate}.collapse{border-radius:var(--radius-box,1rem);isolation:isolate;grid-template-rows:max-content 0fr;grid-template-columns:minmax(0,1fr);width:100%;display:grid;position:relative;overflow:hidden}@media (prefers-reduced-motion:no-preference){.collapse{transition:grid-template-rows .2s}}.collapse>input:is([type=checkbox],[type=radio]){appearance:none;opacity:0;z-index:1;grid-row-start:1;grid-column-start:1;width:100%;min-height:1lh;padding:1rem;padding-inline-end:3rem;transition:background-color .2s ease-out}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close)),.collapse:not(.collapse-close):has(>input:is([type=checkbox],[type=radio]):checked){grid-template-rows:max-content 1fr}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>.collapse-content,.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){content-visibility:visible;min-height:fit-content}@supports not (content-visibility:visible){.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>.collapse-content,.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){visibility:visible}}.collapse:focus-visible,.collapse:has(>input:is([type=checkbox],[type=radio]):focus-visible),.collapse:has(summary:focus-visible){outline-color:var(--color-base-content);outline-offset:2px;outline-width:2px;outline-style:solid}.collapse:not(.collapse-close)>input[type=checkbox],.collapse:not(.collapse-close)>input[type=radio]:not(:checked),.collapse:not(.collapse-close)>.collapse-title{cursor:pointer}:is(.collapse[tabindex]:focus:not(.collapse-close,.collapse[open]),.collapse[tabindex]:focus-within:not(.collapse-close,.collapse[open]))>.collapse-title{cursor:unset}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){padding-bottom:1rem}.collapse:is(details){width:100%}@media (prefers-reduced-motion:no-preference){.collapse:is(details)::details-content{transition:content-visibility .2s allow-discrete,visibility .2s allow-discrete,min-height .2s ease-out allow-discrete,padding .1s ease-out 20ms,background-color .2s ease-out,height .2s;interpolate-size:allow-keywords;height:0}.collapse:is(details):where([open])::details-content{height:auto}}.collapse:is(details) summary{display:block;position:relative}.collapse:is(details) summary::-webkit-details-marker{display:none}.collapse:is(details)>.collapse-content{content-visibility:visible}.collapse:is(details) summary{outline:none}.collapse-content{content-visibility:hidden;min-height:0;cursor:unset;grid-row-start:2;grid-column-start:1;padding-left:1rem;padding-right:1rem}@supports not (content-visibility:hidden){.collapse-content{visibility:hidden}}@media (prefers-reduced-motion:no-preference){.collapse-content{transition:content-visibility .2s allow-discrete,visibility .2s allow-discrete,min-height .2s ease-out allow-discrete,padding .1s ease-out 20ms,background-color .2s ease-out}}.validator-hint{visibility:hidden;margin-top:.5rem;font-size:.75rem}.validator:user-valid{--input-color:var(--color-success)}.validator:user-valid:focus{--input-color:var(--color-success)}.validator:user-valid:checked{--input-color:var(--color-success)}.validator:user-valid[aria-checked=true]{--input-color:var(--color-success)}.validator:user-valid:focus-within{--input-color:var(--color-success)}.validator:has(:user-valid){--input-color:var(--color-success)}.validator:has(:user-valid):focus{--input-color:var(--color-success)}.validator:has(:user-valid):checked{--input-color:var(--color-success)}.validator:has(:user-valid)[aria-checked=true]{--input-color:var(--color-success)}.validator:has(:user-valid):focus-within{--input-color:var(--color-success)}.validator:user-invalid{--input-color:var(--color-error)}.validator:user-invalid:focus{--input-color:var(--color-error)}.validator:user-invalid:checked{--input-color:var(--color-error)}.validator:user-invalid[aria-checked=true]{--input-color:var(--color-error)}.validator:user-invalid:focus-within{--input-color:var(--color-error)}.validator:user-invalid~.validator-hint{visibility:visible;color:var(--color-error)}.validator:has(:user-invalid){--input-color:var(--color-error)}.validator:has(:user-invalid):focus{--input-color:var(--color-error)}.validator:has(:user-invalid):checked{--input-color:var(--color-error)}.validator:has(:user-invalid)[aria-checked=true]{--input-color:var(--color-error)}.validator:has(:user-invalid):focus-within{--input-color:var(--color-error)}.validator:has(:user-invalid)~.validator-hint{visibility:visible;color:var(--color-error)}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))),:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):focus,:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):checked,:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))[aria-checked=true],:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):focus-within{--input-color:var(--color-error)}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))~.validator-hint{visibility:visible;color:var(--color-error)}.list{flex-direction:column;font-size:.875rem;display:flex}.list .list-row{--list-grid-cols:minmax(0,auto)1fr;border-radius:var(--radius-box);word-break:break-word;grid-auto-flow:column;grid-template-columns:var(--list-grid-cols);gap:1rem;padding:1rem;display:grid;position:relative}:is(.list>:not(:last-child).list-row,.list>:not(:last-child) .list-row):after{content:"";border-bottom:var(--border)solid;inset-inline:var(--radius-box);border-color:var(--color-base-content);position:absolute;bottom:0}@supports (color:color-mix(in lab, red, red)){:is(.list>:not(:last-child).list-row,.list>:not(:last-child) .list-row):after{border-color:color-mix(in oklab,var(--color-base-content)5%,transparent)}}.toast{translate:var(--toast-x,0)var(--toast-y,0);inset-inline:auto 1rem;background-color:#0000;flex-direction:column;gap:.5rem;width:max-content;max-width:calc(100vw - 2rem);display:flex;position:fixed;top:auto;bottom:1rem}@media (prefers-reduced-motion:no-preference){.toast>*{animation:.25s ease-out toast}}.input{cursor:text;border:var(--border)solid #0000;appearance:none;background-color:var(--color-base-100);vertical-align:middle;white-space:nowrap;width:clamp(3rem,20rem,100%);height:var(--size);font-size:max(var(--font-size,.875rem),.875rem);touch-action:manipulation;border-color:var(--input-color);box-shadow:0 1px var(--input-color)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset;border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-shrink:1;align-items:center;gap:.5rem;padding-inline:.75rem;display:inline-flex;position:relative}@supports (color:color-mix(in lab, red, red)){.input{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset}}.input{--size:calc(var(--size-field,.25rem)*10);--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.input{--input-color:color-mix(in oklab,var(--color-base-content)20%,#0000)}}.input:where(input){display:inline-flex}.input :where(input){appearance:none;background-color:#0000;border:none;width:100%;height:100%;display:inline-flex}.input :where(input):focus,.input :where(input):focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.input :where(input):focus,.input :where(input):focus-within{outline-offset:2px;outline:2px solid #0000}}.input :where(input[type=url]),.input :where(input[type=email]){direction:ltr}.input :where(input[type=date]){display:inline-flex}.input:focus,.input:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.input:focus,.input:focus-within{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)}}.input:focus,.input:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}@media (pointer:coarse){@supports (-webkit-touch-callout:none){.input:focus,.input:focus-within{--font-size:1rem}}}.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input{cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input{color:color-mix(in oklab,var(--color-base-content)40%,transparent)}}:is(.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input)::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input)::placeholder{color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input{box-shadow:none}.input:has(>input[disabled])>input[disabled]{cursor:not-allowed}.input::-webkit-date-and-time-value{text-align:inherit}.input[type=number]::-webkit-inner-spin-button{margin-block:-.75rem;margin-inline-end:-.75rem}.input::-webkit-calendar-picker-indicator{position:absolute;inset-inline-end:.75em}.input:has(>input[type=date]) :where(input[type=date]){webkit-appearance:none;appearance:none;display:inline-flex}.input:has(>input[type=date]) input[type=date]::-webkit-calendar-picker-indicator{cursor:pointer;width:1em;height:1em;position:absolute;inset-inline-end:.75em}.table{border-collapse:separate;--tw-border-spacing-x:calc(.25rem*0);--tw-border-spacing-y:calc(.25rem*0);width:100%;border-spacing:var(--tw-border-spacing-x)var(--tw-border-spacing-y);border-radius:var(--radius-box);text-align:left;font-size:.875rem;position:relative}.table:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:right}@media (hover:hover){:is(.table tr.row-hover,.table tr.row-hover:nth-child(2n)):hover{background-color:var(--color-base-200)}}.table :where(th,td){vertical-align:middle;padding-block:.75rem;padding-inline:1rem}.table :where(thead,tfoot){white-space:nowrap;color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(thead,tfoot){color:color-mix(in oklab,var(--color-base-content)60%,transparent)}}.table :where(thead,tfoot){font-size:.875rem;font-weight:600}.table :where(tfoot tr:first-child :is(td,th)){border-top:var(--border)solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(tfoot tr:first-child :is(td,th)){border-top:var(--border)solid color-mix(in oklch,var(--color-base-content)5%,#0000)}}.table :where(.table-pin-rows thead tr){z-index:1;background-color:var(--color-base-100);position:sticky;top:0}.table :where(.table-pin-rows tfoot tr){z-index:1;background-color:var(--color-base-100);position:sticky;bottom:0}.table :where(.table-pin-cols tr th){background-color:var(--color-base-100);position:sticky;left:0;right:0}.table :where(thead tr :is(td,th),tbody tr:not(:last-child) :is(td,th)){border-bottom:var(--border)solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(thead tr :is(td,th),tbody tr:not(:last-child) :is(td,th)){border-bottom:var(--border)solid color-mix(in oklch,var(--color-base-content)5%,#0000)}}.range{appearance:none;webkit-appearance:none;--range-thumb:var(--color-base-100);--range-thumb-size:calc(var(--size-selector,.25rem)*6);--range-progress:currentColor;--range-fill:1;--range-p:.25rem;--range-bg:currentColor}@supports (color:color-mix(in lab, red, red)){.range{--range-bg:color-mix(in oklab,currentColor 10%,#0000)}}.range{cursor:pointer;vertical-align:middle;--radius-selector-max:calc(var(--radius-selector) + var(--radius-selector) + var(--radius-selector));border-radius:calc(var(--radius-selector) + min(var(--range-p),var(--radius-selector-max)));width:clamp(3rem,20rem,100%);height:var(--range-thumb-size);background-color:#0000;border:none;overflow:hidden}[dir=rtl] .range{--range-dir:-1}.range:focus{outline:none}.range:focus-visible{outline-offset:2px;outline:2px solid}.range::-webkit-slider-runnable-track{background-color:var(--range-bg);border-radius:var(--radius-selector);width:100%;height:calc(var(--range-thumb-size)*.5)}@media (forced-colors:active){.range::-webkit-slider-runnable-track{border:1px solid}.range::-moz-range-track{border:1px solid}}.range::-webkit-slider-thumb{box-sizing:border-box;border-radius:calc(var(--radius-selector) + min(var(--range-p),var(--radius-selector-max)));background-color:var(--range-thumb);height:var(--range-thumb-size);width:var(--range-thumb-size);border:var(--range-p)solid;appearance:none;webkit-appearance:none;color:var(--range-progress);box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px currentColor,0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill));position:relative;top:50%;transform:translateY(-50%)}@supports (color:color-mix(in lab, red, red)){.range::-webkit-slider-thumb{box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px color-mix(in oklab,currentColor calc(var(--depth)*10%),#0000),0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill))}}.range::-moz-range-track{background-color:var(--range-bg);border-radius:var(--radius-selector);width:100%;height:calc(var(--range-thumb-size)*.5)}.range::-moz-range-thumb{box-sizing:border-box;border-radius:calc(var(--radius-selector) + min(var(--range-p),var(--radius-selector-max)));height:var(--range-thumb-size);width:var(--range-thumb-size);border:var(--range-p)solid;color:var(--range-progress);box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px currentColor,0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill));background-color:currentColor;position:relative;top:50%}@supports (color:color-mix(in lab, red, red)){.range::-moz-range-thumb{box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px color-mix(in oklab,currentColor calc(var(--depth)*10%),#0000),0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill))}}.range:disabled{cursor:not-allowed;opacity:.3}.select{border:var(--border)solid #0000;appearance:none;background-color:var(--color-base-100);vertical-align:middle;width:clamp(3rem,20rem,100%);height:var(--size);touch-action:manipulation;white-space:nowrap;text-overflow:ellipsis;box-shadow:0 1px var(--input-color)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset;background-image:linear-gradient(45deg,#0000 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,#0000 50%);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16.1px) calc(1px + 50%);background-repeat:no-repeat;background-size:4px 4px,4px 4px;border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-shrink:1;align-items:center;gap:.375rem;padding-inline:.75rem 1.75rem;font-size:.875rem;display:inline-flex;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.select{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset}}.select{border-color:var(--input-color);--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.select{--input-color:color-mix(in oklab,var(--color-base-content)20%,#0000)}}.select{--size:calc(var(--size-field,.25rem)*10)}[dir=rtl] .select{background-position:12px calc(1px + 50%),16px calc(1px + 50%)}[dir=rtl] .select::picker(select){translate:.5rem}[dir=rtl] .select select::picker(select){translate:.5rem}.select[multiple]{background-image:none;height:auto;padding-block:.75rem;padding-inline-end:.75rem;overflow:auto}.select select{appearance:none;width:calc(100% + 2.75rem);height:calc(100% - calc(var(--border)*2));background:inherit;border-radius:inherit;border-style:none;align-items:center;margin-inline:-.75rem -1.75rem;padding-inline:.75rem 1.75rem}.select select:focus,.select select:focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.select select:focus,.select select:focus-within{outline-offset:2px;outline:2px solid #0000}}.select select:not(:last-child){background-image:none;margin-inline-end:-1.375rem}.select:focus,.select:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.select:focus,.select:focus-within{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)}}.select:focus,.select:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select{cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select{color:color-mix(in oklab,var(--color-base-content)40%,transparent)}}:is(.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select)::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select)::placeholder{color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.select:has(>select[disabled])>select[disabled]{cursor:not-allowed}@supports (appearance:base-select){.select,.select select{appearance:base-select}:is(.select,.select select)::picker(select){appearance:base-select}}:is(.select,.select select)::picker(select){color:inherit;border:var(--border)solid var(--color-base-200);border-radius:var(--radius-box);background-color:inherit;max-height:min(24rem,70dvh);box-shadow:0 2px calc(var(--depth)*3px)-2px oklch(0% 0 0/.2);box-shadow:0 20px 25px -5px rgb(0 0 0/calc(var(--depth)*.1)),0 8px 10px -6px rgb(0 0 0/calc(var(--depth)*.1));margin-block:.5rem;margin-inline:.5rem;padding:.5rem;translate:-.5rem}:is(.select,.select select)::picker-icon{display:none}:is(.select,.select select) optgroup{padding-top:.5em}:is(.select,.select select) optgroup option:first-child{margin-top:.5em}:is(.select,.select select) option{border-radius:var(--radius-field);white-space:normal;padding-block:.375rem;padding-inline:.75rem;transition-property:color,background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{outline-offset:2px;outline:2px solid #0000}}:is(.select,.select select) option:not(:disabled):active{background-color:var(--color-neutral);color:var(--color-neutral-content);box-shadow:0 2px calc(var(--depth)*3px)-2px var(--color-neutral)}.swap{cursor:pointer;vertical-align:middle;webkit-user-select:none;-webkit-user-select:none;user-select:none;place-content:center;display:inline-grid;position:relative}.swap input{appearance:none;border:none}.swap>*{grid-row-start:1;grid-column-start:1}@media (prefers-reduced-motion:no-preference){.swap>*{transition-property:transform,rotate,opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}}.swap .swap-on,.swap .swap-indeterminate,.swap input:indeterminate~.swap-on,.swap input:is(:checked,:indeterminate)~.swap-off{opacity:0}.swap input:checked~.swap-on,.swap input:indeterminate~.swap-indeterminate{opacity:1;backface-visibility:visible}.collapse-title{grid-row-start:1;grid-column-start:1;width:100%;min-height:1lh;padding:1rem;padding-inline-end:3rem;transition:background-color .2s ease-out;position:relative}.checkbox{border:var(--border)solid var(--input-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.checkbox{border:var(--border)solid var(--input-color,color-mix(in oklab,var(--color-base-content)20%,#0000))}}.checkbox{cursor:pointer;appearance:none;border-radius:var(--radius-selector);vertical-align:middle;color:var(--color-base-content);box-shadow:0 1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 0 #0000 inset,0 0 #0000;--size:calc(var(--size-selector,.25rem)*6);width:var(--size);height:var(--size);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise);flex-shrink:0;padding:.25rem;transition:background-color .2s,box-shadow .2s;display:inline-block;position:relative}.checkbox:before{--tw-content:"";content:var(--tw-content);opacity:0;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,70% 80%,70% 100%);width:100%;height:100%;box-shadow:0px 3px 0 0px oklch(100% 0 0/calc(var(--depth)*.1))inset;background-color:currentColor;font-size:1rem;line-height:.75;transition:clip-path .3s .1s,opacity .1s .1s,rotate .3s .1s,translate .3s .1s;display:block;rotate:45deg}.checkbox:focus-visible{outline:2px solid var(--input-color,currentColor);outline-offset:2px}.checkbox:checked,.checkbox[aria-checked=true]{background-color:var(--input-color,#0000);box-shadow:0 0 #0000 inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px oklch(0% 0 0/calc(var(--depth)*.1))}:is(.checkbox:checked,.checkbox[aria-checked=true]):before{clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 0%,70% 0%,70% 100%);opacity:1}@media (forced-colors:active){:is(.checkbox:checked,.checkbox[aria-checked=true]):before{--tw-content:"✔︎";clip-path:none;background-color:#0000;rotate:none}}@media print{:is(.checkbox:checked,.checkbox[aria-checked=true]):before{--tw-content:"✔︎";clip-path:none;background-color:#0000;rotate:none}}.checkbox:indeterminate{background-color:var(--input-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.checkbox:indeterminate{background-color:var(--input-color,color-mix(in oklab,var(--color-base-content)20%,#0000))}}.checkbox:indeterminate:before{opacity:1;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,80% 80%,80% 100%);translate:0 -35%;rotate:none}.radio{cursor:pointer;appearance:none;vertical-align:middle;border:var(--border)solid var(--input-color,currentColor);border-radius:3.40282e38px;flex-shrink:0;padding:.25rem;display:inline-block;position:relative}@supports (color:color-mix(in lab, red, red)){.radio{border:var(--border)solid var(--input-color,color-mix(in srgb,currentColor 20%,#0000))}}.radio{box-shadow:0 1px oklch(0% 0 0/calc(var(--depth)*.1))inset;--size:calc(var(--size-selector,.25rem)*6);width:var(--size);height:var(--size);color:var(--input-color,currentColor)}.radio:before{--tw-content:"";content:var(--tw-content);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise);border-radius:3.40282e38px;width:100%;height:100%;display:block}.radio:focus-visible{outline:2px solid}.radio:checked,.radio[aria-checked=true]{background-color:var(--color-base-100);border-color:currentColor}@media (prefers-reduced-motion:no-preference){.radio:checked,.radio[aria-checked=true]{animation:.2s ease-out radio}}:is(.radio:checked,.radio[aria-checked=true]):before{box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px oklch(0% 0 0/calc(var(--depth)*.1));background-color:currentColor}@media (forced-colors:active){:is(.radio:checked,.radio[aria-checked=true]):before{outline-style:var(--tw-outline-style);outline-offset:calc(1px*-1);outline-width:1px}}@media print{:is(.radio:checked,.radio[aria-checked=true]):before{outline-offset:-1rem;outline:.25rem solid}}.rating{vertical-align:middle;display:inline-flex;position:relative}.rating input{appearance:none;border:none}.rating :where(*){background-color:var(--color-base-content);opacity:.2;border-radius:0;width:1.5rem;height:1.5rem}@media (prefers-reduced-motion:no-preference){.rating :where(*){animation:.25s ease-out rating}}.rating :where(*):is(input){cursor:pointer}.rating .rating-hidden{background-color:#0000;width:.5rem}.rating input[type=radio]:checked{background-image:none}.rating :checked,.rating [aria-checked=true],.rating [aria-current=true],.rating :has(~:checked,~[aria-checked=true],~[aria-current=true]){opacity:1}.rating :focus-visible{scale:1.1}@media (prefers-reduced-motion:no-preference){.rating :focus-visible{transition:scale .2s ease-out}}.rating :active:focus{animation:none;scale:1.1}.navbar{align-items:center;width:100%;min-height:4rem;padding:.5rem;display:flex}.stats{border-radius:var(--radius-box);grid-auto-flow:column;display:inline-grid;position:relative;overflow-x:auto}.progress{appearance:none;border-radius:var(--radius-box);background-color:currentColor;width:100%;height:.5rem;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.progress{background-color:color-mix(in oklab,currentcolor 20%,transparent)}}.progress{color:var(--color-base-content)}.progress:indeterminate{background-image:repeating-linear-gradient(90deg,currentColor -1% 10%,#0000 10% 90%);background-position-x:15%;background-size:200%}@media (prefers-reduced-motion:no-preference){.progress:indeterminate{animation:5s ease-in-out infinite progress}}@supports ((-moz-appearance:none)){.progress:indeterminate::-moz-progress-bar{background-color:#0000}@media (prefers-reduced-motion:no-preference){.progress:indeterminate::-moz-progress-bar{background-image:repeating-linear-gradient(90deg,currentColor -1% 10%,#0000 10% 90%);background-position-x:15%;background-size:200%;animation:5s ease-in-out infinite progress}}.progress::-moz-progress-bar{border-radius:var(--radius-box);background-color:currentColor}}@supports ((-webkit-appearance:none)){.progress::-webkit-progress-bar{border-radius:var(--radius-box);background-color:#0000}.progress::-webkit-progress-value{border-radius:var(--radius-box);background-color:currentColor}}.textarea{border:var(--border)solid #0000;appearance:none;border-radius:var(--radius-field);background-color:var(--color-base-100);vertical-align:middle;width:clamp(3rem,20rem,100%);min-height:5rem;font-size:max(var(--font-size,.875rem),.875rem);touch-action:manipulation;border-color:var(--input-color);box-shadow:0 1px var(--input-color)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset;flex-shrink:1;padding-block:.5rem;padding-inline:.75rem}@supports (color:color-mix(in lab, red, red)){.textarea{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset}}.textarea{--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.textarea{--input-color:color-mix(in oklab,var(--color-base-content)20%,#0000)}}.textarea textarea{appearance:none;background-color:#0000;border:none}.textarea textarea:focus,.textarea textarea:focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.textarea textarea:focus,.textarea textarea:focus-within{outline-offset:2px;outline:2px solid #0000}}.textarea:focus,.textarea:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.textarea:focus,.textarea:focus-within{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)}}.textarea:focus,.textarea:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}@media (pointer:coarse){@supports (-webkit-touch-callout:none){.textarea:focus,.textarea:focus-within{--font-size:1rem}}}.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){color:color-mix(in oklab,var(--color-base-content)40%,transparent)}}:is(.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]))::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]))::placeholder{color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){box-shadow:none}.textarea:has(>textarea[disabled])>textarea[disabled]{cursor:not-allowed}.filter{flex-wrap:wrap;display:flex}.filter input[type=radio]{width:auto}.filter input{opacity:1;transition:margin .1s,opacity .3s,padding .3s,border-width .1s;overflow:hidden;scale:1}.filter input:not(:last-child){margin-inline-end:.25rem}.filter input.filter-reset{aspect-ratio:1}.filter input.filter-reset:after{--tw-content:"×";content:var(--tw-content)}.filter:not(:has(input:checked:not(.filter-reset))) .filter-reset,.filter:not(:has(input:checked:not(.filter-reset))) input[type=reset],.filter:has(input:checked:not(.filter-reset)) input:not(:checked,.filter-reset,input[type=reset]){opacity:0;border-width:0;width:0;margin-inline:0;padding-inline:0;scale:0}.label{white-space:nowrap;color:currentColor;align-items:center;gap:.375rem;display:inline-flex}@supports (color:color-mix(in lab, red, red)){.label{color:color-mix(in oklab,currentcolor 60%,transparent)}}.label:has(input){cursor:pointer}.label:is(.input>*,.select>*){white-space:nowrap;height:calc(100% - .5rem);font-size:inherit;align-items:center;padding-inline:.75rem;display:flex}.label:is(.input>*,.select>*):first-child{border-inline-end:var(--border)solid currentColor;margin-inline:-.75rem .75rem}@supports (color:color-mix(in lab, red, red)){.label:is(.input>*,.select>*):first-child{border-inline-end:var(--border)solid color-mix(in oklab,currentColor 10%,#0000)}}.label:is(.input>*,.select>*):last-child{border-inline-start:var(--border)solid currentColor;margin-inline:.75rem -.75rem}@supports (color:color-mix(in lab, red, red)){.label:is(.input>*,.select>*):last-child{border-inline-start:var(--border)solid color-mix(in oklab,currentColor 10%,#0000)}}.fieldset-legend{color:var(--color-base-content);justify-content:space-between;align-items:center;gap:.5rem;margin-bottom:-.25rem;padding-block:.5rem;font-weight:600;display:flex}.status{aspect-ratio:1;border-radius:var(--radius-selector);background-color:var(--color-base-content);width:.5rem;height:.5rem;display:inline-block}@supports (color:color-mix(in lab, red, red)){.status{background-color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.status{vertical-align:middle;color:#0000004d;background-position:50%;background-repeat:no-repeat}@supports (color:color-mix(in lab, red, red)){.status{color:color-mix(in oklab,var(--color-black)30%,transparent)}}.status{background-image:radial-gradient(circle at 35% 30%,oklch(1 0 0/calc(var(--depth)*.5)),#0000);box-shadow:0 2px 3px -1px}@supports (color:color-mix(in lab, red, red)){.status{box-shadow:0 2px 3px -1px color-mix(in oklab,currentColor calc(var(--depth)*100%),#0000)}}.badge{border-radius:var(--radius-selector);vertical-align:middle;color:var(--badge-fg);border:var(--border)solid var(--badge-color,var(--color-base-200));background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise);background-color:var(--badge-bg);--badge-bg:var(--badge-color,var(--color-base-100));--badge-fg:var(--color-base-content);--size:calc(var(--size-selector,.25rem)*6);width:fit-content;height:var(--size);padding-inline:calc(var(--size)/2 - var(--border));justify-content:center;align-items:center;gap:.5rem;font-size:.875rem;display:inline-flex}.navbar-end{justify-content:flex-end;align-items:center;width:50%;display:inline-flex}.navbar-start{justify-content:flex-start;align-items:center;width:50%;display:inline-flex}.navbar-center{flex-shrink:0;align-items:center;display:inline-flex}.fieldset-label{color:var(--color-base-content);align-items:center;gap:.375rem;display:flex}@supports (color:color-mix(in lab, red, red)){.fieldset-label{color:color-mix(in oklab,var(--color-base-content)60%,transparent)}}.fieldset-label:has(input){cursor:pointer}.fieldset{grid-template-columns:1fr;grid-auto-rows:max-content;gap:.375rem;padding-block:.25rem;font-size:.75rem;display:grid}.mask{vertical-align:middle;display:inline-block;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.skeleton{border-radius:var(--radius-box);background-color:var(--color-base-300)}@media (prefers-reduced-motion:reduce){.skeleton{transition-duration:15s}}.skeleton{will-change:background-position;background-image:linear-gradient(105deg,#0000 0% 40%,var(--color-base-100)50%,#0000 60% 100%);background-position-x:-50%;background-size:200%}@media (prefers-reduced-motion:no-preference){.skeleton{animation:1.8s ease-in-out infinite skeleton}}.link{cursor:pointer;text-decoration-line:underline}.link:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.link:focus{outline-offset:2px;outline:2px solid #0000}}.link:focus-visible{outline-offset:2px;outline:2px solid}.btn-primary{--btn-color:var(--color-primary);--btn-fg:var(--color-primary-content)}}@layer daisyui.l1.l2{.tooltip>.tooltip-content,.tooltip[data-tip]:before{transform:translateX(-50%)translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-off)50%}.tooltip:after{transform:translateX(-50%)translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-tail)50%}.btn:disabled:not(.btn-link,.btn-ghost){background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn:disabled:not(.btn-link,.btn-ghost){background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.btn:disabled:not(.btn-link,.btn-ghost){box-shadow:none}.btn:disabled{pointer-events:none;--btn-border:#0000;--btn-noise:none;--btn-fg:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn:disabled{--btn-fg:color-mix(in oklch,var(--color-base-content)20%,#0000)}}.btn[disabled]:not(.btn-link,.btn-ghost){background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn[disabled]:not(.btn-link,.btn-ghost){background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.btn[disabled]:not(.btn-link,.btn-ghost){box-shadow:none}.btn[disabled]{pointer-events:none;--btn-border:#0000;--btn-noise:none;--btn-fg:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn[disabled]{--btn-fg:color-mix(in oklch,var(--color-base-content)20%,#0000)}}@media (prefers-reduced-motion:no-preference){.collapse[open].collapse-arrow>.collapse-title:after,.collapse.collapse-open.collapse-arrow>.collapse-title:after{transform:translateY(-50%)rotate(225deg)}}.collapse.collapse-open.collapse-plus>.collapse-title:after{--tw-content:"−";content:var(--tw-content)}:is(.collapse[tabindex].collapse-arrow:focus:not(.collapse-close),.collapse.collapse-arrow[tabindex]:focus-within:not(.collapse-close))>.collapse-title:after,.collapse.collapse-arrow:not(.collapse-close)>input:is([type=checkbox],[type=radio]):checked~.collapse-title:after{transform:translateY(-50%)rotate(225deg)}.collapse[open].collapse-plus>.collapse-title:after,.collapse[tabindex].collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse.collapse-plus:not(.collapse-close)>input:is([type=checkbox],[type=radio]):checked~.collapse-title:after{--tw-content:"−";content:var(--tw-content)}.list .list-row:has(.list-col-grow:first-child){--list-grid-cols:1fr}.list .list-row:has(.list-col-grow:nth-child(2)){--list-grid-cols:minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(3)){--list-grid-cols:minmax(0,auto)minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(4)){--list-grid-cols:minmax(0,auto)minmax(0,auto)minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(5)){--list-grid-cols:minmax(0,auto)minmax(0,auto)minmax(0,auto)minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(6)){--list-grid-cols:minmax(0,auto)minmax(0,auto)minmax(0,auto)minmax(0,auto)minmax(0,auto)1fr}.list .list-row>*{grid-row-start:1}.menu-vertical{flex-direction:column;display:inline-flex}.menu-vertical>li:not(.menu-title)>details>ul{background-color:revert-layer;border-radius:revert-layer;animation:revert-layer;box-shadow:revert-layer;margin-inline-start:1rem;margin-top:0;padding-block:0;padding-inline-end:0;transition:revert-layer;position:relative}.checkbox:disabled,.radio:disabled{cursor:not-allowed;opacity:.2}.rating.rating-xs :where(:not(.rating-hidden)){width:1rem;height:1rem}.rating.rating-sm :where(:not(.rating-hidden)){width:1.25rem;height:1.25rem}.rating.rating-md :where(:not(.rating-hidden)){width:1.5rem;height:1.5rem}.rating.rating-lg :where(:not(.rating-hidden)){width:1.75rem;height:1.75rem}.rating.rating-xl :where(:not(.rating-hidden)){width:2rem;height:2rem}:where(.navbar){position:relative}.tooltip-top>.tooltip-content,.tooltip-top[data-tip]:before{transform:translateX(-50%)translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-off)50%}.tooltip-top:after{transform:translateX(-50%)translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-tail)50%}.input-lg{--size:calc(var(--size-field,.25rem)*12);font-size:max(var(--font-size,1.125rem),1.125rem)}.input-lg[type=number]::-webkit-inner-spin-button{margin-block:-.75rem;margin-inline-end:-.75rem}.input-sm{--size:calc(var(--size-field,.25rem)*8);font-size:max(var(--font-size,.75rem),.75rem)}.input-sm[type=number]::-webkit-inner-spin-button{margin-block:-.5rem;margin-inline-end:-.75rem}.input-xl{--size:calc(var(--size-field,.25rem)*14);font-size:max(var(--font-size,1.375rem),1.375rem)}.input-xl[type=number]::-webkit-inner-spin-button{margin-block:-1rem;margin-inline-end:-.75rem}.input-xs{--size:calc(var(--size-field,.25rem)*6);font-size:max(var(--font-size,.6875rem),.6875rem)}.input-xs[type=number]::-webkit-inner-spin-button{margin-block:-.25rem;margin-inline-end:-.75rem}.select-lg{--size:calc(var(--size-field,.25rem)*12);font-size:1.125rem}.select-lg option{padding-block:.375rem;padding-inline:1rem}.select-sm{--size:calc(var(--size-field,.25rem)*8);font-size:.75rem}.select-sm option{padding-block:.25rem;padding-inline:.625rem}.select-xl{--size:calc(var(--size-field,.25rem)*14);font-size:1.375rem}.select-xl option{padding-block:.375rem;padding-inline:1.25rem}.select-xs{--size:calc(var(--size-field,.25rem)*6);font-size:.6875rem}.select-xs option{padding-block:.25rem;padding-inline:.5rem}.textarea-lg{font-size:max(var(--font-size,1.125rem),1.125rem)}.textarea-sm{font-size:max(var(--font-size,.75rem),.75rem)}.textarea-xl{font-size:max(var(--font-size,1.375rem),1.375rem)}.textarea-xs{font-size:max(var(--font-size,.6875rem),.6875rem)}.btn-sm{--fontsize:.75rem;--btn-p:.75rem;--size:calc(var(--size-field,.25rem)*8)}.range-lg{--range-thumb-size:calc(var(--size-selector,.25rem)*7)}.range-sm{--range-thumb-size:calc(var(--size-selector,.25rem)*5)}.range-xl{--range-thumb-size:calc(var(--size-selector,.25rem)*8)}.range-xs{--range-thumb-size:calc(var(--size-selector,.25rem)*4)}.textarea-error,.textarea-error:focus,.textarea-error:focus-within{--input-color:var(--color-error)}}.collapse:not(td,tr,colgroup){visibility:revert-layer}.validator:user-invalid~.validator-hint{display:revert-layer}.validator:has(:user-invalid)~.validator-hint{display:revert-layer}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))~.validator-hint{display:revert-layer}.collapse{visibility:collapse}.visible{visibility:visible}.m-5{margin:calc(var(--spacing)*5)}.mx-auto{margin-inline:auto}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-0\!{margin-top:calc(var(--spacing)*0)!important}.mt-4{margin-top:calc(var(--spacing)*4)}.mr-5{margin-right:calc(var(--spacing)*5)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.flex{display:flex}.hidden{display:none}.inline{display:inline}.table{display:table}.h-6{height:calc(var(--spacing)*6)}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-screen{min-height:100vh}.w-16{width:calc(var(--spacing)*16)}.w-20{width:calc(var(--spacing)*20)}.w-56{width:calc(var(--spacing)*56)}.w-auto{width:auto}.w-full{width:100%}.max-w-7{max-width:calc(var(--spacing)*7)}.max-w-lg{max-width:var(--container-lg)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:calc(var(--spacing)*0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.gap-0{gap:calc(var(--spacing)*0)}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-1{gap:calc(var(--spacing)*1)}.gap-3{gap:calc(var(--spacing)*3)}.gap-6{gap:calc(var(--spacing)*6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-base-300{border-color:var(--color-base-300)}.bg-base-100{background-color:var(--color-base-100)}.bg-base-200{background-color:var(--color-base-200)}.bg-primary{background-color:var(--color-primary)}.p-0{padding:calc(var(--spacing)*0)}.p-0\!{padding:calc(var(--spacing)*0)!important}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-base-content,.text-base-content\/80{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.text-base-content\/80{color:color-mix(in oklab,var(--color-base-content)80%,transparent)}}.text-error{color:var(--color-error)}.text-primary{color:var(--color-primary)}.text-primary-content{color:var(--color-primary-content)}.no-underline{text-decoration-line:none}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@layer daisyui.l1{.btn-ghost:not(.btn-active,:hover,:active:focus,:focus-visible,input:checked:not(.filter .btn)){--btn-shadow:"";--btn-bg:#0000;--btn-border:#0000;--btn-noise:none}.btn-ghost:not(.btn-active,:hover,:active:focus,:focus-visible,input:checked:not(.filter .btn)):not(:disabled,[disabled],.btn-disabled){--btn-fg:var(--btn-color,currentColor);outline-color:currentColor}@media (hover:none){.btn-ghost:not(.btn-active,:active,:focus-visible,input:checked:not(.filter .btn)):hover{--btn-shadow:"";--btn-bg:#0000;--btn-fg:var(--btn-color,currentColor);--btn-border:#0000;--btn-noise:none;outline-color:currentColor}}}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}@media (hover:hover){.hover\:bg-base-200:hover{background-color:var(--color-base-200)}.hover\:text-base-content:hover{color:var(--color-base-content)}}.active\:bg-base-200:active{background-color:var(--color-base-200)}.\[\&\>svg\]\:h-5>svg{height:calc(var(--spacing)*5)}.\[\&\>svg\]\:w-5>svg{width:calc(var(--spacing)*5)}}@keyframes rating{0%,40%{filter:brightness(1.05)contrast(1.05);scale:1.1}}@keyframes dropdown{0%{opacity:0}}@keyframes radio{0%{padding:5px}50%{padding:3px}}@keyframes toast{0%{opacity:0;scale:.9}to{opacity:1;scale:1}}@keyframes rotator{89.9999%,to{--first-item-position:0 0%}90%,99.9999%{--first-item-position:0 calc(var(--items)*100%)}to{translate:0 -100%}}@keyframes skeleton{0%{background-position:150%}to{background-position:-50%}}@keyframes menu{0%{opacity:0}}@keyframes progress{50%{background-position-x:-115%}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false} \ No newline at end of file +/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-black:#000;--spacing:.25rem;--container-xs:20rem;--container-lg:32rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(98% 0 0);--color-base-300:oklch(95% 0 0);--color-base-content:oklch(21% .006 285.885);--color-primary:oklch(45% .24 277.023);--color-primary-content:oklch(93% .034 272.788);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}@media (prefers-color-scheme:dark){:root:not([data-theme]){color-scheme:dark;--color-base-100:oklch(25.33% .016 252.42);--color-base-200:oklch(23.26% .014 253.1);--color-base-300:oklch(21.15% .012 254.09);--color-base-content:oklch(97.807% .029 256.847);--color-primary:oklch(58% .233 277.117);--color-primary-content:oklch(96% .018 272.314);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}}:root:has(input.theme-controller[value=light]:checked),[data-theme=light]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(98% 0 0);--color-base-300:oklch(95% 0 0);--color-base-content:oklch(21% .006 285.885);--color-primary:oklch(45% .24 277.023);--color-primary-content:oklch(93% .034 272.788);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=dark]:checked),[data-theme=dark]{color-scheme:dark;--color-base-100:oklch(25.33% .016 252.42);--color-base-200:oklch(23.26% .014 253.1);--color-base-300:oklch(21.15% .012 254.09);--color-base-content:oklch(97.807% .029 256.847);--color-primary:oklch(58% .233 277.117);--color-primary-content:oklch(96% .018 272.314);--color-secondary:oklch(65% .241 354.308);--color-secondary-content:oklch(94% .028 342.258);--color-accent:oklch(77% .152 181.912);--color-accent-content:oklch(38% .063 188.416);--color-neutral:oklch(14% .005 285.823);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(71% .194 13.428);--color-error-content:oklch(27% .105 12.094);--radius-selector:.5rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=cupcake]:checked),[data-theme=cupcake]{color-scheme:light;--color-base-100:oklch(97.788% .004 56.375);--color-base-200:oklch(93.982% .007 61.449);--color-base-300:oklch(91.586% .006 53.44);--color-base-content:oklch(23.574% .066 313.189);--color-primary:oklch(85% .138 181.071);--color-primary-content:oklch(43% .078 188.216);--color-secondary:oklch(89% .061 343.231);--color-secondary-content:oklch(45% .187 3.815);--color-accent:oklch(90% .076 70.697);--color-accent-content:oklch(47% .157 37.304);--color-neutral:oklch(27% .006 286.033);--color-neutral-content:oklch(92% .004 286.32);--color-info:oklch(68% .169 237.323);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(69% .17 162.48);--color-success-content:oklch(26% .051 172.552);--color-warning:oklch(79% .184 86.047);--color-warning-content:oklch(28% .066 53.813);--color-error:oklch(64% .246 16.439);--color-error-content:oklch(27% .105 12.094);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:1;--noise:0}:root:has(input.theme-controller[value=bumblebee]:checked),[data-theme=bumblebee]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97% 0 0);--color-base-300:oklch(92% 0 0);--color-base-content:oklch(20% 0 0);--color-primary:oklch(85% .199 91.936);--color-primary-content:oklch(42% .095 57.708);--color-secondary:oklch(75% .183 55.934);--color-secondary-content:oklch(40% .123 38.172);--color-accent:oklch(0% 0 0);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(37% .01 67.558);--color-neutral-content:oklch(92% .003 48.717);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(39% .09 240.876);--color-success:oklch(76% .177 163.223);--color-success-content:oklch(37% .077 168.94);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(39% .141 25.723);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=emerald]:checked),[data-theme=emerald]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(93% 0 0);--color-base-300:oklch(86% 0 0);--color-base-content:oklch(35.519% .032 262.988);--color-primary:oklch(76.662% .135 153.45);--color-primary-content:oklch(33.387% .04 162.24);--color-secondary:oklch(61.302% .202 261.294);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(72.772% .149 33.2);--color-accent-content:oklch(0% 0 0);--color-neutral:oklch(35.519% .032 262.988);--color-neutral-content:oklch(98.462% .001 247.838);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=corporate]:checked),[data-theme=corporate]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(93% 0 0);--color-base-300:oklch(86% 0 0);--color-base-content:oklch(22.389% .031 278.072);--color-primary:oklch(58% .158 241.966);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(55% .046 257.417);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(60% .118 184.704);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(0% 0 0);--color-neutral-content:oklch(100% 0 0);--color-info:oklch(60% .126 221.723);--color-info-content:oklch(100% 0 0);--color-success:oklch(62% .194 149.214);--color-success-content:oklch(100% 0 0);--color-warning:oklch(85% .199 91.936);--color-warning-content:oklch(0% 0 0);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(0% 0 0);--radius-selector:.25rem;--radius-field:.25rem;--radius-box:.25rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=synthwave]:checked),[data-theme=synthwave]{color-scheme:dark;--color-base-100:oklch(15% .09 281.288);--color-base-200:oklch(20% .09 281.288);--color-base-300:oklch(25% .09 281.288);--color-base-content:oklch(78% .115 274.713);--color-primary:oklch(71% .202 349.761);--color-primary-content:oklch(28% .109 3.907);--color-secondary:oklch(82% .111 230.318);--color-secondary-content:oklch(29% .066 243.157);--color-accent:oklch(75% .183 55.934);--color-accent-content:oklch(26% .079 36.259);--color-neutral:oklch(45% .24 277.023);--color-neutral-content:oklch(87% .065 274.039);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(77% .152 181.912);--color-success-content:oklch(27% .046 192.524);--color-warning:oklch(90% .182 98.111);--color-warning-content:oklch(42% .095 57.708);--color-error:oklch(73.7% .121 32.639);--color-error-content:oklch(23.501% .096 290.329);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=retro]:checked),[data-theme=retro]{color-scheme:light;--color-base-100:oklch(91.637% .034 90.515);--color-base-200:oklch(88.272% .049 91.774);--color-base-300:oklch(84.133% .065 90.856);--color-base-content:oklch(41% .112 45.904);--color-primary:oklch(80% .114 19.571);--color-primary-content:oklch(39% .141 25.723);--color-secondary:oklch(92% .084 155.995);--color-secondary-content:oklch(44% .119 151.328);--color-accent:oklch(68% .162 75.834);--color-accent-content:oklch(41% .112 45.904);--color-neutral:oklch(44% .011 73.639);--color-neutral-content:oklch(86% .005 56.366);--color-info:oklch(58% .158 241.966);--color-info-content:oklch(96% .059 95.617);--color-success:oklch(51% .096 186.391);--color-success-content:oklch(96% .059 95.617);--color-warning:oklch(64% .222 41.116);--color-warning-content:oklch(96% .059 95.617);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(40% .123 38.172);--radius-selector:.25rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=cyberpunk]:checked),[data-theme=cyberpunk]{color-scheme:light;--color-base-100:oklch(94.51% .179 104.32);--color-base-200:oklch(91.51% .179 104.32);--color-base-300:oklch(85.51% .179 104.32);--color-base-content:oklch(0% 0 0);--color-primary:oklch(74.22% .209 6.35);--color-primary-content:oklch(14.844% .041 6.35);--color-secondary:oklch(83.33% .184 204.72);--color-secondary-content:oklch(16.666% .036 204.72);--color-accent:oklch(71.86% .217 310.43);--color-accent-content:oklch(14.372% .043 310.43);--color-neutral:oklch(23.04% .065 269.31);--color-neutral-content:oklch(94.51% .179 104.32);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:0rem;--radius-field:0rem;--radius-box:0rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=valentine]:checked),[data-theme=valentine]{color-scheme:light;--color-base-100:oklch(97% .014 343.198);--color-base-200:oklch(94% .028 342.258);--color-base-300:oklch(89% .061 343.231);--color-base-content:oklch(52% .223 3.958);--color-primary:oklch(65% .241 354.308);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(62% .265 303.9);--color-secondary-content:oklch(97% .014 308.299);--color-accent:oklch(82% .111 230.318);--color-accent-content:oklch(39% .09 240.876);--color-neutral:oklch(40% .153 2.432);--color-neutral-content:oklch(89% .061 343.231);--color-info:oklch(86% .127 207.078);--color-info-content:oklch(44% .11 240.79);--color-success:oklch(84% .143 164.978);--color-success-content:oklch(43% .095 166.913);--color-warning:oklch(75% .183 55.934);--color-warning-content:oklch(26% .079 36.259);--color-error:oklch(63% .237 25.331);--color-error-content:oklch(97% .013 17.38);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=halloween]:checked),[data-theme=halloween]{color-scheme:dark;--color-base-100:oklch(21% .006 56.043);--color-base-200:oklch(14% .004 49.25);--color-base-300:oklch(0% 0 0);--color-base-content:oklch(84.955% 0 0);--color-primary:oklch(77.48% .204 60.62);--color-primary-content:oklch(19.693% .004 196.779);--color-secondary:oklch(45.98% .248 305.03);--color-secondary-content:oklch(89.196% .049 305.03);--color-accent:oklch(64.8% .223 136.073);--color-accent-content:oklch(0% 0 0);--color-neutral:oklch(24.371% .046 65.681);--color-neutral-content:oklch(84.874% .009 65.681);--color-info:oklch(54.615% .215 262.88);--color-info-content:oklch(90.923% .043 262.88);--color-success:oklch(62.705% .169 149.213);--color-success-content:oklch(12.541% .033 149.213);--color-warning:oklch(66.584% .157 58.318);--color-warning-content:oklch(13.316% .031 58.318);--color-error:oklch(65.72% .199 27.33);--color-error-content:oklch(13.144% .039 27.33);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=garden]:checked),[data-theme=garden]{color-scheme:light;--color-base-100:oklch(92.951% .002 17.197);--color-base-200:oklch(86.445% .002 17.197);--color-base-300:oklch(79.938% .001 17.197);--color-base-content:oklch(16.961% .001 17.32);--color-primary:oklch(62.45% .278 3.836);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(48.495% .11 355.095);--color-secondary-content:oklch(89.699% .022 355.095);--color-accent:oklch(56.273% .054 154.39);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(24.155% .049 89.07);--color-neutral-content:oklch(92.951% .002 17.197);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=forest]:checked),[data-theme=forest]{color-scheme:dark;--color-base-100:oklch(20.84% .008 17.911);--color-base-200:oklch(18.522% .007 17.911);--color-base-300:oklch(16.203% .007 17.911);--color-base-content:oklch(83.768% .001 17.911);--color-primary:oklch(68.628% .185 148.958);--color-primary-content:oklch(0% 0 0);--color-secondary:oklch(69.776% .135 168.327);--color-secondary-content:oklch(13.955% .027 168.327);--color-accent:oklch(70.628% .119 185.713);--color-accent-content:oklch(14.125% .023 185.713);--color-neutral:oklch(30.698% .039 171.364);--color-neutral-content:oklch(86.139% .007 171.364);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=aqua]:checked),[data-theme=aqua]{color-scheme:dark;--color-base-100:oklch(37% .146 265.522);--color-base-200:oklch(28% .091 267.935);--color-base-300:oklch(22% .091 267.935);--color-base-content:oklch(90% .058 230.902);--color-primary:oklch(85.661% .144 198.645);--color-primary-content:oklch(40.124% .068 197.603);--color-secondary:oklch(60.682% .108 309.782);--color-secondary-content:oklch(96% .016 293.756);--color-accent:oklch(93.426% .102 94.555);--color-accent-content:oklch(18.685% .02 94.555);--color-neutral:oklch(27% .146 265.522);--color-neutral-content:oklch(80% .146 265.522);--color-info:oklch(54.615% .215 262.88);--color-info-content:oklch(90.923% .043 262.88);--color-success:oklch(62.705% .169 149.213);--color-success-content:oklch(12.541% .033 149.213);--color-warning:oklch(66.584% .157 58.318);--color-warning-content:oklch(27% .077 45.635);--color-error:oklch(73.95% .19 27.33);--color-error-content:oklch(14.79% .038 27.33);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=lofi]:checked),[data-theme=lofi]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97% 0 0);--color-base-300:oklch(94% 0 0);--color-base-content:oklch(0% 0 0);--color-primary:oklch(15.906% 0 0);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(21.455% .001 17.278);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(26.861% 0 0);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(0% 0 0);--color-neutral-content:oklch(100% 0 0);--color-info:oklch(79.54% .103 205.9);--color-info-content:oklch(15.908% .02 205.9);--color-success:oklch(90.13% .153 164.14);--color-success-content:oklch(18.026% .03 164.14);--color-warning:oklch(88.37% .135 79.94);--color-warning-content:oklch(17.674% .027 79.94);--color-error:oklch(78.66% .15 28.47);--color-error-content:oklch(15.732% .03 28.47);--radius-selector:2rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=pastel]:checked),[data-theme=pastel]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(98.462% .001 247.838);--color-base-300:oklch(92.462% .001 247.838);--color-base-content:oklch(20% 0 0);--color-primary:oklch(90% .063 306.703);--color-primary-content:oklch(49% .265 301.924);--color-secondary:oklch(89% .058 10.001);--color-secondary-content:oklch(51% .222 16.935);--color-accent:oklch(90% .093 164.15);--color-accent-content:oklch(50% .118 165.612);--color-neutral:oklch(55% .046 257.417);--color-neutral-content:oklch(92% .013 255.508);--color-info:oklch(86% .127 207.078);--color-info-content:oklch(52% .105 223.128);--color-success:oklch(87% .15 154.449);--color-success-content:oklch(52% .154 150.069);--color-warning:oklch(83% .128 66.29);--color-warning-content:oklch(55% .195 38.402);--color-error:oklch(80% .114 19.571);--color-error-content:oklch(50% .213 27.518);--radius-selector:1rem;--radius-field:2rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:0;--noise:0}:root:has(input.theme-controller[value=fantasy]:checked),[data-theme=fantasy]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(93% 0 0);--color-base-300:oklch(86% 0 0);--color-base-content:oklch(27.807% .029 256.847);--color-primary:oklch(37.45% .189 325.02);--color-primary-content:oklch(87.49% .037 325.02);--color-secondary:oklch(53.92% .162 241.36);--color-secondary-content:oklch(90.784% .032 241.36);--color-accent:oklch(75.98% .204 56.72);--color-accent-content:oklch(15.196% .04 56.72);--color-neutral:oklch(27.807% .029 256.847);--color-neutral-content:oklch(85.561% .005 256.847);--color-info:oklch(72.06% .191 231.6);--color-info-content:oklch(0% 0 0);--color-success:oklch(64.8% .15 160);--color-success-content:oklch(0% 0 0);--color-warning:oklch(84.71% .199 83.87);--color-warning-content:oklch(0% 0 0);--color-error:oklch(71.76% .221 22.18);--color-error-content:oklch(0% 0 0);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=wireframe]:checked),[data-theme=wireframe]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97% 0 0);--color-base-300:oklch(94% 0 0);--color-base-content:oklch(20% 0 0);--color-primary:oklch(87% 0 0);--color-primary-content:oklch(26% 0 0);--color-secondary:oklch(87% 0 0);--color-secondary-content:oklch(26% 0 0);--color-accent:oklch(87% 0 0);--color-accent-content:oklch(26% 0 0);--color-neutral:oklch(87% 0 0);--color-neutral-content:oklch(26% 0 0);--color-info:oklch(44% .11 240.79);--color-info-content:oklch(90% .058 230.902);--color-success:oklch(43% .095 166.913);--color-success-content:oklch(90% .093 164.15);--color-warning:oklch(47% .137 46.201);--color-warning-content:oklch(92% .12 95.746);--color-error:oklch(44% .177 26.899);--color-error-content:oklch(88% .062 18.334);--radius-selector:0rem;--radius-field:.25rem;--radius-box:.25rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=black]:checked),[data-theme=black]{color-scheme:dark;--color-base-100:oklch(0% 0 0);--color-base-200:oklch(19% 0 0);--color-base-300:oklch(22% 0 0);--color-base-content:oklch(87.609% 0 0);--color-primary:oklch(35% 0 0);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(35% 0 0);--color-secondary-content:oklch(100% 0 0);--color-accent:oklch(35% 0 0);--color-accent-content:oklch(100% 0 0);--color-neutral:oklch(35% 0 0);--color-neutral-content:oklch(100% 0 0);--color-info:oklch(45.201% .313 264.052);--color-info-content:oklch(89.04% .062 264.052);--color-success:oklch(51.975% .176 142.495);--color-success-content:oklch(90.395% .035 142.495);--color-warning:oklch(96.798% .211 109.769);--color-warning-content:oklch(19.359% .042 109.769);--color-error:oklch(62.795% .257 29.233);--color-error-content:oklch(12.559% .051 29.233);--radius-selector:0rem;--radius-field:0rem;--radius-box:0rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=luxury]:checked),[data-theme=luxury]{color-scheme:dark;--color-base-100:oklch(14.076% .004 285.822);--color-base-200:oklch(20.219% .004 308.229);--color-base-300:oklch(23.219% .004 308.229);--color-base-content:oklch(75.687% .123 76.89);--color-primary:oklch(100% 0 0);--color-primary-content:oklch(20% 0 0);--color-secondary:oklch(27.581% .064 261.069);--color-secondary-content:oklch(85.516% .012 261.069);--color-accent:oklch(36.674% .051 338.825);--color-accent-content:oklch(87.334% .01 338.825);--color-neutral:oklch(24.27% .057 59.825);--color-neutral-content:oklch(93.203% .089 90.861);--color-info:oklch(79.061% .121 237.133);--color-info-content:oklch(15.812% .024 237.133);--color-success:oklch(78.119% .192 132.154);--color-success-content:oklch(15.623% .038 132.154);--color-warning:oklch(86.127% .136 102.891);--color-warning-content:oklch(17.225% .027 102.891);--color-error:oklch(71.753% .176 22.568);--color-error-content:oklch(14.35% .035 22.568);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=dracula]:checked),[data-theme=dracula]{color-scheme:dark;--color-base-100:oklch(28.822% .022 277.508);--color-base-200:oklch(26.805% .02 277.508);--color-base-300:oklch(24.787% .019 277.508);--color-base-content:oklch(97.747% .007 106.545);--color-primary:oklch(75.461% .183 346.812);--color-primary-content:oklch(15.092% .036 346.812);--color-secondary:oklch(74.202% .148 301.883);--color-secondary-content:oklch(14.84% .029 301.883);--color-accent:oklch(83.392% .124 66.558);--color-accent-content:oklch(16.678% .024 66.558);--color-neutral:oklch(39.445% .032 275.524);--color-neutral-content:oklch(87.889% .006 275.524);--color-info:oklch(88.263% .093 212.846);--color-info-content:oklch(17.652% .018 212.846);--color-success:oklch(87.099% .219 148.024);--color-success-content:oklch(17.419% .043 148.024);--color-warning:oklch(95.533% .134 112.757);--color-warning-content:oklch(19.106% .026 112.757);--color-error:oklch(68.22% .206 24.43);--color-error-content:oklch(13.644% .041 24.43);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=cmyk]:checked),[data-theme=cmyk]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(95% 0 0);--color-base-300:oklch(90% 0 0);--color-base-content:oklch(20% 0 0);--color-primary:oklch(71.772% .133 239.443);--color-primary-content:oklch(14.354% .026 239.443);--color-secondary:oklch(64.476% .202 359.339);--color-secondary-content:oklch(12.895% .04 359.339);--color-accent:oklch(94.228% .189 105.306);--color-accent-content:oklch(18.845% .037 105.306);--color-neutral:oklch(21.778% 0 0);--color-neutral-content:oklch(84.355% 0 0);--color-info:oklch(68.475% .094 217.284);--color-info-content:oklch(13.695% .018 217.284);--color-success:oklch(46.949% .162 321.406);--color-success-content:oklch(89.389% .032 321.406);--color-warning:oklch(71.236% .159 52.023);--color-warning-content:oklch(14.247% .031 52.023);--color-error:oklch(62.013% .208 28.717);--color-error-content:oklch(12.402% .041 28.717);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=autumn]:checked),[data-theme=autumn]{color-scheme:light;--color-base-100:oklch(95.814% 0 0);--color-base-200:oklch(89.107% 0 0);--color-base-300:oklch(82.4% 0 0);--color-base-content:oklch(19.162% 0 0);--color-primary:oklch(40.723% .161 17.53);--color-primary-content:oklch(88.144% .032 17.53);--color-secondary:oklch(61.676% .169 23.865);--color-secondary-content:oklch(12.335% .033 23.865);--color-accent:oklch(73.425% .094 60.729);--color-accent-content:oklch(14.685% .018 60.729);--color-neutral:oklch(54.367% .037 51.902);--color-neutral-content:oklch(90.873% .007 51.902);--color-info:oklch(69.224% .097 207.284);--color-info-content:oklch(13.844% .019 207.284);--color-success:oklch(60.995% .08 174.616);--color-success-content:oklch(12.199% .016 174.616);--color-warning:oklch(70.081% .164 56.844);--color-warning-content:oklch(14.016% .032 56.844);--color-error:oklch(53.07% .241 24.16);--color-error-content:oklch(90.614% .048 24.16);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=business]:checked),[data-theme=business]{color-scheme:dark;--color-base-100:oklch(24.353% 0 0);--color-base-200:oklch(22.648% 0 0);--color-base-300:oklch(20.944% 0 0);--color-base-content:oklch(84.87% 0 0);--color-primary:oklch(41.703% .099 251.473);--color-primary-content:oklch(88.34% .019 251.473);--color-secondary:oklch(64.092% .027 229.389);--color-secondary-content:oklch(12.818% .005 229.389);--color-accent:oklch(67.271% .167 35.791);--color-accent-content:oklch(13.454% .033 35.791);--color-neutral:oklch(27.441% .013 253.041);--color-neutral-content:oklch(85.488% .002 253.041);--color-info:oklch(62.616% .143 240.033);--color-info-content:oklch(12.523% .028 240.033);--color-success:oklch(70.226% .094 156.596);--color-success-content:oklch(14.045% .018 156.596);--color-warning:oklch(77.482% .115 81.519);--color-warning-content:oklch(15.496% .023 81.519);--color-error:oklch(51.61% .146 29.674);--color-error-content:oklch(90.322% .029 29.674);--radius-selector:0rem;--radius-field:.25rem;--radius-box:.25rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=acid]:checked),[data-theme=acid]{color-scheme:light;--color-base-100:oklch(98% 0 0);--color-base-200:oklch(95% 0 0);--color-base-300:oklch(91% 0 0);--color-base-content:oklch(0% 0 0);--color-primary:oklch(71.9% .357 330.759);--color-primary-content:oklch(14.38% .071 330.759);--color-secondary:oklch(73.37% .224 48.25);--color-secondary-content:oklch(14.674% .044 48.25);--color-accent:oklch(92.78% .264 122.962);--color-accent-content:oklch(18.556% .052 122.962);--color-neutral:oklch(21.31% .128 278.68);--color-neutral-content:oklch(84.262% .025 278.68);--color-info:oklch(60.72% .227 252.05);--color-info-content:oklch(12.144% .045 252.05);--color-success:oklch(85.72% .266 158.53);--color-success-content:oklch(17.144% .053 158.53);--color-warning:oklch(91.01% .212 100.5);--color-warning-content:oklch(18.202% .042 100.5);--color-error:oklch(64.84% .293 29.349);--color-error-content:oklch(12.968% .058 29.349);--radius-selector:1rem;--radius-field:1rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=lemonade]:checked),[data-theme=lemonade]{color-scheme:light;--color-base-100:oklch(98.71% .02 123.72);--color-base-200:oklch(91.8% .018 123.72);--color-base-300:oklch(84.89% .017 123.72);--color-base-content:oklch(19.742% .004 123.72);--color-primary:oklch(58.92% .199 134.6);--color-primary-content:oklch(11.784% .039 134.6);--color-secondary:oklch(77.75% .196 111.09);--color-secondary-content:oklch(15.55% .039 111.09);--color-accent:oklch(85.39% .201 100.73);--color-accent-content:oklch(17.078% .04 100.73);--color-neutral:oklch(30.98% .075 108.6);--color-neutral-content:oklch(86.196% .015 108.6);--color-info:oklch(86.19% .047 224.14);--color-info-content:oklch(17.238% .009 224.14);--color-success:oklch(86.19% .047 157.85);--color-success-content:oklch(17.238% .009 157.85);--color-warning:oklch(86.19% .047 102.15);--color-warning-content:oklch(17.238% .009 102.15);--color-error:oklch(86.19% .047 25.85);--color-error-content:oklch(17.238% .009 25.85);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=night]:checked),[data-theme=night]{color-scheme:dark;--color-base-100:oklch(20.768% .039 265.754);--color-base-200:oklch(19.314% .037 265.754);--color-base-300:oklch(17.86% .034 265.754);--color-base-content:oklch(84.153% .007 265.754);--color-primary:oklch(75.351% .138 232.661);--color-primary-content:oklch(15.07% .027 232.661);--color-secondary:oklch(68.011% .158 276.934);--color-secondary-content:oklch(13.602% .031 276.934);--color-accent:oklch(72.36% .176 350.048);--color-accent-content:oklch(14.472% .035 350.048);--color-neutral:oklch(27.949% .036 260.03);--color-neutral-content:oklch(85.589% .007 260.03);--color-info:oklch(68.455% .148 237.251);--color-info-content:oklch(0% 0 0);--color-success:oklch(78.452% .132 181.911);--color-success-content:oklch(15.69% .026 181.911);--color-warning:oklch(83.242% .139 82.95);--color-warning-content:oklch(16.648% .027 82.95);--color-error:oklch(71.785% .17 13.118);--color-error-content:oklch(14.357% .034 13.118);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=coffee]:checked),[data-theme=coffee]{color-scheme:dark;--color-base-100:oklch(24% .023 329.708);--color-base-200:oklch(21% .021 329.708);--color-base-300:oklch(16% .019 329.708);--color-base-content:oklch(72.354% .092 79.129);--color-primary:oklch(71.996% .123 62.756);--color-primary-content:oklch(14.399% .024 62.756);--color-secondary:oklch(34.465% .029 199.194);--color-secondary-content:oklch(86.893% .005 199.194);--color-accent:oklch(42.621% .074 224.389);--color-accent-content:oklch(88.524% .014 224.389);--color-neutral:oklch(16.51% .015 326.261);--color-neutral-content:oklch(83.302% .003 326.261);--color-info:oklch(79.49% .063 184.558);--color-info-content:oklch(15.898% .012 184.558);--color-success:oklch(74.722% .072 131.116);--color-success-content:oklch(14.944% .014 131.116);--color-warning:oklch(88.15% .14 87.722);--color-warning-content:oklch(17.63% .028 87.722);--color-error:oklch(77.318% .128 31.871);--color-error-content:oklch(15.463% .025 31.871);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=winter]:checked),[data-theme=winter]{color-scheme:light;--color-base-100:oklch(100% 0 0);--color-base-200:oklch(97.466% .011 259.822);--color-base-300:oklch(93.268% .016 262.751);--color-base-content:oklch(41.886% .053 255.824);--color-primary:oklch(56.86% .255 257.57);--color-primary-content:oklch(91.372% .051 257.57);--color-secondary:oklch(42.551% .161 282.339);--color-secondary-content:oklch(88.51% .032 282.339);--color-accent:oklch(59.939% .191 335.171);--color-accent-content:oklch(11.988% .038 335.171);--color-neutral:oklch(19.616% .063 257.651);--color-neutral-content:oklch(83.923% .012 257.651);--color-info:oklch(88.127% .085 214.515);--color-info-content:oklch(17.625% .017 214.515);--color-success:oklch(80.494% .077 197.823);--color-success-content:oklch(16.098% .015 197.823);--color-warning:oklch(89.172% .045 71.47);--color-warning-content:oklch(17.834% .009 71.47);--color-error:oklch(73.092% .11 20.076);--color-error-content:oklch(14.618% .022 20.076);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=dim]:checked),[data-theme=dim]{color-scheme:dark;--color-base-100:oklch(30.857% .023 264.149);--color-base-200:oklch(28.036% .019 264.182);--color-base-300:oklch(26.346% .018 262.177);--color-base-content:oklch(82.901% .031 222.959);--color-primary:oklch(86.133% .141 139.549);--color-primary-content:oklch(17.226% .028 139.549);--color-secondary:oklch(73.375% .165 35.353);--color-secondary-content:oklch(14.675% .033 35.353);--color-accent:oklch(74.229% .133 311.379);--color-accent-content:oklch(14.845% .026 311.379);--color-neutral:oklch(24.731% .02 264.094);--color-neutral-content:oklch(82.901% .031 222.959);--color-info:oklch(86.078% .142 206.182);--color-info-content:oklch(17.215% .028 206.182);--color-success:oklch(86.171% .142 166.534);--color-success-content:oklch(17.234% .028 166.534);--color-warning:oklch(86.163% .142 94.818);--color-warning-content:oklch(17.232% .028 94.818);--color-error:oklch(82.418% .099 33.756);--color-error-content:oklch(16.483% .019 33.756);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=nord]:checked),[data-theme=nord]{color-scheme:light;--color-base-100:oklch(95.127% .007 260.731);--color-base-200:oklch(93.299% .01 261.788);--color-base-300:oklch(89.925% .016 262.749);--color-base-content:oklch(32.437% .022 264.182);--color-primary:oklch(59.435% .077 254.027);--color-primary-content:oklch(11.887% .015 254.027);--color-secondary:oklch(69.651% .059 248.687);--color-secondary-content:oklch(13.93% .011 248.687);--color-accent:oklch(77.464% .062 217.469);--color-accent-content:oklch(15.492% .012 217.469);--color-neutral:oklch(45.229% .035 264.131);--color-neutral-content:oklch(89.925% .016 262.749);--color-info:oklch(69.207% .062 332.664);--color-info-content:oklch(13.841% .012 332.664);--color-success:oklch(76.827% .074 131.063);--color-success-content:oklch(15.365% .014 131.063);--color-warning:oklch(85.486% .089 84.093);--color-warning-content:oklch(17.097% .017 84.093);--color-error:oklch(60.61% .12 15.341);--color-error-content:oklch(12.122% .024 15.341);--radius-selector:1rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=sunset]:checked),[data-theme=sunset]{color-scheme:dark;--color-base-100:oklch(22% .019 237.69);--color-base-200:oklch(20% .019 237.69);--color-base-300:oklch(18% .019 237.69);--color-base-content:oklch(77.383% .043 245.096);--color-primary:oklch(74.703% .158 39.947);--color-primary-content:oklch(14.94% .031 39.947);--color-secondary:oklch(72.537% .177 2.72);--color-secondary-content:oklch(14.507% .035 2.72);--color-accent:oklch(71.294% .166 299.844);--color-accent-content:oklch(14.258% .033 299.844);--color-neutral:oklch(26% .019 237.69);--color-neutral-content:oklch(70% .019 237.69);--color-info:oklch(85.559% .085 206.015);--color-info-content:oklch(17.111% .017 206.015);--color-success:oklch(85.56% .085 144.778);--color-success-content:oklch(17.112% .017 144.778);--color-warning:oklch(85.569% .084 74.427);--color-warning-content:oklch(17.113% .016 74.427);--color-error:oklch(85.511% .078 16.886);--color-error-content:oklch(17.102% .015 16.886);--radius-selector:1rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:0;--noise:0}:root:has(input.theme-controller[value=caramellatte]:checked),[data-theme=caramellatte]{color-scheme:light;--color-base-100:oklch(98% .016 73.684);--color-base-200:oklch(95% .038 75.164);--color-base-300:oklch(90% .076 70.697);--color-base-content:oklch(40% .123 38.172);--color-primary:oklch(0% 0 0);--color-primary-content:oklch(100% 0 0);--color-secondary:oklch(22.45% .075 37.85);--color-secondary-content:oklch(90% .076 70.697);--color-accent:oklch(46.44% .111 37.85);--color-accent-content:oklch(90% .076 70.697);--color-neutral:oklch(55% .195 38.402);--color-neutral-content:oklch(98% .016 73.684);--color-info:oklch(42% .199 265.638);--color-info-content:oklch(90% .076 70.697);--color-success:oklch(43% .095 166.913);--color-success-content:oklch(90% .076 70.697);--color-warning:oklch(82% .189 84.429);--color-warning-content:oklch(41% .112 45.904);--color-error:oklch(70% .191 22.216);--color-error-content:oklch(39% .141 25.723);--radius-selector:2rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:1;--noise:1}:root:has(input.theme-controller[value=abyss]:checked),[data-theme=abyss]{color-scheme:dark;--color-base-100:oklch(20% .08 209);--color-base-200:oklch(15% .08 209);--color-base-300:oklch(10% .08 209);--color-base-content:oklch(90% .076 70.697);--color-primary:oklch(92% .2653 125);--color-primary-content:oklch(50% .2653 125);--color-secondary:oklch(83.27% .0764 298.3);--color-secondary-content:oklch(43.27% .0764 298.3);--color-accent:oklch(43% 0 0);--color-accent-content:oklch(98% 0 0);--color-neutral:oklch(30% .08 209);--color-neutral-content:oklch(90% .076 70.697);--color-info:oklch(74% .16 232.661);--color-info-content:oklch(29% .066 243.157);--color-success:oklch(79% .209 151.711);--color-success-content:oklch(26% .065 152.934);--color-warning:oklch(84.8% .1962 84.62);--color-warning-content:oklch(44.8% .1962 84.62);--color-error:oklch(65% .1985 24.22);--color-error-content:oklch(27% .1985 24.22);--radius-selector:2rem;--radius-field:.25rem;--radius-box:.5rem;--size-selector:.25rem;--size-field:.25rem;--border:1px;--depth:1;--noise:0}:root:has(input.theme-controller[value=silk]:checked),[data-theme=silk]{color-scheme:light;--color-base-100:oklch(97% .0035 67.78);--color-base-200:oklch(95% .0081 61.42);--color-base-300:oklch(90% .0081 61.42);--color-base-content:oklch(40% .0081 61.42);--color-primary:oklch(23.27% .0249 284.3);--color-primary-content:oklch(94.22% .2505 117.44);--color-secondary:oklch(23.27% .0249 284.3);--color-secondary-content:oklch(73.92% .2135 50.94);--color-accent:oklch(23.27% .0249 284.3);--color-accent-content:oklch(88.92% .2061 189.9);--color-neutral:oklch(20% 0 0);--color-neutral-content:oklch(80% .0081 61.42);--color-info:oklch(80.39% .1148 241.68);--color-info-content:oklch(30.39% .1148 241.68);--color-success:oklch(83.92% .0901 136.87);--color-success-content:oklch(23.92% .0901 136.87);--color-warning:oklch(83.92% .1085 80);--color-warning-content:oklch(43.92% .1085 80);--color-error:oklch(75.1% .1814 22.37);--color-error-content:oklch(35.1% .1814 22.37);--radius-selector:2rem;--radius-field:.5rem;--radius-box:1rem;--size-selector:.25rem;--size-field:.25rem;--border:2px;--depth:1;--noise:0}:root{--fx-noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E");scrollbar-color:currentColor #0000}@supports (color:color-mix(in lab, red, red)){:root{scrollbar-color:color-mix(in oklch, currentColor 35%, #0000) #0000}}@property --radialprogress{syntax:"";inherits:true;initial-value:0%}:root:not(span){overflow:var(--page-overflow)}:root{background:var(--page-scroll-bg,var(--root-bg));--page-scroll-bg-on:linear-gradient(var(--root-bg,#0000), var(--root-bg,#0000)) var(--root-bg,#0000)}@supports (color:color-mix(in lab, red, red)){:root{--page-scroll-bg-on:linear-gradient(var(--root-bg,#0000), var(--root-bg,#0000)) color-mix(in srgb, var(--root-bg,#0000), oklch(0% 0 0) calc(var(--page-has-backdrop,0) * 40%))}}:root{--page-scroll-transition-on:background-color .3s ease-out;transition:var(--page-scroll-transition);scrollbar-gutter:var(--page-scroll-gutter,unset);scrollbar-gutter:if(style(--page-has-scroll: 1): var(--page-scroll-gutter,unset) ; else: unset)}@keyframes set-page-has-scroll{0%,to{--page-has-scroll:1}}:root,[data-theme]{background:var(--page-scroll-bg,var(--root-bg));color:var(--color-base-content)}:where(:root,[data-theme]){--root-bg:var(--color-base-100)}}@layer components;@layer utilities{@layer daisyui.l1.l2.l3{.tooltip{--tt-bg:var(--color-neutral);--tt-off:calc(100% + .5rem);--tt-tail:calc(100% + 1px + .25rem);display:inline-block;position:relative}.tooltip>.tooltip-content,.tooltip[data-tip]:before{border-radius:var(--radius-field);text-align:center;white-space:normal;max-width:20rem;color:var(--color-neutral-content);opacity:0;background-color:var(--tt-bg);pointer-events:none;z-index:2;--tw-content:attr(data-tip);content:var(--tw-content);width:max-content;padding-block:.25rem;padding-inline:.5rem;font-size:.875rem;line-height:1.25;position:absolute}.tooltip:after{opacity:0;background-color:var(--tt-bg);content:"";pointer-events:none;--mask-tooltip:url("data:image/svg+xml,%3Csvg width='10' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.500009 1C3.5 1 3.00001 4 5.00001 4C7 4 6.5 1 9.5 1C10 1 10 0.499897 10 0H0C-1.99338e-08 0.5 0 1 0.500009 1Z' fill='black'/%3E%3C/svg%3E%0A");width:.625rem;height:.25rem;-webkit-mask-position:-1px 0;mask-position:-1px 0;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-image:var(--mask-tooltip);-webkit-mask-image:var(--mask-tooltip);mask-image:var(--mask-tooltip);display:block;position:absolute}@media (prefers-reduced-motion:no-preference){.tooltip>.tooltip-content,.tooltip[data-tip]:before,.tooltip:after{transition:opacity .2s cubic-bezier(.4,0,.2,1) 75ms,transform .2s cubic-bezier(.4,0,.2,1) 75ms}}:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))>.tooltip-content,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))[data-tip]:before,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible)):after{opacity:1;--tt-pos:0rem}@media (prefers-reduced-motion:no-preference){:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))>.tooltip-content,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))[data-tip]:before,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible)):after{transition:opacity .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1)}}.tab{cursor:pointer;appearance:none;text-align:center;webkit-user-select:none;-webkit-user-select:none;user-select:none;flex-wrap:wrap;justify-content:center;align-items:center;display:inline-flex;position:relative}@media (hover:hover){.tab:hover{color:var(--color-base-content)}}.tab{--tab-p:.75rem;--tab-bg:var(--color-base-100);--tab-border-color:var(--color-base-300);--tab-radius-ss:0;--tab-radius-se:0;--tab-radius-es:0;--tab-radius-ee:0;--tab-order:0;--tab-radius-min:calc(.75rem - var(--border));--tab-radius-limit:min(var(--radius-field), var(--tab-radius-min));--tab-radius-grad:#0000 calc(69% - var(--border)), var(--tab-border-color) calc(69% - var(--border) + .25px), var(--tab-border-color) 69%, var(--tab-bg) calc(69% + .25px);order:var(--tab-order);height:var(--tab-height);padding-inline:var(--tab-p);border-color:#0000;font-size:.875rem}.tab:is(input[type=radio]){min-width:fit-content}.tab:is(input[type=radio]):after{--tw-content:attr(aria-label);content:var(--tw-content)}.tab:is(label){position:relative}.tab:is(label) input{cursor:pointer;appearance:none;opacity:0;position:absolute;inset:0}:is(.tab:checked,.tab:is(label:has(:checked)),.tab:is(.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]))+.tab-content{display:block}.tab:not(:checked,label:has(:checked),:hover,.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]){color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.tab:not(:checked,label:has(:checked),:hover,.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]){color:color-mix(in oklab, var(--color-base-content) 50%, transparent)}}.tab:not(input):empty{cursor:default;flex-grow:1}.tab:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tab:focus{outline-offset:2px;outline:2px solid #0000}}.tab:focus-visible,.tab:is(label:has(:checked:focus-visible)){outline-offset:-5px;outline:2px solid}.tab[disabled]{pointer-events:none;opacity:.4}.menu{--menu-active-fg:var(--color-neutral-content);--menu-active-bg:var(--color-neutral);flex-flow:column wrap;width:fit-content;padding:.5rem;font-size:.875rem;display:flex}.menu :where(li ul){white-space:nowrap;margin-inline-start:1rem;padding-inline-start:.5rem;position:relative}.menu :where(li ul):before{background-color:var(--color-base-content);opacity:.1;width:var(--border);content:"";inset-inline-start:0;position:absolute;top:.75rem;bottom:.75rem}.menu :where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}.menu :where(li:not(.menu-title)>:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--radius-field);text-align:start;text-wrap:balance;-webkit-user-select:none;user-select:none;grid-auto-columns:minmax(auto,max-content) auto max-content;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:.5rem;padding-block:.375rem;padding-inline:.75rem;transition-property:color,background-color,box-shadow;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);display:grid}.menu :where(li>details>summary){--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li>details>summary){outline-offset:2px;outline:2px solid #0000}}.menu :where(li>details>summary)::-webkit-details-marker{display:none}:is(.menu :where(li>details>summary),.menu :where(li>.menu-dropdown-toggle)):after{content:"";transform-origin:50%;pointer-events:none;justify-self:flex-end;width:.375rem;height:.375rem;transition-property:rotate,translate;transition-duration:.2s;display:block;translate:0 -1px;rotate:-135deg;box-shadow:inset 2px 2px}.menu details{interpolate-size:allow-keywords;overflow:hidden}.menu details::details-content{block-size:0}@media (prefers-reduced-motion:no-preference){.menu details::details-content{transition-behavior:allow-discrete;transition-property:block-size,content-visibility;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}}.menu details[open]::details-content{block-size:auto}.menu :where(li>details[open]>summary):after,.menu :where(li>.menu-dropdown-toggle.menu-dropdown-show):after{translate:0 1px;rotate:45deg}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{background-color:color-mix(in oklab, var(--color-base-content) 10%, transparent)}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{color:var(--color-base-content);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{outline-offset:2px;outline:2px solid #0000}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){background-color:color-mix(in oklab, var(--color-base-content) 10%, transparent)}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){outline-offset:2px;outline:2px solid #0000}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){box-shadow:inset 0 1px oklch(0% 0 0/.01),inset 0 -1px oklch(100% 0 0/.01)}.menu :where(li:empty){background-color:var(--color-base-content);opacity:.1;height:1px;margin:.5rem 1rem}.menu :where(li){flex-flow:column wrap;flex-shrink:0;align-items:stretch;display:flex;position:relative}.menu :where(li) .badge{justify-self:flex-end}.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{outline-offset:2px;outline:2px solid #0000}}.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{color:var(--menu-active-fg);background-color:var(--menu-active-bg);background-size:auto, calc(var(--noise) * 100%);background-image:none, var(--fx-noise)}:is(.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active):not(:is(.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active):active){box-shadow:0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg)}.menu :where(li).menu-disabled{pointer-events:none;color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li).menu-disabled{color:color-mix(in oklab, var(--color-base-content) 20%, transparent)}}.menu .dropdown:focus-within .menu-dropdown-toggle:after{translate:0 1px;rotate:45deg}.menu .dropdown-content{margin-top:.5rem;padding:.5rem}.menu .dropdown-content:before{display:none}:where(.btn){width:unset}.btn{cursor:pointer;text-align:center;vertical-align:middle;outline-offset:2px;webkit-user-select:none;-webkit-user-select:none;user-select:none;padding-inline:var(--btn-p);color:var(--btn-fg);--tw-prose-links:var(--btn-fg);height:var(--size);font-size:var(--fontsize,.875rem);outline-color:var(--btn-color,var(--color-base-content));background-color:var(--btn-bg);background-size:auto, calc(var(--noise) * 100%);background-image:none, var(--btn-noise);border-width:var(--border);border-style:solid;border-color:var(--btn-border);text-shadow:0 .5px oklch(100% 0 0 / calc(var(--depth) * .15));touch-action:manipulation;box-shadow:0 .5px 0 .5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow);--size:calc(var(--size-field,.25rem) * 10);--btn-bg:var(--btn-color,var(--color-base-200));--btn-fg:var(--color-base-content);--btn-p:1rem;--btn-border:var(--btn-bg);border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-wrap:nowrap;flex-shrink:0;justify-content:center;align-items:center;gap:.375rem;font-weight:600;transition-property:color,background-color,border-color,box-shadow;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn{--btn-border:color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%))}}.btn{--btn-shadow:0 3px 2px -2px var(--btn-bg), 0 4px 3px -2px var(--btn-bg)}@supports (color:color-mix(in lab, red, red)){.btn{--btn-shadow:0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000), 0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000)}}.btn{--btn-noise:var(--fx-noise)}@media (hover:hover){.btn:hover{--btn-bg:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn:hover{--btn-bg:color-mix(in oklab, var(--btn-color,var(--color-base-200)), #000 7%)}}}.btn:focus-visible,.btn:has(:focus-visible){isolation:isolate;outline-width:2px;outline-style:solid}.btn:active:not(.btn-active){--btn-bg:var(--btn-color,var(--color-base-200));translate:0 .5px}@supports (color:color-mix(in lab, red, red)){.btn:active:not(.btn-active){--btn-bg:color-mix(in oklab, var(--btn-color,var(--color-base-200)), #000 5%)}}.btn:active:not(.btn-active){--btn-border:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn:active:not(.btn-active){--btn-border:color-mix(in oklab, var(--btn-color,var(--color-base-200)), #000 7%)}}.btn:active:not(.btn-active){--btn-shadow:0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)}.btn:is(input[type=checkbox],input[type=radio]){appearance:none}.btn:is(input[type=checkbox],input[type=radio])[aria-label]:after{--tw-content:attr(aria-label);content:var(--tw-content)}.btn:where(input:checked:not(.filter .btn)){--btn-color:var(--color-primary);--btn-fg:var(--color-primary-content);isolation:isolate}.collapse{border-radius:var(--radius-box,1rem);isolation:isolate;grid-template-rows:max-content 0fr;grid-template-columns:minmax(0,1fr);width:100%;display:grid;position:relative;overflow:hidden}@media (prefers-reduced-motion:no-preference){.collapse{transition:grid-template-rows .2s}}.collapse>input:is([type=checkbox],[type=radio]){appearance:none;opacity:0;z-index:1;grid-row-start:1;grid-column-start:1;width:100%;min-height:1lh;padding:1rem;padding-inline-end:3rem;transition:background-color .2s ease-out}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close)),.collapse:not(.collapse-close):has(>input:is([type=checkbox],[type=radio]):checked){grid-template-rows:max-content 1fr}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>.collapse-content,.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){content-visibility:visible;min-height:fit-content}@supports not (content-visibility:visible){.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>.collapse-content,.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){visibility:visible}}.collapse:focus-visible,.collapse:has(>input:is([type=checkbox],[type=radio]):focus-visible),.collapse:has(summary:focus-visible){outline-color:var(--color-base-content);outline-offset:2px;outline-width:2px;outline-style:solid}.collapse:not(.collapse-close)>input[type=checkbox],.collapse:not(.collapse-close)>input[type=radio]:not(:checked),.collapse:not(.collapse-close)>.collapse-title{cursor:pointer}:is(.collapse[tabindex]:focus:not(.collapse-close,.collapse[open]),.collapse[tabindex]:focus-within:not(.collapse-close,.collapse[open]))>.collapse-title{cursor:unset}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){padding-bottom:1rem}.collapse:is(details){width:100%}@media (prefers-reduced-motion:no-preference){.collapse:is(details)::details-content{transition:content-visibility .2s allow-discrete, visibility .2s allow-discrete, min-height .2s ease-out allow-discrete, padding .1s ease-out 20ms, background-color .2s ease-out, height .2s;interpolate-size:allow-keywords;height:0}.collapse:is(details):where([open])::details-content{height:auto}}.collapse:is(details) summary{display:block;position:relative}.collapse:is(details) summary::-webkit-details-marker{display:none}.collapse:is(details)>.collapse-content{content-visibility:visible}.collapse:is(details) summary{outline:none}.validator-hint{visibility:hidden;margin-top:.5rem;font-size:.75rem}.validator:user-valid{--input-color:var(--color-success)}.validator:user-valid:focus{--input-color:var(--color-success)}.validator:user-valid:checked{--input-color:var(--color-success)}.validator:user-valid[aria-checked=true]{--input-color:var(--color-success)}.validator:user-valid:focus-within{--input-color:var(--color-success)}.validator:has(:user-valid){--input-color:var(--color-success)}.validator:has(:user-valid):focus{--input-color:var(--color-success)}.validator:has(:user-valid):checked{--input-color:var(--color-success)}.validator:has(:user-valid)[aria-checked=true]{--input-color:var(--color-success)}.validator:has(:user-valid):focus-within{--input-color:var(--color-success)}.validator:user-invalid{--input-color:var(--color-error)}.validator:user-invalid:focus{--input-color:var(--color-error)}.validator:user-invalid:checked{--input-color:var(--color-error)}.validator:user-invalid[aria-checked=true]{--input-color:var(--color-error)}.validator:user-invalid:focus-within{--input-color:var(--color-error)}.validator:user-invalid~.validator-hint{visibility:visible;color:var(--color-error)}.validator:has(:user-invalid){--input-color:var(--color-error)}.validator:has(:user-invalid):focus{--input-color:var(--color-error)}.validator:has(:user-invalid):checked{--input-color:var(--color-error)}.validator:has(:user-invalid)[aria-checked=true]{--input-color:var(--color-error)}.validator:has(:user-invalid):focus-within{--input-color:var(--color-error)}.validator:has(:user-invalid)~.validator-hint{visibility:visible;color:var(--color-error)}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))),:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):focus,:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):checked,:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))[aria-checked=true],:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):focus-within{--input-color:var(--color-error)}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))~.validator-hint{visibility:visible;color:var(--color-error)}.list{flex-direction:column;font-size:.875rem;display:flex}.list .list-row{--list-grid-cols:minmax(0, auto) 1fr;border-radius:var(--radius-box);word-break:break-word;grid-auto-flow:column;grid-template-columns:var(--list-grid-cols);gap:1rem;padding:1rem;display:grid;position:relative}:is(.list>:not(:last-child).list-row,.list>:not(:last-child) .list-row):after{content:"";border-bottom:var(--border) solid;inset-inline:var(--radius-box);border-color:var(--color-base-content);position:absolute;bottom:0}@supports (color:color-mix(in lab, red, red)){:is(.list>:not(:last-child).list-row,.list>:not(:last-child) .list-row):after{border-color:color-mix(in oklab, var(--color-base-content) 5%, transparent)}}.input{cursor:text;border:var(--border) solid #0000;appearance:none;background-color:var(--color-base-100);vertical-align:middle;white-space:nowrap;width:clamp(3rem,20rem,100%);height:var(--size);font-size:max(var(--font-size,.875rem), .875rem);touch-action:manipulation;border-color:var(--input-color);box-shadow:0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset;border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-shrink:1;align-items:center;gap:.5rem;padding-inline:.75rem;display:inline-flex;position:relative}@supports (color:color-mix(in lab, red, red)){.input{box-shadow:0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset}}.input{--size:calc(var(--size-field,.25rem) * 10);--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.input{--input-color:color-mix(in oklab, var(--color-base-content) 20%, #0000)}}.input:where(input){display:inline-flex}.input :where(input){appearance:none;background-color:#0000;border:none;width:100%;height:100%;display:inline-flex}.input :where(input):focus,.input :where(input):focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.input :where(input):focus,.input :where(input):focus-within{outline-offset:2px;outline:2px solid #0000}}.input :where(input[type=url]),.input :where(input[type=email]){direction:ltr}.input :where(input[type=date]){display:inline-flex}.input:focus,.input:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.input:focus,.input:focus-within{box-shadow:0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)}}.input:focus,.input:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}@media (pointer:coarse){@supports (-webkit-touch-callout:none){.input:focus,.input:focus-within{--font-size:1rem}}}.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input{cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input{color:color-mix(in oklab, var(--color-base-content) 40%, transparent)}}:is(.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input)::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input)::placeholder{color:color-mix(in oklab, var(--color-base-content) 20%, transparent)}}.input:has(>input[disabled]),.input:is(:disabled,[disabled]),fieldset:disabled .input{box-shadow:none}.input:has(>input[disabled])>input[disabled]{cursor:not-allowed}.input::-webkit-date-and-time-value{text-align:inherit}.input[type=number]::-webkit-inner-spin-button{margin-block:-.75rem;margin-inline-end:-.75rem}.input::-webkit-calendar-picker-indicator{position:absolute;inset-inline-end:.75em}.input:has(>input[type=date]) :where(input[type=date]){webkit-appearance:none;appearance:none;display:inline-flex}.input:has(>input[type=date]) input[type=date]::-webkit-calendar-picker-indicator{cursor:pointer;width:1em;height:1em;position:absolute;inset-inline-end:.75em}.table{border-collapse:separate;--tw-border-spacing-x:calc(.25rem * 0);--tw-border-spacing-y:calc(.25rem * 0);width:100%;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y);border-radius:var(--radius-box);text-align:left;font-size:.875rem;position:relative}.table:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:right}@media (hover:hover){:is(.table tr.row-hover,.table tr.row-hover:nth-child(2n)):hover{background-color:var(--color-base-200)}}.table :where(th,td){vertical-align:middle;padding-block:.75rem;padding-inline:1rem}.table :where(thead,tfoot){white-space:nowrap;color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(thead,tfoot){color:color-mix(in oklab, var(--color-base-content) 60%, transparent)}}.table :where(thead,tfoot){font-size:.875rem;font-weight:600}.table :where(tfoot tr:first-child :is(td,th)){border-top:var(--border) solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(tfoot tr:first-child :is(td,th)){border-top:var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)}}.table :where(.table-pin-rows thead tr){z-index:1;background-color:var(--color-base-100);position:sticky;top:0}.table :where(.table-pin-rows tfoot tr){z-index:1;background-color:var(--color-base-100);position:sticky;bottom:0}.table :where(.table-pin-cols tr th){background-color:var(--color-base-100);position:sticky;left:0;right:0}.table :where(thead tr :is(td,th),tbody tr:not(:last-child) :is(td,th)){border-bottom:var(--border) solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(thead tr :is(td,th),tbody tr:not(:last-child) :is(td,th)){border-bottom:var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)}}.steps{counter-reset:step;grid-auto-columns:1fr;grid-auto-flow:column;display:inline-grid;overflow:auto hidden}.steps .step{text-align:center;--step-bg:var(--color-base-300);--step-fg:var(--color-base-content);grid-template-rows:40px 1fr;grid-template-columns:auto;place-items:center;min-width:4rem;display:grid}.steps .step:before{width:100%;height:.5rem;color:var(--step-bg);background-color:var(--step-bg);content:"";border:1px solid;grid-row-start:1;grid-column-start:1;margin-inline-start:-100%;top:0}.steps .step>.step-icon,.steps .step:not(:has(.step-icon)):after{--tw-content:counter(step);content:var(--tw-content);counter-increment:step;z-index:1;color:var(--step-fg);background-color:var(--step-bg);border:1px solid var(--step-bg);border-radius:3.40282e38px;grid-row-start:1;grid-column-start:1;place-self:center;place-items:center;width:2rem;height:2rem;display:grid;position:relative}.steps .step:first-child:before{--tw-content:none;content:var(--tw-content)}.steps .step[data-content]:after{--tw-content:attr(data-content);content:var(--tw-content)}.range{appearance:none;webkit-appearance:none;--range-thumb:var(--color-base-100);--range-thumb-size:calc(var(--size-selector,.25rem) * 6);--range-progress:currentColor;--range-fill:1;--range-p:.25rem;--range-bg:currentColor}@supports (color:color-mix(in lab, red, red)){.range{--range-bg:color-mix(in oklab, currentColor 10%, #0000)}}.range{cursor:pointer;vertical-align:middle;--radius-selector-max:calc(var(--radius-selector) + var(--radius-selector) + var(--radius-selector));border-radius:calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));width:clamp(3rem,20rem,100%);height:var(--range-thumb-size);background-color:#0000;border:none;overflow:hidden}[dir=rtl] .range{--range-dir:-1}.range:focus{outline:none}.range:focus-visible{outline-offset:2px;outline:2px solid}.range::-webkit-slider-runnable-track{background-color:var(--range-bg);border-radius:var(--radius-selector);width:100%;height:calc(var(--range-thumb-size) * .5)}@media (forced-colors:active){.range::-webkit-slider-runnable-track{border:1px solid}.range::-moz-range-track{border:1px solid}}.range::-webkit-slider-thumb{box-sizing:border-box;border-radius:calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));background-color:var(--range-thumb);height:var(--range-thumb-size);width:var(--range-thumb-size);border:var(--range-p) solid;appearance:none;webkit-appearance:none;color:var(--range-progress);box-shadow:0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px currentColor, 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir,1) * -100cqw) - (var(--range-dir,1) * var(--range-thumb-size) / 2)) 0 0 calc(100cqw * var(--range-fill));position:relative;top:50%;transform:translateY(-50%)}@supports (color:color-mix(in lab, red, red)){.range::-webkit-slider-thumb{box-shadow:0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir,1) * -100cqw) - (var(--range-dir,1) * var(--range-thumb-size) / 2)) 0 0 calc(100cqw * var(--range-fill))}}.range::-moz-range-track{background-color:var(--range-bg);border-radius:var(--radius-selector);width:100%;height:calc(var(--range-thumb-size) * .5)}.range::-moz-range-thumb{box-sizing:border-box;border-radius:calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));height:var(--range-thumb-size);width:var(--range-thumb-size);border:var(--range-p) solid;color:var(--range-progress);box-shadow:0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px currentColor, 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir,1) * -100cqw) - (var(--range-dir,1) * var(--range-thumb-size) / 2)) 0 0 calc(100cqw * var(--range-fill));background-color:currentColor;position:relative;top:50%}@supports (color:color-mix(in lab, red, red)){.range::-moz-range-thumb{box-shadow:0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir,1) * -100cqw) - (var(--range-dir,1) * var(--range-thumb-size) / 2)) 0 0 calc(100cqw * var(--range-fill))}}.range:disabled{cursor:not-allowed;opacity:.3}.select{border:var(--border) solid #0000;appearance:none;background-color:var(--color-base-100);vertical-align:middle;width:clamp(3rem,20rem,100%);height:var(--size);touch-action:manipulation;white-space:nowrap;text-overflow:ellipsis;box-shadow:0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset;background-image:linear-gradient(45deg,#0000 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,#0000 50%);background-position:calc(100% - 20px) calc(1px + 50%),calc(100% - 16.1px) calc(1px + 50%);background-repeat:no-repeat;background-size:4px 4px,4px 4px;border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-shrink:1;align-items:center;gap:.375rem;padding-inline:.75rem 1.75rem;font-size:.875rem;display:inline-flex;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.select{box-shadow:0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset}}.select{border-color:var(--input-color);--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.select{--input-color:color-mix(in oklab, var(--color-base-content) 20%, #0000)}}.select{--size:calc(var(--size-field,.25rem) * 10)}[dir=rtl] .select{background-position:12px calc(1px + 50%),16px calc(1px + 50%)}[dir=rtl] .select::picker(select){translate:.5rem}[dir=rtl] .select select::picker(select){translate:.5rem}.select[multiple]{background-image:none;height:auto;padding-block:.75rem;padding-inline-end:.75rem;overflow:auto}.select select{appearance:none;width:calc(100% + 2.75rem);height:calc(100% - calc(var(--border) * 2));background:inherit;border-radius:inherit;border-style:none;align-items:center;margin-inline:-.75rem -1.75rem;padding-inline:.75rem 1.75rem}.select select:focus,.select select:focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.select select:focus,.select select:focus-within{outline-offset:2px;outline:2px solid #0000}}.select select:not(:last-child){background-image:none;margin-inline-end:-1.375rem}.select:focus,.select:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.select:focus,.select:focus-within{box-shadow:0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)}}.select:focus,.select:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select{cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select{color:color-mix(in oklab, var(--color-base-content) 40%, transparent)}}:is(.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select)::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.select:has(>select[disabled]),.select:is(:disabled,[disabled]),fieldset:disabled .select)::placeholder{color:color-mix(in oklab, var(--color-base-content) 20%, transparent)}}.select:has(>select[disabled])>select[disabled]{cursor:not-allowed}@supports (appearance:base-select){.select,.select select{appearance:base-select}:is(.select,.select select)::picker(select){appearance:base-select}}:is(.select,.select select)::picker(select){color:inherit;border:var(--border) solid var(--color-base-200);border-radius:var(--radius-box);background-color:inherit;max-height:min(24rem,70dvh);box-shadow:0 2px calc(var(--depth) * 3px) -2px oklch(0% 0 0/.2);box-shadow:0 20px 25px -5px rgb(0 0 0/calc(var(--depth) * .1)), 0 8px 10px -6px rgb(0 0 0/calc(var(--depth) * .1));margin-block:.5rem;margin-inline:.5rem;padding:.5rem;translate:-.5rem}:is(.select,.select select)::picker-icon{display:none}:is(.select,.select select) optgroup{padding-top:.5em}:is(.select,.select select) optgroup option:first-child{margin-top:.5em}:is(.select,.select select) option{border-radius:var(--radius-field);white-space:normal;padding-block:.375rem;padding-inline:.75rem;transition-property:color,background-color;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{background-color:color-mix(in oklab, var(--color-base-content) 10%, transparent)}}:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){:is(.select,.select select) option:not(:disabled):hover,:is(.select,.select select) option:not(:disabled):focus-visible{outline-offset:2px;outline:2px solid #0000}}:is(.select,.select select) option:not(:disabled):active{background-color:var(--color-neutral);color:var(--color-neutral-content);box-shadow:0 2px calc(var(--depth) * 3px) -2px var(--color-neutral)}.swap{cursor:pointer;vertical-align:middle;webkit-user-select:none;-webkit-user-select:none;user-select:none;place-content:center;display:inline-grid;position:relative}.swap input{appearance:none;border:none}.swap>*{grid-row-start:1;grid-column-start:1}@media (prefers-reduced-motion:no-preference){.swap>*{transition-property:transform,rotate,opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}}.swap .swap-on,.swap .swap-indeterminate,.swap input:indeterminate~.swap-on,.swap input:is(:checked,:indeterminate)~.swap-off{opacity:0}.swap input:checked~.swap-on,.swap input:indeterminate~.swap-indeterminate{opacity:1;backface-visibility:visible}.checkbox{border:var(--border) solid var(--input-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.checkbox{border:var(--border) solid var(--input-color,color-mix(in oklab, var(--color-base-content) 20%, #0000))}}.checkbox{cursor:pointer;appearance:none;border-radius:var(--radius-selector);vertical-align:middle;color:var(--color-base-content);box-shadow:0 1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 0 #0000 inset, 0 0 #0000;--size:calc(var(--size-selector,.25rem) * 6);width:var(--size);height:var(--size);background-size:auto, calc(var(--noise) * 100%);background-image:none, var(--fx-noise);flex-shrink:0;padding:.25rem;transition:background-color .2s,box-shadow .2s;display:inline-block;position:relative}.checkbox:before{--tw-content:"";content:var(--tw-content);opacity:0;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,70% 80%,70% 100%);width:100%;height:100%;box-shadow:0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * .1)) inset;background-color:currentColor;font-size:1rem;line-height:.75;transition:clip-path .3s .1s,opacity .1s .1s,rotate .3s .1s,translate .3s .1s;display:block;rotate:45deg}.checkbox:focus-visible{outline:2px solid var(--input-color,currentColor);outline-offset:2px}.checkbox:checked,.checkbox[aria-checked=true]{background-color:var(--input-color,#0000);box-shadow:0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * .1))}:is(.checkbox:checked,.checkbox[aria-checked=true]):before{clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 0%,70% 0%,70% 100%);opacity:1}@media (forced-colors:active){:is(.checkbox:checked,.checkbox[aria-checked=true]):before{--tw-content:"✔︎";clip-path:none;background-color:#0000;rotate:none}}@media print{:is(.checkbox:checked,.checkbox[aria-checked=true]):before{--tw-content:"✔︎";clip-path:none;background-color:#0000;rotate:none}}.checkbox:indeterminate{background-color:var(--input-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.checkbox:indeterminate{background-color:var(--input-color,color-mix(in oklab, var(--color-base-content) 20%, #0000))}}.checkbox:indeterminate:before{opacity:1;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,80% 80%,80% 100%);translate:0 -35%;rotate:none}.radio{cursor:pointer;appearance:none;vertical-align:middle;border:var(--border) solid var(--input-color,currentColor);border-radius:3.40282e38px;flex-shrink:0;padding:.25rem;display:inline-block;position:relative}@supports (color:color-mix(in lab, red, red)){.radio{border:var(--border) solid var(--input-color,color-mix(in srgb, currentColor 20%, #0000))}}.radio{box-shadow:0 1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset;--size:calc(var(--size-selector,.25rem) * 6);width:var(--size);height:var(--size);color:var(--input-color,currentColor)}.radio:before{--tw-content:"";content:var(--tw-content);background-size:auto, calc(var(--noise) * 100%);background-image:none, var(--fx-noise);border-radius:3.40282e38px;width:100%;height:100%;display:block}.radio:focus-visible{outline:2px solid}.radio:checked,.radio[aria-checked=true]{background-color:var(--color-base-100);border-color:currentColor}@media (prefers-reduced-motion:no-preference){.radio:checked,.radio[aria-checked=true]{animation:.2s ease-out radio}}:is(.radio:checked,.radio[aria-checked=true]):before{box-shadow:0 -1px oklch(0% 0 0 / calc(var(--depth) * .1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * .1));background-color:currentColor}@media (forced-colors:active){:is(.radio:checked,.radio[aria-checked=true]):before{outline-style:var(--tw-outline-style);outline-offset:calc(1px * -1);outline-width:1px}}@media print{:is(.radio:checked,.radio[aria-checked=true]):before{outline-offset:-1rem;outline:.25rem solid}}.navbar{align-items:center;width:100%;min-height:4rem;padding:.5rem;display:flex}.card{border-radius:var(--radius-box);outline-offset:2px;outline:0 solid #0000;flex-direction:column;transition:outline .2s ease-in-out;display:flex;position:relative}.card:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.card:focus{outline-offset:2px;outline:2px solid #0000}}.card:focus-visible{outline-color:currentColor}.card :where(figure:first-child){border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-end-radius:unset;border-end-start-radius:unset;overflow:hidden}.card :where(figure:last-child){border-start-start-radius:unset;border-start-end-radius:unset;border-end-end-radius:inherit;border-end-start-radius:inherit;overflow:hidden}.card figure{justify-content:center;align-items:center;display:flex}.card:has(>input:is(input[type=checkbox],input[type=radio])){cursor:pointer;-webkit-user-select:none;user-select:none}.card:has(>:checked){outline:2px solid}.progress{appearance:none;border-radius:var(--radius-box);background-color:currentColor;width:100%;height:.5rem;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.progress{background-color:color-mix(in oklab, currentcolor 20%, transparent)}}.progress{color:var(--color-base-content)}.progress:indeterminate{background-image:repeating-linear-gradient(90deg,currentColor -1% 10%,#0000 10% 90%);background-position-x:15%;background-size:200%}@media (prefers-reduced-motion:no-preference){.progress:indeterminate{animation:5s ease-in-out infinite progress}}@supports ((-moz-appearance:none)){.progress:indeterminate::-moz-progress-bar{background-color:#0000}@media (prefers-reduced-motion:no-preference){.progress:indeterminate::-moz-progress-bar{background-image:repeating-linear-gradient(90deg,currentColor -1% 10%,#0000 10% 90%);background-position-x:15%;background-size:200%;animation:5s ease-in-out infinite progress}}.progress::-moz-progress-bar{border-radius:var(--radius-box);background-color:currentColor}}@supports ((-webkit-appearance:none)){.progress::-webkit-progress-bar{border-radius:var(--radius-box);background-color:#0000}.progress::-webkit-progress-value{border-radius:var(--radius-box);background-color:currentColor}}.textarea{border:var(--border) solid #0000;appearance:none;border-radius:var(--radius-field);background-color:var(--color-base-100);vertical-align:middle;width:clamp(3rem,20rem,100%);min-height:5rem;font-size:max(var(--font-size,.875rem), .875rem);touch-action:manipulation;border-color:var(--input-color);box-shadow:0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset;flex-shrink:1;padding-block:.5rem;padding-inline:.75rem}@supports (color:color-mix(in lab, red, red)){.textarea{box-shadow:0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset}}.textarea{--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.textarea{--input-color:color-mix(in oklab, var(--color-base-content) 20%, #0000)}}.textarea textarea{appearance:none;background-color:#0000;border:none}.textarea textarea:focus,.textarea textarea:focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.textarea textarea:focus,.textarea textarea:focus-within{outline-offset:2px;outline:2px solid #0000}}.textarea:focus,.textarea:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.textarea:focus,.textarea:focus-within{box-shadow:0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)}}.textarea:focus,.textarea:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}@media (pointer:coarse){@supports (-webkit-touch-callout:none){.textarea:focus,.textarea:focus-within{--font-size:1rem}}}.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){color:color-mix(in oklab, var(--color-base-content) 40%, transparent)}}:is(.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]))::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]))::placeholder{color:color-mix(in oklab, var(--color-base-content) 20%, transparent)}}.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){box-shadow:none}.textarea:has(>textarea[disabled])>textarea[disabled]{cursor:not-allowed}.divider{white-space:nowrap;height:1rem;margin:var(--divider-m,1rem 0);--divider-color:var(--color-base-content);flex-direction:row;align-self:stretch;align-items:center;display:flex}@supports (color:color-mix(in lab, red, red)){.divider{--divider-color:color-mix(in oklab, var(--color-base-content) 10%, transparent)}}.divider:before,.divider:after{content:"";background-color:var(--divider-color);flex-grow:1;width:100%;height:.125rem}@media print{.divider:before,.divider:after{border:.5px solid}}.divider:not(:empty){gap:1rem}.filter{flex-wrap:wrap;display:flex}.filter input[type=radio]{width:auto}.filter input{opacity:1;transition:margin .1s,opacity .3s,padding .3s,border-width .1s;overflow:hidden;scale:1}.filter input:not(:last-child){margin-inline-end:.25rem}.filter input.filter-reset{aspect-ratio:1}.filter input.filter-reset:after{--tw-content:"×";content:var(--tw-content)}.filter:not(:has(input:checked:not(.filter-reset))) .filter-reset,.filter:not(:has(input:checked:not(.filter-reset))) input[type=reset],.filter:has(input:checked:not(.filter-reset)) input:not(:checked,.filter-reset,input[type=reset]){opacity:0;border-width:0;width:0;margin-inline:0;padding-inline:0;scale:0}.label{white-space:nowrap;color:currentColor;align-items:center;gap:.375rem;display:inline-flex}@supports (color:color-mix(in lab, red, red)){.label{color:color-mix(in oklab, currentcolor 60%, transparent)}}.label:has(input){cursor:pointer}.label:is(.input>*,.select>*){white-space:nowrap;height:calc(100% - .5rem);font-size:inherit;align-items:center;padding-inline:.75rem;display:flex}.label:is(.input>*,.select>*):first-child{border-inline-end:var(--border) solid currentColor;margin-inline:-.75rem .75rem}@supports (color:color-mix(in lab, red, red)){.label:is(.input>*,.select>*):first-child{border-inline-end:var(--border) solid color-mix(in oklab, currentColor 10%, #0000)}}.label:is(.input>*,.select>*):last-child{border-inline-start:var(--border) solid currentColor;margin-inline:.75rem -.75rem}@supports (color:color-mix(in lab, red, red)){.label:is(.input>*,.select>*):last-child{border-inline-start:var(--border) solid color-mix(in oklab, currentColor 10%, #0000)}}.fieldset-legend{color:var(--color-base-content);justify-content:space-between;align-items:center;gap:.5rem;margin-bottom:-.25rem;padding-block:.5rem;font-weight:600;display:flex}.status{aspect-ratio:1;border-radius:var(--radius-selector);background-color:var(--color-base-content);width:.5rem;height:.5rem;display:inline-block}@supports (color:color-mix(in lab, red, red)){.status{background-color:color-mix(in oklab, var(--color-base-content) 20%, transparent)}}.status{vertical-align:middle;color:#0000004d;background-position:50%;background-repeat:no-repeat}@supports (color:color-mix(in lab, red, red)){.status{color:color-mix(in oklab, var(--color-black) 30%, transparent)}}.status{background-image:radial-gradient(circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * .5)), #0000);box-shadow:0 2px 3px -1px}@supports (color:color-mix(in lab, red, red)){.status{box-shadow:0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000)}}.badge{border-radius:var(--radius-selector);vertical-align:middle;color:var(--badge-fg);border:var(--border) solid var(--badge-color,var(--color-base-200));background-size:auto, calc(var(--noise) * 100%);background-image:none, var(--fx-noise);background-color:var(--badge-bg);--badge-bg:var(--badge-color,var(--color-base-100));--badge-fg:var(--color-base-content);--size:calc(var(--size-selector,.25rem) * 6);width:fit-content;height:var(--size);padding-inline:calc(var(--size) / 2 - var(--border));justify-content:center;align-items:center;gap:.5rem;font-size:.875rem;display:inline-flex}.footer{grid-auto-flow:row;place-items:start;gap:2.5rem 1rem;width:100%;font-size:.875rem;line-height:1.25rem;display:grid}.footer>*{place-items:start;gap:.5rem;display:grid}.footer.footer-center{text-align:center;grid-auto-flow:column dense;place-items:center}.footer.footer-center>*{place-items:center}.navbar-end{justify-content:flex-end;align-items:center;width:50%;display:inline-flex}.navbar-start{justify-content:flex-start;align-items:center;width:50%;display:inline-flex}.card-body{padding:var(--card-p,1.5rem);font-size:var(--card-fs,.875rem);flex-direction:column;flex:auto;gap:.5rem;display:flex}.card-body :where(p){flex-grow:1}.navbar-center{flex-shrink:0;align-items:center;display:inline-flex}.fieldset-label{color:var(--color-base-content);align-items:center;gap:.375rem;display:flex}@supports (color:color-mix(in lab, red, red)){.fieldset-label{color:color-mix(in oklab, var(--color-base-content) 60%, transparent)}}.fieldset-label:has(input){cursor:pointer}.alert{--alert-border-color:var(--color-base-200);border-radius:var(--radius-box);color:var(--color-base-content);background-color:var(--alert-color,var(--color-base-200));text-align:start;background-size:auto, calc(var(--noise) * 100%);background-image:none, var(--fx-noise);box-shadow:0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * .08)) inset, 0 1px #000, 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * .08));border-style:solid;grid-template-columns:auto;grid-auto-flow:column;justify-content:start;place-items:center start;gap:1rem;padding-block:.75rem;padding-inline:1rem;font-size:.875rem;line-height:1.25rem;display:grid}@supports (color:color-mix(in lab, red, red)){.alert{box-shadow:0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * .08)) inset, 0 1px color-mix(in oklab, color-mix(in oklab, #000 20%, var(--alert-color,var(--color-base-200))) calc(var(--depth) * 20%), #0000), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * .08))}}.alert:has(:nth-child(2)){grid-template-columns:auto minmax(auto,1fr)}.fieldset{grid-template-columns:1fr;grid-auto-rows:max-content;gap:.375rem;padding-block:.25rem;font-size:.75rem;display:grid}.mask{vertical-align:middle;display:inline-block;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.link{cursor:pointer;text-decoration-line:underline}.link:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.link:focus{outline-offset:2px;outline:2px solid #0000}}.link:focus-visible{outline-offset:2px;outline:2px solid}.btn-primary{--btn-color:var(--color-primary);--btn-fg:var(--color-primary-content)}}@layer daisyui.l1.l2{.tooltip>.tooltip-content,.tooltip[data-tip]:before{transform:translateX(-50%) translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-off) 50%}.tooltip:after{transform:translateX(-50%) translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-tail) 50%}.btn:disabled:not(.btn-link,.btn-ghost){background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn:disabled:not(.btn-link,.btn-ghost){background-color:color-mix(in oklab, var(--color-base-content) 10%, transparent)}}.btn:disabled:not(.btn-link,.btn-ghost){box-shadow:none}.btn:disabled{pointer-events:none;--btn-border:#0000;--btn-noise:none;--btn-fg:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn:disabled{--btn-fg:color-mix(in oklch, var(--color-base-content) 20%, #0000)}}.btn[disabled]:not(.btn-link,.btn-ghost){background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn[disabled]:not(.btn-link,.btn-ghost){background-color:color-mix(in oklab, var(--color-base-content) 10%, transparent)}}.btn[disabled]:not(.btn-link,.btn-ghost){box-shadow:none}.btn[disabled]{pointer-events:none;--btn-border:#0000;--btn-noise:none;--btn-fg:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn[disabled]{--btn-fg:color-mix(in oklch, var(--color-base-content) 20%, #0000)}}@media (prefers-reduced-motion:no-preference){.collapse[open].collapse-arrow>.collapse-title:after,.collapse.collapse-open.collapse-arrow>.collapse-title:after{transform:translateY(-50%)rotate(225deg)}}.collapse.collapse-open.collapse-plus>.collapse-title:after{--tw-content:"−";content:var(--tw-content)}:is(.collapse[tabindex].collapse-arrow:focus:not(.collapse-close),.collapse.collapse-arrow[tabindex]:focus-within:not(.collapse-close))>.collapse-title:after,.collapse.collapse-arrow:not(.collapse-close)>input:is([type=checkbox],[type=radio]):checked~.collapse-title:after{transform:translateY(-50%)rotate(225deg)}.collapse[open].collapse-plus>.collapse-title:after,.collapse[tabindex].collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse.collapse-plus:not(.collapse-close)>input:is([type=checkbox],[type=radio]):checked~.collapse-title:after{--tw-content:"−";content:var(--tw-content)}.list .list-row:has(.list-col-grow:first-child){--list-grid-cols:1fr}.list .list-row:has(.list-col-grow:nth-child(2)){--list-grid-cols:minmax(0, auto) 1fr}.list .list-row:has(.list-col-grow:nth-child(3)){--list-grid-cols:minmax(0, auto) minmax(0, auto) 1fr}.list .list-row:has(.list-col-grow:nth-child(4)){--list-grid-cols:minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr}.list .list-row:has(.list-col-grow:nth-child(5)){--list-grid-cols:minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr}.list .list-row:has(.list-col-grow:nth-child(6)){--list-grid-cols:minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr}.list .list-row>*{grid-row-start:1}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after,.steps .step-neutral>.step-icon{--step-bg:var(--color-neutral);--step-fg:var(--color-neutral-content)}.steps .step-primary+.step-primary:before,.steps .step-primary:after,.steps .step-primary>.step-icon{--step-bg:var(--color-primary);--step-fg:var(--color-primary-content)}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after,.steps .step-secondary>.step-icon{--step-bg:var(--color-secondary);--step-fg:var(--color-secondary-content)}.steps .step-accent+.step-accent:before,.steps .step-accent:after,.steps .step-accent>.step-icon{--step-bg:var(--color-accent);--step-fg:var(--color-accent-content)}.steps .step-info+.step-info:before,.steps .step-info:after,.steps .step-info>.step-icon{--step-bg:var(--color-info);--step-fg:var(--color-info-content)}.steps .step-success+.step-success:before,.steps .step-success:after,.steps .step-success>.step-icon{--step-bg:var(--color-success);--step-fg:var(--color-success-content)}.steps .step-warning+.step-warning:before,.steps .step-warning:after,.steps .step-warning>.step-icon{--step-bg:var(--color-warning);--step-fg:var(--color-warning-content)}.steps .step-error+.step-error:before,.steps .step-error:after,.steps .step-error>.step-icon{--step-bg:var(--color-error);--step-fg:var(--color-error-content)}.menu-vertical{flex-direction:column;display:inline-flex}.menu-vertical>li:not(.menu-title)>details>ul{background-color:revert-layer;border-radius:revert-layer;animation:revert-layer;box-shadow:revert-layer;margin-inline-start:1rem;margin-top:0;padding-block:0;padding-inline-end:0;transition:revert-layer;position:relative}.checkbox:disabled,.radio:disabled{cursor:not-allowed;opacity:.2}:where(.navbar){position:relative}.tooltip-top>.tooltip-content,.tooltip-top[data-tip]:before{transform:translateX(-50%) translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-off) 50%}.tooltip-top:after{transform:translateX(-50%) translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-tail) 50%}.input-lg{--size:calc(var(--size-field,.25rem) * 12);font-size:max(var(--font-size,1.125rem), 1.125rem)}.input-lg[type=number]::-webkit-inner-spin-button{margin-block:-.75rem;margin-inline-end:-.75rem}.input-md{--size:calc(var(--size-field,.25rem) * 10);font-size:max(var(--font-size,.875rem), .875rem)}.input-md[type=number]::-webkit-inner-spin-button{margin-block:-.75rem;margin-inline-end:-.75rem}.input-sm{--size:calc(var(--size-field,.25rem) * 8);font-size:max(var(--font-size,.75rem), .75rem)}.input-sm[type=number]::-webkit-inner-spin-button{margin-block:-.5rem;margin-inline-end:-.75rem}.input-xl{--size:calc(var(--size-field,.25rem) * 14);font-size:max(var(--font-size,1.375rem), 1.375rem)}.input-xl[type=number]::-webkit-inner-spin-button{margin-block:-1rem;margin-inline-end:-.75rem}.input-xs{--size:calc(var(--size-field,.25rem) * 6);font-size:max(var(--font-size,.6875rem), .6875rem)}.input-xs[type=number]::-webkit-inner-spin-button{margin-block:-.25rem;margin-inline-end:-.75rem}.badge-ghost{border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content);background-image:none}.badge-outline{color:var(--badge-color);--badge-bg:#0000;background-image:none;border-color:currentColor}.select-lg{--size:calc(var(--size-field,.25rem) * 12);font-size:1.125rem}.select-lg option{padding-block:.375rem;padding-inline:1rem}.select-sm{--size:calc(var(--size-field,.25rem) * 8);font-size:.75rem}.select-sm option{padding-block:.25rem;padding-inline:.625rem}.select-xl{--size:calc(var(--size-field,.25rem) * 14);font-size:1.375rem}.select-xl option{padding-block:.375rem;padding-inline:1.25rem}.select-xs{--size:calc(var(--size-field,.25rem) * 6);font-size:.6875rem}.select-xs option{padding-block:.25rem;padding-inline:.5rem}.table-sm :not(thead,tfoot) tr{font-size:.75rem}.table-sm :where(th,td){padding-block:.5rem;padding-inline:.75rem}.badge-sm{--size:calc(var(--size-selector,.25rem) * 5);font-size:.75rem}.textarea-lg{font-size:max(var(--font-size,1.125rem), 1.125rem)}.textarea-sm{font-size:max(var(--font-size,.75rem), .75rem)}.textarea-xl{font-size:max(var(--font-size,1.375rem), 1.375rem)}.textarea-xs{font-size:max(var(--font-size,.6875rem), .6875rem)}.alert-error{color:var(--color-error-content);--alert-border-color:var(--color-error);--alert-color:var(--color-error)}.link-primary{color:var(--color-primary)}@media (hover:hover){.link-primary:hover{color:var(--color-primary)}@supports (color:color-mix(in lab, red, red)){.link-primary:hover{color:color-mix(in oklab, var(--color-primary) 80%, #000)}}}.btn-sm{--fontsize:.75rem;--btn-p:.75rem;--size:calc(var(--size-field,.25rem) * 8)}.badge-error{--badge-color:var(--color-error);--badge-fg:var(--color-error-content)}.badge-info{--badge-color:var(--color-info);--badge-fg:var(--color-info-content)}.badge-secondary{--badge-color:var(--color-secondary);--badge-fg:var(--color-secondary-content)}.badge-success{--badge-color:var(--color-success);--badge-fg:var(--color-success-content)}.badge-warning{--badge-color:var(--color-warning);--badge-fg:var(--color-warning-content)}.range-lg{--range-thumb-size:calc(var(--size-selector,.25rem) * 7)}.range-sm{--range-thumb-size:calc(var(--size-selector,.25rem) * 5)}.range-xl{--range-thumb-size:calc(var(--size-selector,.25rem) * 8)}.range-xs{--range-thumb-size:calc(var(--size-selector,.25rem) * 4)}.textarea-error,.textarea-error:focus,.textarea-error:focus-within{--input-color:var(--color-error)}}.prose :where(a.btn:not(.btn-link)):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:none}.collapse:not(td,tr,colgroup){visibility:revert-layer}.validator:user-invalid~.validator-hint{display:revert-layer}.validator:has(:user-invalid)~.validator-hint{display:revert-layer}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))~.validator-hint{display:revert-layer}.collapse{visibility:collapse}.visible{visibility:visible}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-5{margin:calc(var(--spacing) * 5)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing) * 2)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\!{margin-top:calc(var(--spacing) * 0)!important}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.alert{border-width:var(--border);border-color:var(--alert-border-color,var(--color-base-200))}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[200px\]{min-height:200px}.min-h-\[calc\(100vh-4rem\)\]{min-height:calc(100vh - 4rem)}.min-h-screen{min-height:100vh}.w-4{width:calc(var(--spacing) * 4)}.w-48{width:calc(var(--spacing) * 48)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-7{max-width:calc(var(--spacing) * 7)}.max-w-lg{max-width:var(--container-lg)}.max-w-xs{max-width:var(--container-xs)}.flex-1{flex:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-base-200{border-color:var(--color-base-200)}.border-base-300{border-color:var(--color-base-300)}.bg-base-100{background-color:var(--color-base-100)}.bg-base-200{background-color:var(--color-base-200)}.bg-primary{background-color:var(--color-primary)}.p-0{padding:calc(var(--spacing) * 0)}.p-0\!{padding:calc(var(--spacing) * 0)!important}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.whitespace-pre-wrap{white-space:pre-wrap}.text-base-content,.text-base-content\/60{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.text-base-content\/60{color:color-mix(in oklab, var(--color-base-content) 60%, transparent)}}.text-error{color:var(--color-error)}.text-primary{color:var(--color-primary)}.text-primary-content{color:var(--color-primary-content)}.italic{font-style:italic}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}@layer daisyui.l1{.btn-ghost:not(.btn-active,:hover,:active:focus,:focus-visible,input:checked:not(.filter .btn)){--btn-shadow:"";--btn-bg:#0000;--btn-border:#0000;--btn-noise:none}.btn-ghost:not(.btn-active,:hover,:active:focus,:focus-visible,input:checked:not(.filter .btn)):not(:disabled,[disabled],.btn-disabled){--btn-fg:var(--btn-color,currentColor);outline-color:currentColor}@media (hover:none){.btn-ghost:not(.btn-active,:active,:focus-visible,input:checked:not(.filter .btn)):hover{--btn-shadow:"";--btn-bg:#0000;--btn-fg:var(--btn-color,currentColor);--btn-border:#0000;--btn-noise:none;outline-color:currentColor}}}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.placeholder\:text-base-content\/70::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.placeholder\:text-base-content\/70::placeholder{color:color-mix(in oklab, var(--color-base-content) 70%, transparent)}}@media (hover:hover){.hover\:text-primary:hover{color:var(--color-primary)}}@media (min-width:48rem){.md\:w-64{width:calc(var(--spacing) * 64)}}@media (min-width:64rem){.lg\:w-1\/2{width:50%}.lg\:flex-row{flex-direction:row}}@media (min-width:80rem){.xl\:w-\[45\%\]{width:45%}.xl\:w-\[55\%\]{width:55%}}}@keyframes rating{0%,40%{filter:brightness(1.05)contrast(1.05);scale:1.1}}@keyframes dropdown{0%{opacity:0}}@keyframes radio{0%{padding:5px}50%{padding:3px}}@keyframes toast{0%{opacity:0;scale:.9}to{opacity:1;scale:1}}@keyframes rotator{89.9999%,to{--first-item-position:0 0%}90%,99.9999%{--first-item-position:0 calc(var(--items) * 100%)}to{translate:0 -100%}}@keyframes skeleton{0%{background-position:150%}to{background-position:-50%}}@keyframes menu{0%{opacity:0}}@keyframes progress{50%{background-position-x:-115%}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false} \ No newline at end of file diff --git a/pkg/web/components/base/table.templ b/pkg/web/components/base/table.templ index d24c844..b7432f2 100644 --- a/pkg/web/components/base/table.templ +++ b/pkg/web/components/base/table.templ @@ -2,7 +2,7 @@ package base templ Table(headers []templ.Component, rows []templ.Component, attrs templ.Attributes) {
- +
for _, header := range headers { diff --git a/pkg/web/components/base/table_templ.go b/pkg/web/components/base/table_templ.go index dc224b4..b7544a0 100644 --- a/pkg/web/components/base/table_templ.go +++ b/pkg/web/components/base/table_templ.go @@ -37,7 +37,7 @@ func Table(headers []templ.Component, rows []templ.Component, attrs templ.Attrib if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " class=\"table\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " class=\"table table-sm\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/pkg/web/components/dashboard.templ b/pkg/web/components/dashboard.templ new file mode 100644 index 0000000..1d8e687 --- /dev/null +++ b/pkg/web/components/dashboard.templ @@ -0,0 +1,102 @@ +package components + +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components/base" +) + +type DashboardPageProps struct { + ActiveIssues []models.Issue + ClosedIssues []models.Issue + SelectedIssue *models.Issue + SelectedID string + BaseURL string + QueryParam string + EmptyText string + DisplayOwner string + DisplayAssignee string +} + +templ DashboardPage(props DashboardPageProps) { +
+
+
+
+

Issues

+ @base.Table( + []templ.Component{ + base.PlainText("ID"), + base.PlainText("Title"), + base.PlainText("Status"), + base.PlainText("Type"), + base.PlainText("Priority"), + }, + []templ.Component{ + DashboardIssueRows(props.ActiveIssues, props.SelectedID, props.BaseURL, props.QueryParam), + }, + templ.Attributes{}, + ) +
+
+

Closed issues

+ @base.Table( + []templ.Component{ + base.PlainText("ID"), + base.PlainText("Title"), + base.PlainText("Status"), + base.PlainText("Type"), + base.PlainText("Priority"), + }, + []templ.Component{ + DashboardIssueRows(props.ClosedIssues, props.SelectedID, props.BaseURL, props.QueryParam), + }, + templ.Attributes{}, + ) +
+
+ +
+ if props.SelectedIssue != nil { +
+ @IssueDetailCard(IssueDetailCardProps{ + Issue: *props.SelectedIssue, + DisplayOwner: props.DisplayOwner, + DisplayAssignee: props.DisplayAssignee, + }) +
+ } else { +
+

{ props.EmptyText }

+ Create an issue +
+ } +
+
+
+} + +templ DashboardIssueRows(issues []models.Issue, selectedID string, baseURL string, queryParam string) { + if len(issues) == 0 { + + } else { + for _, issue := range issues { + if issue.ID == selectedID { + + + + + + + + } else { + + + + + + + + } + } + } +} diff --git a/pkg/web/components/dashboard_templ.go b/pkg/web/components/dashboard_templ.go new file mode 100644 index 0000000..233b4a3 --- /dev/null +++ b/pkg/web/components/dashboard_templ.go @@ -0,0 +1,360 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components/base" +) + +type DashboardPageProps struct { + ActiveIssues []models.Issue + ClosedIssues []models.Issue + SelectedIssue *models.Issue + SelectedID string + BaseURL string + QueryParam string + EmptyText string + DisplayOwner string + DisplayAssignee string +} + +func DashboardPage(props DashboardPageProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Issues

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = base.Table( + []templ.Component{ + base.PlainText("ID"), + base.PlainText("Title"), + base.PlainText("Status"), + base.PlainText("Type"), + base.PlainText("Priority"), + }, + []templ.Component{ + DashboardIssueRows(props.ActiveIssues, props.SelectedID, props.BaseURL, props.QueryParam), + }, + templ.Attributes{}, + ).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Closed issues

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = base.Table( + []templ.Component{ + base.PlainText("ID"), + base.PlainText("Title"), + base.PlainText("Status"), + base.PlainText("Type"), + base.PlainText("Priority"), + }, + []templ.Component{ + DashboardIssueRows(props.ClosedIssues, props.SelectedID, props.BaseURL, props.QueryParam), + }, + templ.Attributes{}, + ).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.SelectedIssue != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = IssueDetailCard(IssueDetailCardProps{ + Issue: *props.SelectedIssue, + DisplayOwner: props.DisplayOwner, + DisplayAssignee: props.DisplayAssignee, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.EmptyText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/dashboard.templ`, Line: 69, Col: 46} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Create an issue
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func DashboardIssueRows(issues []models.Issue, selectedID string, baseURL string, queryParam string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if len(issues) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + for _, issue := range issues { + if issue.ID == selectedID { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/components/issue_detail_card.templ b/pkg/web/components/issue_detail_card.templ new file mode 100644 index 0000000..bba2d18 --- /dev/null +++ b/pkg/web/components/issue_detail_card.templ @@ -0,0 +1,103 @@ +package components + +import ( + "fmt" + + "github.com/LazyBachelor/LazyPM/internal/models" +) + +type IssueDetailCardProps struct { + Issue models.Issue + DisplayOwner string + DisplayAssignee string +} + +templ IssueDetailCard(props IssueDetailCardProps) { +
+
+ { props.Issue.ID } +

{ props.Issue.Title }

+
+ @StatusBadge(props.Issue.Status) + @TypeBadge(props.Issue.IssueType) + @PriorityBadge(props.Issue.Priority) +
+ if props.Issue.Description != "" { +
+

Description

+

{ props.Issue.Description }

+
+ } +
+
+
+ Created +

{ props.Issue.CreatedAt.Format("Jan 2, 2006") }

+
+
+ Updated +

{ props.Issue.UpdatedAt.Format("Jan 2, 2006") }

+
+
+ Created by +

{ props.DisplayOwner }

+
+
+ Assignee +

{ props.DisplayAssignee }

+
+
+
+
+} + +templ StatusBadge(status models.Status) { + switch status { + case "open": + Open + case "in_progress": + In Progress + case "closed": + Closed + case "blocked": + Blocked + case "deferred": + Deferred + default: + { string(status) } + } +} + +templ TypeBadge(issueType models.IssueType) { + switch issueType { + case "bug": + Bug + case "feature": + Feature + case "task": + Task + case "chore": + Chore + case "epic": + Epic + default: + { string(issueType) } + } +} + +templ PriorityBadge(priority int) { + switch priority { + case 0: + Critical + case 1: + High + case 2: + Medium + case 3: + Low + case 4: + Minimal + default: + { fmt.Sprintf("P%d", priority) } + } +} diff --git a/pkg/web/components/issue_detail_card_templ.go b/pkg/web/components/issue_detail_card_templ.go new file mode 100644 index 0000000..a0c0246 --- /dev/null +++ b/pkg/web/components/issue_detail_card_templ.go @@ -0,0 +1,379 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + + "github.com/LazyBachelor/LazyPM/internal/models" +) + +type IssueDetailCardProps struct { + Issue models.Issue + DisplayOwner string + DisplayAssignee string +} + +func IssueDetailCard(props IssueDetailCardProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 18, Col: 70} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 19, Col: 57} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = StatusBadge(props.Issue.Status).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TypeBadge(props.Issue.IssueType).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = PriorityBadge(props.Issue.Priority).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Issue.Description != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Description

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.Description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 28, Col: 69} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
Created

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.CreatedAt.Format("Jan 2, 2006")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 35, Col: 53} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

Updated

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.UpdatedAt.Format("Jan 2, 2006")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 39, Col: 53} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

Created by

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(props.DisplayOwner) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 43, Col: 28} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

Assignee

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(props.DisplayAssignee) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 47, Col: 31} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func StatusBadge(status models.Status) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + switch status { + case "open": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "Open") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "in_progress": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "In Progress") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "closed": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "Closed") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "blocked": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "Blocked") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "deferred": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "Deferred") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(string(status)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 67, Col: 60} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func TypeBadge(issueType models.IssueType) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + switch issueType { + case "bug": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Bug") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "feature": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "Feature") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "task": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Task") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "chore": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "Chore") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "epic": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Epic") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(string(issueType)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 84, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func PriorityBadge(priority int) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var13 := templ.GetChildren(ctx) + if templ_7745c5c3_Var13 == nil { + templ_7745c5c3_Var13 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + switch priority { + case 0: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "Critical") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 1: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "High") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 2: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "Medium") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 3: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "Low") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 4: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "Minimal") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", priority)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail_card.templ`, Line: 101, Col: 74} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/handler/dashboard.go b/pkg/web/handler/dashboard.go new file mode 100644 index 0000000..8608978 --- /dev/null +++ b/pkg/web/handler/dashboard.go @@ -0,0 +1,95 @@ +package handler + +import ( + "net/http" + "sort" + + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components" + "github.com/LazyBachelor/LazyPM/pkg/web/routes" +) + +func orDisplay(s, fallback string) string { + if s != "" { + return s + } + return fallback +} + +func selectedID(issue *models.Issue) string { + if issue == nil { + return "" + } + return issue.ID +} + +func DashboardHandler(w http.ResponseWriter, r *http.Request) { + app := App(r) + + issues, err := app.Issues.AllIssues(r.Context()) + if err != nil { + http.Error(w, "Failed to retrieve issues", http.StatusInternalServerError) + return + } + + var activeIssues, closedIssues []models.Issue + for _, issue := range issues { + if issue.Status == models.StatusClosed { + closedIssues = append(closedIssues, issue) + } else { + activeIssues = append(activeIssues, issue) + } + } + sort.Slice(activeIssues, func(i, j int) bool { return activeIssues[i].Priority > activeIssues[j].Priority }) + sort.Slice(closedIssues, func(i, j int) bool { return closedIssues[i].Priority > closedIssues[j].Priority }) + + selID := r.URL.Query().Get("id") + var selectedIssue *models.Issue + if selID != "" { + for i := range issues { + if issues[i].ID == selID { + selectedIssue = &issues[i] + break + } + } + } + if selectedIssue == nil && len(activeIssues) > 0 { + selectedIssue = &activeIssues[0] + selID = selectedIssue.ID + } + + searchQuery := r.URL.Query().Get("q") + + var displayOwner, displayAssignee string + if selectedIssue != nil { + displayOwner = orDisplay(selectedIssue.Owner, "—") + displayAssignee = orDisplay(selectedIssue.Assignee, "—") + } + + props := routes.DashboardProps{ + ActiveIssues: activeIssues, + ClosedIssues: closedIssues, + SelectedIssue: selectedIssue, + SelectedID: selectedID(selectedIssue), + SearchQuery: searchQuery, + DisplayOwner: displayOwner, + DisplayAssignee: displayAssignee, + } + + if HTMX(r).IsHxRequest() { + components.DashboardPage(components.DashboardPageProps{ + ActiveIssues: props.ActiveIssues, + ClosedIssues: props.ClosedIssues, + SelectedIssue: props.SelectedIssue, + SelectedID: props.SelectedID, + BaseURL: "/dashboard", + QueryParam: "?id=", + EmptyText: "Select an issue to view details", + DisplayOwner: props.DisplayOwner, + DisplayAssignee: props.DisplayAssignee, + }).Render(r.Context(), w) + return + } + + routes.Dashboard(props).Render(r.Context(), w) +} \ No newline at end of file diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go index b9ceace..28f2902 100644 --- a/pkg/web/handler/issues.go +++ b/pkg/web/handler/issues.go @@ -110,18 +110,25 @@ func GetIssue(w http.ResponseWriter, r *http.Request) { comments := r.Context().Value(commentsKey).([]*models.Comment) hx := HTMX(r) + displayOwner := orDisplay(issue.Owner, "—") + displayAssignee := orDisplay(issue.Assignee, "—") + if !hx.IsHxRequest() && strings.Contains(r.Header.Get("Accept"), "text/html") { routes.IssueDetail(routes.IssueDetailProps{ - Issue: *issue, - Comments: comments, + Issue: *issue, + DisplayOwner: displayOwner, + DisplayAssignee: displayAssignee, + Comments: comments, }).Render(r.Context(), w) return } if hx.IsHxRequest() { routes.IssueDetailContent(routes.IssueDetailProps{ - Issue: *issue, - Comments: comments, + Issue: *issue, + DisplayOwner: displayOwner, + DisplayAssignee: displayAssignee, + Comments: comments, }).Render(r.Context(), w) return } diff --git a/pkg/web/input.css b/pkg/web/input.css index f503969..73adc6f 100644 --- a/pkg/web/input.css +++ b/pkg/web/input.css @@ -1,7 +1,13 @@ @import 'tailwindcss'; @source "./**/*.templ"; @source "./**/*.go"; +@source "./components/**/*.templ"; +@source "./components/**/*_templ.go"; @plugin "@tailwindcss/typography"; @plugin "daisyui" { -themes: all; -} \ No newline at end of file + themes: all; +} +/* Safelist: force badge classes into build (class names in comments are scanned by Tailwind v4) */ +/* StatusBadge: inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-medium bg-sky-100 text-sky-800 bg-amber-100 text-amber-800 bg-emerald-100 text-emerald-800 bg-red-100 text-red-800 bg-slate-100 text-slate-600 text-slate-700 */ +/* TypeBadge: border border-red-200 border-emerald-200 border-sky-200 border-amber-200 border-violet-200 border-slate-200 text-red-700 text-emerald-700 text-sky-700 text-amber-700 text-violet-700 text-slate-600 */ +/* PriorityBadge: gap-1 shadow-sm opacity-75 font-semibold bg-red-100 text-red-800 bg-amber-100 bg-sky-100 bg-slate-50 border-slate-300 */ \ No newline at end of file diff --git a/pkg/web/routes/dashboard.templ b/pkg/web/routes/dashboard.templ new file mode 100644 index 0000000..33d392c --- /dev/null +++ b/pkg/web/routes/dashboard.templ @@ -0,0 +1,32 @@ +package routes + +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components" +) + +type DashboardProps struct { + ActiveIssues []models.Issue + ClosedIssues []models.Issue + SelectedIssue *models.Issue + SelectedID string + SearchQuery string + DisplayOwner string + DisplayAssignee string +} + +templ Dashboard(props DashboardProps) { + @BaseLayout(BaseLayoutProps{SearchQuery: props.SearchQuery}) { + @components.DashboardPage(components.DashboardPageProps{ + ActiveIssues: props.ActiveIssues, + ClosedIssues: props.ClosedIssues, + SelectedIssue: props.SelectedIssue, + SelectedID: props.SelectedID, + BaseURL: "/dashboard", + QueryParam: "?id=", + EmptyText: "Select an issue to view details", + DisplayOwner: props.DisplayOwner, + DisplayAssignee: props.DisplayAssignee, + }) + } +} \ No newline at end of file diff --git a/pkg/web/routes/dashboard_templ.go b/pkg/web/routes/dashboard_templ.go new file mode 100644 index 0000000..9ec5e3a --- /dev/null +++ b/pkg/web/routes/dashboard_templ.go @@ -0,0 +1,83 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package routes + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/web/components" +) + +type DashboardProps struct { + ActiveIssues []models.Issue + ClosedIssues []models.Issue + SelectedIssue *models.Issue + SelectedID string + SearchQuery string + DisplayOwner string + DisplayAssignee string +} + +func Dashboard(props DashboardProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = components.DashboardPage(components.DashboardPageProps{ + ActiveIssues: props.ActiveIssues, + ClosedIssues: props.ClosedIssues, + SelectedIssue: props.SelectedIssue, + SelectedID: props.SelectedID, + BaseURL: "/dashboard", + QueryParam: "?id=", + EmptyText: "Select an issue to view details", + DisplayOwner: props.DisplayOwner, + DisplayAssignee: props.DisplayAssignee, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = BaseLayout(BaseLayoutProps{SearchQuery: props.SearchQuery}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/routes/issue_detail.templ b/pkg/web/routes/issue_detail.templ index 92b34a7..25d8c32 100644 --- a/pkg/web/routes/issue_detail.templ +++ b/pkg/web/routes/issue_detail.templ @@ -1,14 +1,15 @@ package routes import ( - "fmt" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/web/components" ) type IssueDetailProps struct { - Issue models.Issue - Comments []*models.Comment + Issue models.Issue + DisplayOwner string + DisplayAssignee string + Comments []*models.Comment } templ IssueDetailContent(props IssueDetailProps) { @@ -25,49 +26,8 @@ templ IssueDetailContent(props IssueDetailProps) { Back to Issues -
-
-
-
- { props.Issue.ID } -

{ props.Issue.Title }

-
-
- @StatusBadge(props.Issue.Status) - @TypeBadge(props.Issue.IssueType) - @PriorityBadge(props.Issue.Priority) -
-
- if props.Issue.Description != "" { -
-

Description

-

{ props.Issue.Description }

-
- } -
-
-
- Created -

{ props.Issue.CreatedAt.Format("Jan 2, 2006") }

-
-
- Updated -

{ props.Issue.UpdatedAt.Format("Jan 2, 2006") }

-
- if props.Issue.Assignee != "" { -
- Assignee -

{ props.Issue.Assignee }

-
- } - if props.Issue.Owner != "" { -
- Owner -

{ props.Issue.Owner }

-
- } -
-
+
+ @components.IssueDetailCard(components.IssueDetailCardProps{Issue: props.Issue, DisplayOwner: props.DisplayOwner, DisplayAssignee: props.DisplayAssignee})
@components.CommentSection(components.CommentSectionProps{ IssueID: props.Issue.ID, @@ -81,54 +41,3 @@ templ IssueDetail(props IssueDetailProps) { @IssueDetailContent(props) } } - -templ StatusBadge(status models.Status) { - switch status { - case "open": - Open - case "in_progress": - In Progress - case "closed": - Closed - case "blocked": - Blocked - case "deferred": - Deferred - default: - { string(status) } - } -} - -templ TypeBadge(issueType models.IssueType) { - switch issueType { - case "bug": - Bug - case "feature": - Feature - case "task": - Task - case "chore": - Chore - case "epic": - Epic - default: - { string(issueType) } - } -} - -templ PriorityBadge(priority int) { - switch priority { - case 0: - P0 - Critical - case 1: - P1 - High - case 2: - P2 - Medium - case 3: - P3 - Low - case 4: - P4 - Minimal - default: - { fmt.Sprintf("P%d", priority) } - } -} diff --git a/pkg/web/routes/issue_detail_templ.go b/pkg/web/routes/issue_detail_templ.go index 79e00d3..ea8c95f 100644 --- a/pkg/web/routes/issue_detail_templ.go +++ b/pkg/web/routes/issue_detail_templ.go @@ -9,14 +9,15 @@ import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" import ( - "fmt" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/web/components" ) type IssueDetailProps struct { - Issue models.Issue - Comments []*models.Comment + Issue models.Issue + DisplayOwner string + DisplayAssignee string + Comments []*models.Comment } func IssueDetailContent(props IssueDetailProps) templ.Component { @@ -48,140 +49,15 @@ func IssueDetailContent(props IssueDetailProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "Back to Issues
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "Back to Issues
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.ID) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 32, Col: 66} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + templ_7745c5c3_Err = components.IssueDetailCard(components.IssueDetailCardProps{Issue: props.Issue, DisplayOwner: props.DisplayOwner, DisplayAssignee: props.DisplayAssignee}).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.Title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 33, Col: 61} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = StatusBadge(props.Issue.Status).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = TypeBadge(props.Issue.IssueType).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = PriorityBadge(props.Issue.Priority).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if props.Issue.Description != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Description

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.Description) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 44, Col: 62} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
Created

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.CreatedAt.Format("Jan 2, 2006")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 51, Col: 74} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

Updated

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.UpdatedAt.Format("Jan 2, 2006")) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 55, Col: 74} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if props.Issue.Assignee != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
Assignee

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.Assignee) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 60, Col: 52} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if props.Issue.Owner != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
Owner

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(props.Issue.Owner) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 66, Col: 49} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -192,7 +68,7 @@ func IssueDetailContent(props IssueDetailProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -216,12 +92,12 @@ func IssueDetail(props IssueDetailProps) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var10 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -239,7 +115,7 @@ func IssueDetail(props IssueDetailProps) templ.Component { } return nil }) - templ_7745c5c3_Err = BaseLayout(BaseLayoutProps{SearchQuery: ""}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = BaseLayout(BaseLayoutProps{SearchQuery: ""}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -247,214 +123,4 @@ func IssueDetail(props IssueDetailProps) templ.Component { }) } -func StatusBadge(status models.Status) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var11 := templ.GetChildren(ctx) - if templ_7745c5c3_Var11 == nil { - templ_7745c5c3_Var11 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - switch status { - case "open": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Open") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "in_progress": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "In Progress") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "closed": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Closed") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "blocked": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "Blocked") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "deferred": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "Deferred") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - default: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(string(status)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 98, Col: 39} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func TypeBadge(issueType models.IssueType) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var13 := templ.GetChildren(ctx) - if templ_7745c5c3_Var13 == nil { - templ_7745c5c3_Var13 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - switch issueType { - case "bug": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "Bug") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "feature": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "Feature") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "task": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "Task") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "chore": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "Chore") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case "epic": - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "Epic") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - default: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(string(issueType)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 115, Col: 56} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func PriorityBadge(priority int) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var15 := templ.GetChildren(ctx) - if templ_7745c5c3_Var15 == nil { - templ_7745c5c3_Var15 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - switch priority { - case 0: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "P0 - Critical") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 1: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "P1 - High") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 2: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "P2 - Medium") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 3: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "P3 - Low") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 4: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "P4 - Minimal") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - default: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", priority)) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 132, Col: 53} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/routes/layout.templ b/pkg/web/routes/layout.templ index 45f079a..8bd0caf 100644 --- a/pkg/web/routes/layout.templ +++ b/pkg/web/routes/layout.templ @@ -22,7 +22,7 @@ func buildLayoutProps(searchQuery string) components.LayoutProps { }, }, Routes: []components.NavRoutes{ - {Name: "Dashboard", Path: "/", Icon: components.IconDashboard()}, + {Name: "Dashboard", Path: "/dashboard", Icon: components.IconDashboard()}, {Name: "Issues", Path: "/", Icon: components.IconIssues()}, {Name: "Agile Board", Path: "/board", Icon: components.IconAgileBoard()}, {Name: "Backlog", Path: "/backlog", Icon: components.IconBacklog()}, diff --git a/pkg/web/routes/layout_templ.go b/pkg/web/routes/layout_templ.go index 322dacb..e8dc269 100644 --- a/pkg/web/routes/layout_templ.go +++ b/pkg/web/routes/layout_templ.go @@ -30,7 +30,7 @@ func buildLayoutProps(searchQuery string) components.LayoutProps { }, }, Routes: []components.NavRoutes{ - {Name: "Dashboard", Path: "/", Icon: components.IconDashboard()}, + {Name: "Dashboard", Path: "/dashboard", Icon: components.IconDashboard()}, {Name: "Issues", Path: "/", Icon: components.IconIssues()}, {Name: "Agile Board", Path: "/board", Icon: components.IconAgileBoard()}, {Name: "Backlog", Path: "/backlog", Icon: components.IconBacklog()}, diff --git a/pkg/web/server/routes.go b/pkg/web/server/routes.go index 9a2169c..ca9497c 100644 --- a/pkg/web/server/routes.go +++ b/pkg/web/server/routes.go @@ -27,6 +27,7 @@ func (s *Server) RegisterRoutes(assets embed.FS) http.Handler { s.handleAssets(r, assets) r.Get("/", handler.IndexHandler) + r.Get("/dashboard", handler.DashboardHandler) r.Get("/status", handler.HandleTaskStatus) r.Route("/issues", func(r chi.Router) { From cd2c61300158364eaba2d5d863efd203b50eaed0 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Mon, 23 Feb 2026 20:10:59 +0100 Subject: [PATCH 20/20] remove comments from input.css --- pkg/web/input.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/web/input.css b/pkg/web/input.css index 73adc6f..73bfd82 100644 --- a/pkg/web/input.css +++ b/pkg/web/input.css @@ -6,8 +6,4 @@ @plugin "@tailwindcss/typography"; @plugin "daisyui" { themes: all; -} -/* Safelist: force badge classes into build (class names in comments are scanned by Tailwind v4) */ -/* StatusBadge: inline-flex items-center rounded-md px-2.5 py-0.5 text-xs font-medium bg-sky-100 text-sky-800 bg-amber-100 text-amber-800 bg-emerald-100 text-emerald-800 bg-red-100 text-red-800 bg-slate-100 text-slate-600 text-slate-700 */ -/* TypeBadge: border border-red-200 border-emerald-200 border-sky-200 border-amber-200 border-violet-200 border-slate-200 text-red-700 text-emerald-700 text-sky-700 text-amber-700 text-violet-700 text-slate-600 */ -/* PriorityBadge: gap-1 shadow-sm opacity-75 font-semibold bg-red-100 text-red-800 bg-amber-100 bg-sky-100 bg-slate-50 border-slate-300 */ \ No newline at end of file +} \ No newline at end of file
No issues
{ issue.ID }{ issue.Title }@StatusBadge(issue.Status)@TypeBadge(issue.IssueType)@PriorityBadge(issue.Priority)
{ issue.ID }{ issue.Title }@StatusBadge(issue.Status)@TypeBadge(issue.IssueType)@PriorityBadge(issue.Priority)
No issues
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/dashboard.templ`, Line: 85, Col: 107} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/dashboard.templ`, Line: 86, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = StatusBadge(issue.Status).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TypeBadge(issue.IssueType).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = PriorityBadge(issue.Priority).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/dashboard.templ`, Line: 93, Col: 107} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/dashboard.templ`, Line: 94, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = StatusBadge(issue.Status).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = TypeBadge(issue.IssueType).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = PriorityBadge(issue.Priority).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "