From 2df20c2840e29fb9cc034c69077621360e48e34d Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:48:47 -1000 Subject: [PATCH 1/9] refactor: adopt portigo infrastructure patterns Add config package with build-tag-switched dev/prod environments, structured logging via zerolog, Taskfile for dev workflow, golangci-lint config, testutil package, and improved DB setup with proper SQLite pragmas and cleanup. Rename sqlc output package from gen to repository. Switch to allowlist .gitignore, Alpine+UPX+scratch Dockerfile, and CI pipeline with test/lint gates before deploy. --- .env.example | 15 ++- .gitea/workflows/deploy.yml | 34 +++++- .gitignore | 40 +++++-- .golangci.yml | 45 ++++++++ Dockerfile | 31 ++---- Taskfile.yml | 63 +++++++++++ config/config.go | 73 +++++++++++++ config/config_dev.go | 9 ++ config/config_prod.go | 9 ++ config/config_test_helper.go | 19 ++++ db/db.go | 57 ++++++++-- db/persister.go | 28 ++--- db/{gen => repository}/chat.sql.go | 2 +- db/{gen => repository}/db.go | 2 +- db/{gen => repository}/games.sql.go | 2 +- db/{gen => repository}/models.go | 2 +- db/{gen => repository}/snake_games.sql.go | 2 +- db/{gen => repository}/users.sql.go | 2 +- db/sqlc.yaml | 8 +- go.mod | 27 ++--- go.sum | 62 +++++------ logging/log.go | 41 +++++++ logging/middleware.go | 126 ++++++++++++++++++++++ main.go | 52 +++++---- testutil/db.go | 47 ++++++++ testutil/sessions.go | 31 ++++++ ui/status.go | 8 +- 27 files changed, 694 insertions(+), 143 deletions(-) create mode 100644 .golangci.yml create mode 100644 Taskfile.yml create mode 100644 config/config.go create mode 100644 config/config_dev.go create mode 100644 config/config_prod.go create mode 100644 config/config_test_helper.go rename db/{gen => repository}/chat.sql.go (98%) rename db/{gen => repository}/db.go (96%) rename db/{gen => repository}/games.sql.go (99%) rename db/{gen => repository}/models.go (98%) rename db/{gen => repository}/snake_games.sql.go (99%) rename db/{gen => repository}/users.sql.go (98%) create mode 100644 logging/log.go create mode 100644 logging/middleware.go create mode 100644 testutil/db.go create mode 100644 testutil/sessions.go diff --git a/.env.example b/.env.example index 0643835..7ae4ff9 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,16 @@ -# Application URL for invite links (defaults to https://games.adriatica.io) +# Log level (TRACE, DEBUG, INFO, WARN, ERROR). Defaults to INFO. +# LOG_LEVEL=DEBUG + +# SQLite database path. Defaults to data/c4.db. +# DB_PATH=data/c4.db + +# Application URL for invite links. Defaults to https://games.adriatica.io. # APP_URL=http://localhost:7331 -# Server port (defaults to 7331) +# Server port. Defaults to 7331. # PORT=7331 + +# Goose CLI migration config (only needed for running goose manually) +GOOSE_DRIVER=sqlite3 +GOOSE_DBSTRING=data/c4.db?_pragma=foreign_keys(1)&_pragma=journal_mode(WAL) +GOOSE_MIGRATION_DIR=db/migrations diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 1a314c7..f8a7271 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,14 +1,44 @@ -name: Deploy c4 +name: CI / Deploy on: push: branches: [main] + pull_request: env: DEPLOY_DIR: /home/ryan/c4 jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Run tests + run: go test ./... + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Install golangci-lint + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest + + - name: Run linter + run: golangci-lint run + deploy: + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + needs: [test, lint] runs-on: games steps: - uses: actions/checkout@v4 @@ -21,8 +51,6 @@ jobs: - name: Ensure data directory exists with correct ownership run: | mkdir -p $DEPLOY_DIR/data - # UID 5 / GID 60 = games:games in the container (debian:bookworm-slim) - sudo chown 5:60 $DEPLOY_DIR/data - name: Rebuild and restart run: cd $DEPLOY_DIR && docker compose up -d --build --remove-orphans diff --git a/.gitignore b/.gitignore index 58dd18e..9dca485 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,34 @@ -c4 -c4.db -data/ -.env +# Allowlisting gitignore: ignore everything, then un-ignore what we track. +# source: https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# Deploy artifacts -c4-deploy-*.tar.gz -c4-deploy-*_b64*.txt +# Ignore everything +* + +# But not these files... +!.gitignore + +!*.go +!*.sql +!go.sum +!go.mod +!Taskfile.yml +!sqlc.yaml +!.golangci.yml +!.gitea/workflows/*.yml + +!.env.example +!LICENSE + +!assets/**/* + +# Generated CSS stays out of version control +assets/css/output.css + +# Deploy scripts and configs +!deploy/*.sh +!deploy/*.service +!docker-compose.yml +!Dockerfile + +# ...even if they are in subdirectories +!*/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..961c8c8 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,45 @@ +version: "2" + +linters: + default: standard + enable: + - errcheck + - govet + - staticcheck + - gosec + - bodyclose + - sqlclosecheck + - misspell + - errname + - copyloopvar + + settings: + staticcheck: + checks: + - all + - "-ST1001" # dot imports + - "-ST1003" # naming conventions + gosec: + excludes: + - G104 # unhandled errors — redundant with errcheck + - G107 # HTTP requests with variable URLs — expected in a web app + - G115 # integer overflow conversion + - G301 # directory permissions 0750 — 0755 is standard for data dirs + - G404 # weak random — acceptable for game IDs and player IDs + +formatters: + enable: + - gofmt + - goimports + + settings: + goimports: + local-prefixes: + - github.com/ryanhamamura/c4 + +issues: + exclude-rules: + - path: _test\.go + linters: + - gosec + - errcheck diff --git a/Dockerfile b/Dockerfile index 08261c3..a22afc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM golang:1.25.4-bookworm AS build +FROM docker.io/golang:1.25.4-alpine AS build + +RUN apk add --no-cache upx WORKDIR /src COPY go.mod go.sum ./ @@ -6,24 +8,11 @@ RUN go mod download COPY . . RUN go tool gotailwind -i assets/css/input.css -o assets/css/output.css --minify -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /c4 . +RUN --mount=type=cache,target=/root/.cache/go-build \ + CGO_ENABLED=0 go build -ldflags="-s" -o /bin/c4 . +RUN upx -9 -k /bin/c4 -FROM debian:bookworm-slim - -RUN apt-get update && \ - apt-get install -y --no-install-recommends ca-certificates wget && \ - rm -rf /var/lib/apt/lists/* - -COPY --from=build /c4 /usr/local/bin/c4 - -WORKDIR /app -RUN mkdir data && chown games:games data - -USER games - -EXPOSE 8080 - -HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ - CMD wget -qO /dev/null http://localhost:8080/ - -CMD ["c4"] +FROM scratch +ENV PORT=8080 +COPY --from=build /bin/c4 / +ENTRYPOINT ["/c4"] diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..f5b9d29 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,63 @@ +version: "3" + +tasks: + build:styles: + desc: Build TailwindCSS styles + cmds: + - go tool gotailwind -i assets/css/input.css -o assets/css/output.css --minify + sources: + - "assets/css/input.css" + - "**/*.go" + generates: + - "assets/css/output.css" + + build: + desc: Production build to bin/c4 + cmds: + - go build -o bin/c4 . + deps: + - build:styles + + live:styles: + desc: Watch and rebuild TailwindCSS styles + cmds: + - go tool gotailwind -i assets/css/input.css -o assets/css/output.css -w + + live:server: + desc: Run server with hot-reload via air + cmds: + - | + go tool air \ + -build.cmd "go build -tags=dev -o tmp/bin/c4 ." \ + -build.bin "tmp/bin/c4" \ + -build.exclude_dir "data,bin,tmp,deploy" \ + -build.include_ext "go" \ + -misc.clean_on_exit "true" + + live: + desc: Dev mode with hot-reload + deps: + - live:styles + - live:server + + test: + desc: Run the test suite + cmds: + - go test ./... + + lint: + desc: Run golangci-lint + cmds: + - golangci-lint run + + run: + desc: Build and run the server + cmds: + - ./bin/c4 + deps: + - build + + default: + desc: Run the default task (live) + cmds: + - task: live diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..6c09cd9 --- /dev/null +++ b/config/config.go @@ -0,0 +1,73 @@ +// Package config provides build-tag-switched application configuration. +// The global Config singleton is initialized at import time via init() +// and can be overridden in tests with LoadForTest(). +package config + +import ( + "os" + "sync" + + "github.com/joho/godotenv" + "github.com/rs/zerolog" +) + +type Environment string + +const ( + Dev Environment = "dev" + Prod Environment = "prod" +) + +type Config struct { + Environment Environment + Host string + Port string + LogLevel zerolog.Level + AppURL string + DBPath string +} + +var ( + Global *Config + once sync.Once +) + +func init() { + once.Do(func() { + Global = Load() + }) +} + +func getEnv(key, fallback string) string { + if val, ok := os.LookupEnv(key); ok { + return val + } + return fallback +} + +func loadBase() *Config { + godotenv.Load() //nolint:errcheck // .env file is optional + + return &Config{ + Host: getEnv("HOST", "0.0.0.0"), + Port: getEnv("PORT", "7331"), + LogLevel: func() zerolog.Level { + switch os.Getenv("LOG_LEVEL") { + case "TRACE": + return zerolog.TraceLevel + case "DEBUG": + return zerolog.DebugLevel + case "INFO": + return zerolog.InfoLevel + case "WARN": + return zerolog.WarnLevel + case "ERROR": + return zerolog.ErrorLevel + default: + return zerolog.InfoLevel + } + }(), + AppURL: getEnv("APP_URL", "https://games.adriatica.io"), + DBPath: getEnv("DB_PATH", "data/c4.db"), + } +} diff --git a/config/config_dev.go b/config/config_dev.go new file mode 100644 index 0000000..53af2dd --- /dev/null +++ b/config/config_dev.go @@ -0,0 +1,9 @@ +//go:build dev + +package config + +func Load() *Config { + cfg := loadBase() + cfg.Environment = Dev + return cfg +} diff --git a/config/config_prod.go b/config/config_prod.go new file mode 100644 index 0000000..9598fb1 --- /dev/null +++ b/config/config_prod.go @@ -0,0 +1,9 @@ +//go:build !dev + +package config + +func Load() *Config { + cfg := loadBase() + cfg.Environment = Prod + return cfg +} diff --git a/config/config_test_helper.go b/config/config_test_helper.go new file mode 100644 index 0000000..aec2a48 --- /dev/null +++ b/config/config_test_helper.go @@ -0,0 +1,19 @@ +package config + +import ( + "github.com/rs/zerolog" +) + +// LoadForTest sets config.Global to safe defaults without reading +// environment variables or .env files. Call this in TestMain or at the +// top of tests that import packages which depend on config.Global. +func LoadForTest() { + Global = &Config{ + Environment: Dev, + Host: "127.0.0.1", + Port: "0", + LogLevel: zerolog.WarnLevel, + AppURL: "http://localhost:0", + DBPath: ":memory:", + } +} diff --git a/db/db.go b/db/db.go index 04bb9bb..3d7031a 100644 --- a/db/db.go +++ b/db/db.go @@ -1,33 +1,70 @@ +// Package db handles SQLite database setup, pragma configuration, and +// goose migrations. package db import ( "database/sql" "embed" + "errors" + "fmt" + "io/fs" + "log/slog" + "os" + "path/filepath" "github.com/pressly/goose/v3" _ "modernc.org/sqlite" ) //go:embed migrations/*.sql -var migrations embed.FS +var MigrationFS embed.FS var DB *sql.DB -func Init(dbPath string) error { +func Init(dbPath string) (func(), error) { + if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil { + return nil, fmt.Errorf("creating data dir: %w", err) + } + + // busy_timeout must be first because the connection needs to block on + // busy before WAL mode is set in case it hasn't been set already. + pragmas := "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-32000)" var err error - DB, err = sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000") + DB, err = goose.OpenDBWithDriver("sqlite", dbPath+pragmas) if err != nil { - return err + return nil, fmt.Errorf("opening database: %w", err) } - DB.SetMaxOpenConns(1) - goose.SetBaseFS(migrations) + if err := DB.Ping(); err != nil { + return nil, errors.Join(fmt.Errorf("pinging database: %w", err), DB.Close()) + } + slog.Info("db connected", "db", dbPath) + + sub, err := fs.Sub(MigrationFS, "migrations") + if err != nil { + return nil, errors.Join(fmt.Errorf("migrations sub fs: %w", err), DB.Close()) + } + goose.SetBaseFS(sub) + if err := goose.SetDialect("sqlite3"); err != nil { - return err + return nil, errors.Join(fmt.Errorf("setting goose dialect: %w", err), DB.Close()) } - if err := goose.Up(DB, "migrations"); err != nil { - return err + if err := goose.Up(DB, "."); err != nil { + return nil, errors.Join(fmt.Errorf("running migrations: %w", err), DB.Close()) } - return nil + if _, err := DB.Exec("PRAGMA optimize"); err != nil { + return nil, errors.Join(fmt.Errorf("pragma optimize: %w", err), DB.Close()) + } + + cleanup := func() { + if _, err := DB.Exec("PRAGMA optimize(0x10002)"); err != nil { + slog.Error("pragma optimize at shutdown", "error", err) + } + if err := DB.Close(); err != nil { + slog.Error("closing database", "error", err) + } + } + + return cleanup, nil } diff --git a/db/persister.go b/db/persister.go index 5e68b17..ddab719 100644 --- a/db/persister.go +++ b/db/persister.go @@ -5,17 +5,17 @@ import ( "database/sql" "slices" - "github.com/ryanhamamura/c4/db/gen" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/snake" "github.com/ryanhamamura/c4/ui" ) type GamePersister struct { - queries *gen.Queries + queries *repository.Queries } -func NewGamePersister(q *gen.Queries) *GamePersister { +func NewGamePersister(q *repository.Queries) *GamePersister { return &GamePersister{queries: q} } @@ -24,7 +24,7 @@ func (p *GamePersister) SaveGame(g *game.Game) error { _, err := p.queries.GetGame(ctx, g.ID) if err == sql.ErrNoRows { - _, err = p.queries.CreateGame(ctx, gen.CreateGameParams{ + _, err = p.queries.CreateGame(ctx, repository.CreateGameParams{ ID: g.ID, Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), @@ -51,7 +51,7 @@ func (p *GamePersister) SaveGame(g *game.Game) error { rematchGameID = sql.NullString{String: *g.RematchGameID, Valid: true} } - return p.queries.UpdateGame(ctx, gen.UpdateGameParams{ + return p.queries.UpdateGame(ctx, repository.UpdateGameParams{ Board: g.BoardToJSON(), CurrentTurn: int64(g.CurrentTurn), Status: int64(g.Status), @@ -100,7 +100,7 @@ func (p *GamePersister) SaveGamePlayer(gameID string, player *game.Player, slot guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} } - return p.queries.CreateGamePlayer(ctx, gen.CreateGamePlayerParams{ + return p.queries.CreateGamePlayer(ctx, repository.CreateGamePlayerParams{ GameID: gameID, UserID: userID, GuestPlayerID: guestPlayerID, @@ -144,10 +144,10 @@ func (p *GamePersister) DeleteGame(id string) error { // SnakePersister implements snake.Persister type SnakePersister struct { - queries *gen.Queries + queries *repository.Queries } -func NewSnakePersister(q *gen.Queries) *SnakePersister { +func NewSnakePersister(q *repository.Queries) *SnakePersister { return &SnakePersister{queries: q} } @@ -167,7 +167,7 @@ func (p *SnakePersister) SaveSnakeGame(sg *snake.SnakeGame) error { _, err := p.queries.GetSnakeGame(ctx, sg.ID) if err == sql.ErrNoRows { - _, err = p.queries.CreateSnakeGame(ctx, gen.CreateSnakeGameParams{ + _, err = p.queries.CreateSnakeGame(ctx, repository.CreateSnakeGameParams{ ID: sg.ID, Board: boardJSON, Status: int64(sg.Status), @@ -192,7 +192,7 @@ func (p *SnakePersister) SaveSnakeGame(sg *snake.SnakeGame) error { rematchGameID = sql.NullString{String: *sg.RematchGameID, Valid: true} } - return p.queries.UpdateSnakeGame(ctx, gen.UpdateSnakeGameParams{ + return p.queries.UpdateSnakeGame(ctx, repository.UpdateSnakeGameParams{ Board: boardJSON, Status: int64(sg.Status), WinnerUserID: winnerUserID, @@ -247,7 +247,7 @@ func (p *SnakePersister) SaveSnakePlayer(gameID string, player *snake.Player) er guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} } - return p.queries.CreateSnakePlayer(ctx, gen.CreateSnakePlayerParams{ + return p.queries.CreateSnakePlayer(ctx, repository.CreateSnakePlayerParams{ GameID: gameID, UserID: userID, GuestPlayerID: guestPlayerID, @@ -290,15 +290,15 @@ func (p *SnakePersister) DeleteSnakeGame(id string) error { } type ChatPersister struct { - queries *gen.Queries + queries *repository.Queries } -func NewChatPersister(q *gen.Queries) *ChatPersister { +func NewChatPersister(q *repository.Queries) *ChatPersister { return &ChatPersister{queries: q} } func (p *ChatPersister) SaveChatMessage(gameID string, msg ui.C4ChatMessage) error { - return p.queries.CreateChatMessage(context.Background(), gen.CreateChatMessageParams{ + return p.queries.CreateChatMessage(context.Background(), repository.CreateChatMessageParams{ GameID: gameID, Nickname: msg.Nickname, Color: int64(msg.Color), diff --git a/db/gen/chat.sql.go b/db/repository/chat.sql.go similarity index 98% rename from db/gen/chat.sql.go rename to db/repository/chat.sql.go index 2d956cd..1694e02 100644 --- a/db/gen/chat.sql.go +++ b/db/repository/chat.sql.go @@ -3,7 +3,7 @@ // sqlc v1.30.0 // source: chat.sql -package gen +package repository import ( "context" diff --git a/db/gen/db.go b/db/repository/db.go similarity index 96% rename from db/gen/db.go rename to db/repository/db.go index d577e39..998bfd3 100644 --- a/db/gen/db.go +++ b/db/repository/db.go @@ -2,7 +2,7 @@ // versions: // sqlc v1.30.0 -package gen +package repository import ( "context" diff --git a/db/gen/games.sql.go b/db/repository/games.sql.go similarity index 99% rename from db/gen/games.sql.go rename to db/repository/games.sql.go index 6bb412d..7883526 100644 --- a/db/gen/games.sql.go +++ b/db/repository/games.sql.go @@ -3,7 +3,7 @@ // sqlc v1.30.0 // source: games.sql -package gen +package repository import ( "context" diff --git a/db/gen/models.go b/db/repository/models.go similarity index 98% rename from db/gen/models.go rename to db/repository/models.go index f539b79..de3f897 100644 --- a/db/gen/models.go +++ b/db/repository/models.go @@ -2,7 +2,7 @@ // versions: // sqlc v1.30.0 -package gen +package repository import ( "database/sql" diff --git a/db/gen/snake_games.sql.go b/db/repository/snake_games.sql.go similarity index 99% rename from db/gen/snake_games.sql.go rename to db/repository/snake_games.sql.go index ec1d547..1c80c2b 100644 --- a/db/gen/snake_games.sql.go +++ b/db/repository/snake_games.sql.go @@ -3,7 +3,7 @@ // sqlc v1.30.0 // source: snake_games.sql -package gen +package repository import ( "context" diff --git a/db/gen/users.sql.go b/db/repository/users.sql.go similarity index 98% rename from db/gen/users.sql.go rename to db/repository/users.sql.go index 5adfa6e..4654293 100644 --- a/db/gen/users.sql.go +++ b/db/repository/users.sql.go @@ -3,7 +3,7 @@ // sqlc v1.30.0 // source: users.sql -package gen +package repository import ( "context" diff --git a/db/sqlc.yaml b/db/sqlc.yaml index 0c1840b..47ad0a3 100644 --- a/db/sqlc.yaml +++ b/db/sqlc.yaml @@ -5,5 +5,9 @@ sql: schema: "migrations" gen: go: - package: "gen" - out: "gen" + package: "repository" + out: "repository" + emit_db_tags: true + emit_json_tags: true + emit_result_struct_pointers: true + emit_pointers_for_null_types: true diff --git a/go.mod b/go.mod index 2c8644a..5b66895 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,28 @@ module github.com/ryanhamamura/c4 go 1.25.4 require ( + github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de + github.com/alexedwards/scs/v2 v2.9.0 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 - github.com/pressly/goose/v3 v3.26.0 + github.com/pressly/goose/v3 v3.27.0 + github.com/rs/zerolog v1.34.0 github.com/ryanhamamura/via v0.23.0 - golang.org/x/crypto v0.47.0 - modernc.org/sqlite v1.44.0 + golang.org/x/crypto v0.48.0 + modernc.org/sqlite v1.46.1 ) require ( github.com/CAFxX/httpcompression v0.0.9 // indirect - github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de // indirect - github.com/alexedwards/scs/v2 v2.9.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/antithesishq/antithesis-sdk-go v0.5.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/delaneyj/toolbelt v0.9.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/go-tpm v0.9.7 // indirect - github.com/hookenz/gotailwind/v4 v4.1.18 // indirect - github.com/klauspost/compress v1.18.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/hookenz/gotailwind/v4 v4.2.1 // indirect + github.com/klauspost/compress v1.18.4 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect @@ -33,18 +34,18 @@ require ( github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rs/zerolog v1.34.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/starfederation/datastar-go v1.0.3 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.40.0 // indirect + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect golang.org/x/time v0.14.0 // indirect maragu.dev/gomponents v1.2.0 // indirect - modernc.org/libc v1.67.4 // indirect + modernc.org/libc v1.68.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 4adb164..e30bc87 100644 --- a/go.sum +++ b/go.sum @@ -31,16 +31,17 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hookenz/gotailwind/v4 v4.1.18 h1:1h3XwTVx1dEBm6A0bcosAplNCde+DCmVJG0arLy5fBE= -github.com/hookenz/gotailwind/v4 v4.1.18/go.mod h1:IfiJtdp8ExV9HV2XUiVjRBvB3QewVXVKWoAGEcpjfNE= +github.com/hookenz/gotailwind/v4 v4.2.1 h1:FpZLtAAbHH7wMvyGYT+01vTLFITGMGZGMtEbp7dd2dM= +github.com/hookenz/gotailwind/v4 v4.2.1/go.mod h1:IfiJtdp8ExV9HV2XUiVjRBvB3QewVXVKWoAGEcpjfNE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -65,21 +66,20 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= -github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= +github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM= -github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY= +github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= +github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/ryanhamamura/via v0.21.2 h1:osR6peY/mZSl9SNPeEv6IvzGU0akkQfQzQJgA74+7mk= -github.com/ryanhamamura/via v0.21.2/go.mod h1:rpJewNVG6tgginZN7Be3qqRuol70+v1sFCKD4UjHsQo= github.com/ryanhamamura/via v0.23.0 h1:0e7nytisazcWq7uxs6T27GM3FwzosCMenkxJd+78Lko= github.com/ryanhamamura/via v0.23.0/go.mod h1:rpJewNVG6tgginZN7Be3qqRuol70+v1sFCKD4UjHsQo= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= @@ -103,24 +103,24 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= -golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -129,18 +129,18 @@ maragu.dev/gomponents v1.2.0 h1:H7/N5htz1GCnhu0HB1GasluWeU2rJZOYztVEyN61iTc= maragu.dev/gomponents v1.2.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= -modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= +modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so= +modernc.org/ccgo/v4 v4.30.2/go.mod h1:yZMnhWEdW0qw3EtCndG1+ldRrVGS+bIwyWmAWzS0XEw= modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= -modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg= -modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA= +modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ= +modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -149,8 +149,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.44.0 h1:YjCKJnzZde2mLVy0cMKTSL4PxCmbIguOq9lGp8ZvGOc= -modernc.org/sqlite v1.44.0/go.mod h1:2Dq41ir5/qri7QJJJKNZcP4UF7TsX/KNeykYgPDtGhE= +modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= +modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/logging/log.go b/logging/log.go new file mode 100644 index 0000000..4cdaed3 --- /dev/null +++ b/logging/log.go @@ -0,0 +1,41 @@ +// Package logging configures zerolog and provides HTTP request logging middleware. +package logging + +import ( + "io" + stdlog "log" + "os" + + "github.com/ryanhamamura/c4/config" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/rs/zerolog/pkgerrors" +) + +func SetupLogger(env config.Environment, level zerolog.Level) *zerolog.Logger { + zerolog.ErrorStackFieldName = "stack_trace" + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack + + zerolog.SetGlobalLevel(level) + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + var output io.Writer + switch env { + case config.Dev: + output = zerolog.ConsoleWriter{ + Out: os.Stderr, + TimeFormat: "2006/01/02 15:04:05", + } + case config.Prod: + output = os.Stderr + } + + logger := zerolog.New(output).With().Timestamp().Stack().Logger() + zerolog.DefaultContextLogger = &logger + log.Logger = logger + + stdlog.SetFlags(0) + stdlog.SetOutput(logger) + return &logger +} diff --git a/logging/middleware.go b/logging/middleware.go new file mode 100644 index 0000000..dd6404e --- /dev/null +++ b/logging/middleware.go @@ -0,0 +1,126 @@ +package logging + +import ( + "fmt" + "net/http" + "time" + + "github.com/ryanhamamura/c4/config" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +const ( + ansiReset = "\033[0m" + ansiBrightRed = "\033[31;1m" + ansiBrightGreen = "\033[32;1m" + ansiBrightYellow = "\033[33;1m" + ansiBrightMagenta = "\033[35;1m" + ansiBrightCyan = "\033[36;1m" + ansiGreen = "\033[32m" + ansiYellow = "\033[33m" + ansiRed = "\033[31m" +) + +func colorStatus(status int, useColor bool) string { + s := fmt.Sprintf("%d", status) + if !useColor { + return s + } + switch { + case status < 200: + return ansiBrightGreen + s + ansiReset + case status < 300: + return ansiBrightGreen + s + ansiReset + case status < 400: + return ansiBrightCyan + s + ansiReset + case status < 500: + return ansiBrightYellow + s + ansiReset + default: + return ansiBrightRed + s + ansiReset + } +} + +func colorMethod(method string, useColor bool) string { + if !useColor { + return method + } + return ansiBrightMagenta + method + ansiReset +} + +func colorLatency(d time.Duration, useColor bool) string { + s := d.String() + if !useColor { + return s + } + switch { + case d < 500*time.Millisecond: + return ansiGreen + s + ansiReset + case d < 5*time.Second: + return ansiYellow + s + ansiReset + default: + return ansiRed + s + ansiReset + } +} + +type responseWriter struct { + http.ResponseWriter + status int +} + +func (rw *responseWriter) WriteHeader(code int) { + rw.status = code + rw.ResponseWriter.WriteHeader(code) +} + +func RequestLogger(logger *zerolog.Logger, env config.Environment) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + rw := &responseWriter{ResponseWriter: w} + + next.ServeHTTP(rw, r) + + status := rw.status + if status == 0 { + status = http.StatusOK + } + l := log.Ctx(r.Context()) + if l.GetLevel() == zerolog.Disabled { + l = logger + } + + var evt *zerolog.Event + switch { + case status < 400: + evt = l.Info() + case status < 500: + evt = l.Warn() + case status < 600: + evt = l.Error() + default: + evt = l.Info() + } + + latency := time.Since(start) + switch env { + case config.Dev: + useColor := true + evt.Msg(fmt.Sprintf("%s %s %s [%s]", + colorStatus(status, useColor), + colorMethod(r.Method, useColor), + r.URL.Path, + colorLatency(latency, useColor), + )) + default: + evt. + Int("status", status). + Str("method", r.Method). + Str("path", r.URL.Path). + Dur("latency", latency). + Msg("request") + } + }) + } +} diff --git a/main.go b/main.go index fa6bce1..e324692 100644 --- a/main.go +++ b/main.go @@ -8,20 +8,20 @@ import ( "encoding/hex" "encoding/json" "io/fs" - "log" - "os" "sync" "time" - "github.com/google/uuid" - "github.com/joho/godotenv" - "github.com/ryanhamamura/c4/auth" + "github.com/ryanhamamura/c4/config" "github.com/ryanhamamura/c4/db" - "github.com/ryanhamamura/c4/db/gen" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/logging" "github.com/ryanhamamura/c4/snake" "github.com/ryanhamamura/c4/ui" + + "github.com/google/uuid" + "github.com/rs/zerolog/log" "github.com/ryanhamamura/via" "github.com/ryanhamamura/via/h" ) @@ -29,7 +29,7 @@ import ( var ( store = game.NewGameStore() snakeStore = snake.NewSnakeStore() - queries *gen.Queries + queries *repository.Queries chatPersister *db.ChatPersister ) @@ -43,39 +43,35 @@ func DaisyUIPlugin(v *via.V) { v.AppendToHead(h.Link(h.Rel("stylesheet"), h.Href("/assets/css/output.css?v="+version))) } -func port() string { - if p := os.Getenv("PORT"); p != "" { - return p - } - return "7331" -} - func main() { - _ = godotenv.Load() + cfg := config.Global + logger := logging.SetupLogger(cfg.Environment, cfg.LogLevel) - if err := os.MkdirAll("data", 0o755); err != nil { - log.Fatal(err) + cleanupDB, err := db.Init(cfg.DBPath) + if err != nil { + log.Fatal().Err(err).Msg("initializing database") } - if err := db.Init("data/c4.db"); err != nil { - log.Fatal(err) - } - queries = gen.New(db.DB) + defer cleanupDB() + + queries = repository.New(db.DB) store.SetPersister(db.NewGamePersister(queries)) snakeStore.SetPersister(db.NewSnakePersister(queries)) chatPersister = db.NewChatPersister(queries) sessionManager, err := via.NewSQLiteSessionManager(db.DB) if err != nil { - log.Fatal(err) + log.Fatal().Err(err).Msg("creating session manager") } + _ = logger + v := via.New() v.Config(via.Options{ - LogLevel: via.LogLevelDebug, - DocumentTitle: "Game Lobby", - ServerAddress: ":" + port(), - SessionManager: sessionManager, - Plugins: []via.Plugin{DaisyUIPlugin}, + LogLevel: via.LogLevelDebug, + DocumentTitle: "Game Lobby", + ServerAddress: ":" + cfg.Port, + SessionManager: sessionManager, + Plugins: []via.Plugin{DaisyUIPlugin}, }) subFS, _ := fs.Sub(assets, "assets") @@ -315,7 +311,7 @@ func main() { ctx := context.Background() id := uuid.New().String() - user, err := queries.CreateUser(ctx, gen.CreateUserParams{ + user, err := queries.CreateUser(ctx, repository.CreateUserParams{ ID: id, Username: username.String(), PasswordHash: hash, diff --git a/testutil/db.go b/testutil/db.go new file mode 100644 index 0000000..d2cb2f9 --- /dev/null +++ b/testutil/db.go @@ -0,0 +1,47 @@ +// Package testutil provides composable test helpers for spinning up +// real infrastructure (in-memory SQLite, session managers) in +// integration tests. +package testutil + +import ( + "database/sql" + "io/fs" + "testing" + + "github.com/ryanhamamura/c4/db" + "github.com/ryanhamamura/c4/db/repository" + + "github.com/pressly/goose/v3" + _ "modernc.org/sqlite" +) + +// NewTestDB opens an in-memory SQLite database with the same pragmas as +// production, runs all goose migrations, and returns the raw connection +// alongside the sqlc Queries handle. The database is closed automatically +// when the test finishes. +func NewTestDB(t *testing.T) (*sql.DB, *repository.Queries) { + t.Helper() + + pragmas := "?_pragma=busy_timeout(10000)&_pragma=journal_mode(WAL)&_pragma=journal_size_limit(200000000)&_pragma=synchronous(NORMAL)&_pragma=foreign_keys(ON)&_pragma=temp_store(MEMORY)&_pragma=cache_size(-32000)" + database, err := goose.OpenDBWithDriver("sqlite", ":memory:"+pragmas) + if err != nil { + t.Fatalf("open test database: %v", err) + } + t.Cleanup(func() { database.Close() }) //nolint:errcheck // test cleanup + + if err := database.Ping(); err != nil { + t.Fatalf("ping test database: %v", err) + } + + sub, err := fs.Sub(db.MigrationFS, "migrations") + if err != nil { + t.Fatalf("migrations sub fs: %v", err) + } + goose.SetBaseFS(sub) + + if err := goose.Up(database, "."); err != nil { + t.Fatalf("run migrations: %v", err) + } + + return database, repository.New(database) +} diff --git a/testutil/sessions.go b/testutil/sessions.go new file mode 100644 index 0000000..5753f7d --- /dev/null +++ b/testutil/sessions.go @@ -0,0 +1,31 @@ +package testutil + +import ( + "database/sql" + "net/http" + "testing" + "time" + + "github.com/alexedwards/scs/sqlite3store" + "github.com/alexedwards/scs/v2" +) + +// NewTestSessionManager creates an SCS session manager backed by the +// provided SQLite database. The background cleanup goroutine is stopped +// automatically when the test finishes. +func NewTestSessionManager(t *testing.T, db *sql.DB) *scs.SessionManager { + t.Helper() + + store := sqlite3store.New(db) + t.Cleanup(func() { store.StopCleanup() }) + + sm := scs.New() + sm.Store = store + sm.Lifetime = 30 * 24 * time.Hour + sm.Cookie.Path = "/" + sm.Cookie.HttpOnly = true + sm.Cookie.Secure = false + sm.Cookie.SameSite = http.SameSiteLaxMode + + return sm +} diff --git a/ui/status.go b/ui/status.go index 37a0e8c..3fb3793 100644 --- a/ui/status.go +++ b/ui/status.go @@ -1,8 +1,7 @@ package ui import ( - "os" - + "github.com/ryanhamamura/c4/config" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/via/h" ) @@ -118,10 +117,7 @@ func PlayerInfo(g *game.Game, myColor int) h.H { } func getBaseURL() string { - if url := os.Getenv("APP_URL"); url != "" { - return url - } - return "https://games.adriatica.io" + return config.Global.AppURL } func InviteLink(gameID string) h.H { From 8c3b3fc6eae768f9e8546403549b731974bf8225 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:16:25 -1000 Subject: [PATCH 2/9] refactor: replace via framework with chi + templ + datastar Migrate from the via meta-framework to direct dependencies: - chi for routing, templ for HTML templates, datastar for SSE/reactivity - Feature-sliced architecture (features/{auth,lobby,c4game,snakegame}/) - Shared layouts and components (features/common/) - Handler factory pattern (HandleX(deps) http.HandlerFunc) - Embedded NATS server (nats/), SCS sessions (sessions/), chi router wiring (router/) - Move ChatMessage domain type from ui package to game package - Remove old ui/ package (gomponents-based via/h views) - Remove via dependency from go.mod entirely --- db/persister.go | 9 +- features/auth/handlers.go | 133 +++ features/auth/pages/login_templ.go | 89 ++ features/auth/pages/register_templ.go | 89 ++ features/auth/routes.go | 16 + features/c4game/components/board_templ.go | 199 +++++ features/c4game/components/chat_templ.go | 173 ++++ features/c4game/components/status_templ.go | 352 ++++++++ features/c4game/handlers.go | 368 ++++++++ features/c4game/pages/game_templ.go | 219 +++++ features/c4game/routes.go | 29 + features/common/components/shared_templ.go | 199 +++++ features/common/layouts/base_templ.go | 69 ++ features/lobby/components/gamelist_templ.go | 239 +++++ features/lobby/components/types.go | 12 + features/lobby/handlers.go | 168 ++++ features/lobby/pages/lobby_templ.go | 339 +++++++ features/lobby/pages/types.go | 20 + features/lobby/routes.go | 29 + features/snakegame/components/board_templ.go | 295 ++++++ features/snakegame/components/chat_templ.go | 173 ++++ features/snakegame/components/status_templ.go | 470 ++++++++++ features/snakegame/handlers.go | 321 +++++++ features/snakegame/pages/game_templ.go | 277 ++++++ features/snakegame/routes.go | 22 + game/types.go | 8 + go.mod | 209 ++++- go.sum | 747 +++++++++++++++- main.go | 838 ++---------------- nats/nats.go | 69 ++ router/router.go | 76 ++ sessions/sessions.go | 31 + ui/auth.go | 130 --- ui/board.go | 69 -- ui/c4chat.go | 64 -- ui/gamelist.go | 110 --- ui/lobby.go | 153 ---- ui/snakeboard.go | 112 --- ui/snakechat.go | 63 -- ui/snakelobby.go | 124 --- ui/snakestatus.go | 161 ---- ui/status.go | 137 --- 42 files changed, 5519 insertions(+), 1891 deletions(-) create mode 100644 features/auth/handlers.go create mode 100644 features/auth/pages/login_templ.go create mode 100644 features/auth/pages/register_templ.go create mode 100644 features/auth/routes.go create mode 100644 features/c4game/components/board_templ.go create mode 100644 features/c4game/components/chat_templ.go create mode 100644 features/c4game/components/status_templ.go create mode 100644 features/c4game/handlers.go create mode 100644 features/c4game/pages/game_templ.go create mode 100644 features/c4game/routes.go create mode 100644 features/common/components/shared_templ.go create mode 100644 features/common/layouts/base_templ.go create mode 100644 features/lobby/components/gamelist_templ.go create mode 100644 features/lobby/components/types.go create mode 100644 features/lobby/handlers.go create mode 100644 features/lobby/pages/lobby_templ.go create mode 100644 features/lobby/pages/types.go create mode 100644 features/lobby/routes.go create mode 100644 features/snakegame/components/board_templ.go create mode 100644 features/snakegame/components/chat_templ.go create mode 100644 features/snakegame/components/status_templ.go create mode 100644 features/snakegame/handlers.go create mode 100644 features/snakegame/pages/game_templ.go create mode 100644 features/snakegame/routes.go create mode 100644 nats/nats.go create mode 100644 router/router.go create mode 100644 sessions/sessions.go delete mode 100644 ui/auth.go delete mode 100644 ui/board.go delete mode 100644 ui/c4chat.go delete mode 100644 ui/gamelist.go delete mode 100644 ui/lobby.go delete mode 100644 ui/snakeboard.go delete mode 100644 ui/snakechat.go delete mode 100644 ui/snakelobby.go delete mode 100644 ui/snakestatus.go delete mode 100644 ui/status.go diff --git a/db/persister.go b/db/persister.go index ddab719..f87760b 100644 --- a/db/persister.go +++ b/db/persister.go @@ -8,7 +8,6 @@ import ( "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/c4/ui" ) type GamePersister struct { @@ -297,7 +296,7 @@ func NewChatPersister(q *repository.Queries) *ChatPersister { return &ChatPersister{queries: q} } -func (p *ChatPersister) SaveChatMessage(gameID string, msg ui.C4ChatMessage) error { +func (p *ChatPersister) SaveChatMessage(gameID string, msg game.ChatMessage) error { return p.queries.CreateChatMessage(context.Background(), repository.CreateChatMessageParams{ GameID: gameID, Nickname: msg.Nickname, @@ -307,14 +306,14 @@ func (p *ChatPersister) SaveChatMessage(gameID string, msg ui.C4ChatMessage) err }) } -func (p *ChatPersister) LoadChatMessages(gameID string) ([]ui.C4ChatMessage, error) { +func (p *ChatPersister) LoadChatMessages(gameID string) ([]game.ChatMessage, error) { rows, err := p.queries.GetChatMessages(context.Background(), gameID) if err != nil { return nil, err } - msgs := make([]ui.C4ChatMessage, len(rows)) + msgs := make([]game.ChatMessage, len(rows)) for i, r := range rows { - msgs[i] = ui.C4ChatMessage{ + msgs[i] = game.ChatMessage{ Nickname: r.Nickname, Color: int(r.Color), Message: r.Message, diff --git a/features/auth/handlers.go b/features/auth/handlers.go new file mode 100644 index 0000000..1212e0b --- /dev/null +++ b/features/auth/handlers.go @@ -0,0 +1,133 @@ +package auth + +import ( + "database/sql" + "net/http" + + "github.com/alexedwards/scs/v2" + "github.com/google/uuid" + "github.com/ryanhamamura/c4/auth" + "github.com/ryanhamamura/c4/db/repository" + "github.com/ryanhamamura/c4/features/auth/pages" + "github.com/starfederation/datastar-go/datastar" +) + +type LoginSignals struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type RegisterSignals struct { + Username string `json:"username"` + Password string `json:"password"` + Confirm string `json:"confirm"` +} + +func HandleLoginPage() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := pages.LoginPage().Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + } +} + +func HandleRegisterPage() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := pages.RegisterPage().Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + } +} + +func HandleLogin(queries *repository.Queries, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var signals LoginSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + sse := datastar.NewSSE(w, r) + + user, err := queries.GetUserByUsername(r.Context(), signals.Username) + if err == sql.ErrNoRows { + sse.MarshalAndPatchSignals(map[string]any{"error": "Invalid username or password"}) //nolint:errcheck + return + } + if err != nil { + sse.MarshalAndPatchSignals(map[string]any{"error": "An error occurred"}) //nolint:errcheck + return + } + if !auth.CheckPassword(signals.Password, user.PasswordHash) { + sse.MarshalAndPatchSignals(map[string]any{"error": "Invalid username or password"}) //nolint:errcheck + return + } + + sessions.RenewToken(r.Context()) //nolint:errcheck + sessions.Put(r.Context(), "user_id", user.ID) + sessions.Put(r.Context(), "username", user.Username) + sessions.Put(r.Context(), "nickname", user.Username) + + redirectURL := "/" + if returnURL := sessions.GetString(r.Context(), "return_url"); returnURL != "" { + sessions.Put(r.Context(), "return_url", "") + redirectURL = returnURL + } + + sse.Redirect(redirectURL) //nolint:errcheck + } +} + +func HandleRegister(queries *repository.Queries, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var signals RegisterSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + sse := datastar.NewSSE(w, r) + + if err := auth.ValidateUsername(signals.Username); err != nil { + sse.MarshalAndPatchSignals(map[string]any{"error": err.Error()}) //nolint:errcheck + return + } + if err := auth.ValidatePassword(signals.Password); err != nil { + sse.MarshalAndPatchSignals(map[string]any{"error": err.Error()}) //nolint:errcheck + return + } + if signals.Password != signals.Confirm { + sse.MarshalAndPatchSignals(map[string]any{"error": "Passwords do not match"}) //nolint:errcheck + return + } + + hash, err := auth.HashPassword(signals.Password) + if err != nil { + sse.MarshalAndPatchSignals(map[string]any{"error": "An error occurred"}) //nolint:errcheck + return + } + + user, err := queries.CreateUser(r.Context(), repository.CreateUserParams{ + ID: uuid.New().String(), + Username: signals.Username, + PasswordHash: hash, + }) + if err != nil { + sse.MarshalAndPatchSignals(map[string]any{"error": "Username already taken"}) //nolint:errcheck + return + } + + sessions.RenewToken(r.Context()) //nolint:errcheck + sessions.Put(r.Context(), "user_id", user.ID) + sessions.Put(r.Context(), "username", user.Username) + sessions.Put(r.Context(), "nickname", user.Username) + + redirectURL := "/" + if returnURL := sessions.GetString(r.Context(), "return_url"); returnURL != "" { + sessions.Put(r.Context(), "return_url", "") + redirectURL = returnURL + } + + sse.Redirect(redirectURL) //nolint:errcheck + } +} diff --git a/features/auth/pages/login_templ.go b/features/auth/pages/login_templ.go new file mode 100644 index 0000000..115ea27 --- /dev/null +++ b/features/auth/pages/login_templ.go @@ -0,0 +1,89 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package pages + +//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/ryanhamamura/c4/features/common/layouts" + "github.com/starfederation/datastar-go/datastar" +) + +func LoginPage() 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Login

Sign in to your account

Don't have an account? Register

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Login").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/features/auth/pages/register_templ.go b/features/auth/pages/register_templ.go new file mode 100644 index 0000000..89efec3 --- /dev/null +++ b/features/auth/pages/register_templ.go @@ -0,0 +1,89 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package pages + +//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/ryanhamamura/c4/features/common/layouts" + "github.com/starfederation/datastar-go/datastar" +) + +func RegisterPage() 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Register

Create a new account

Already have an account? Login

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Register").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/features/auth/routes.go b/features/auth/routes.go new file mode 100644 index 0000000..98ad6df --- /dev/null +++ b/features/auth/routes.go @@ -0,0 +1,16 @@ +package auth + +import ( + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/ryanhamamura/c4/db/repository" +) + +func SetupRoutes(router chi.Router, queries *repository.Queries, sessions *scs.SessionManager) error { + router.Get("/login", HandleLoginPage()) + router.Get("/register", HandleRegisterPage()) + router.Post("/api/auth/login", HandleLogin(queries, sessions)) + router.Post("/api/auth/register", HandleRegister(queries, sessions)) + + return nil +} diff --git a/features/c4game/components/board_templ.go b/features/c4game/components/board_templ.go new file mode 100644 index 0000000..92f27b5 --- /dev/null +++ b/features/c4game/components/board_templ.go @@ -0,0 +1,199 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +func Board(g *game.Game, myColor 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_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 + } + for col := 0; col < 7; col++ { + templ_7745c5c3_Err = column(g, col, myColor).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + return nil + }) +} + +func column(g *game.Game, colIdx int, myColor 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_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if g.Status == game.StatusInProgress && myColor == g.CurrentTurn { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for row := 0; row < 6; row++ { + templ_7745c5c3_Err = cell(g, row, colIdx).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 + } + for row := 0; row < 6; row++ { + templ_7745c5c3_Err = cell(g, row, colIdx).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + } + return nil + }) +} + +func cell(g *game.Game, row int, col 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var5 = []any{cellClass(g, row, col)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) + 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 cellClass(g *game.Game, row, col int) string { + color := g.Board[row][col] + activeTurn := 0 + if g.Status == game.StatusInProgress { + activeTurn = g.CurrentTurn + } + + class := "cell" + switch color { + case 1: + class += " red" + case 2: + class += " yellow" + } + if g.IsWinningCell(row, col) { + class += " winning" + } + if color != 0 && color == activeTurn { + class += " active-turn" + } + return class +} + +// suppress unused import +var _ = fmt.Sprintf + +var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/components/chat_templ.go b/features/c4game/components/chat_templ.go new file mode 100644 index 0000000..c6fa087 --- /dev/null +++ b/features/c4game/components/chat_templ.go @@ -0,0 +1,173 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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/starfederation/datastar-go/datastar" +) + +type ChatMessage struct { + Nickname string `json:"nickname"` + Color int `json:"color"` + Message string `json:"message"` + Time int64 `json:"time"` +} + +var chatColors = map[int]string{ + 1: "#4a2a3a", + 2: "#2a4545", +} + +func Chat(messages []ChatMessage, gameID 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 + } + for _, m := range messages { + 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(m.Nickname) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 27, Col: 18} + } + _, 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 + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(m.Message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 29, Col: 22} + } + _, 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, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = chatAutoScroll().Render(ctx, templ_7745c5c3_Buffer) + 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 + } + return nil + }) +} + +func chatAutoScroll() 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func chatColor(color int) string { + if c, ok := chatColors[color]; ok { + return c + } + return "#666" +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/components/status_templ.go b/features/c4game/components/status_templ.go new file mode 100644 index 0000000..ba58ad9 --- /dev/null +++ b/features/c4game/components/status_templ.go @@ -0,0 +1,352 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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/ryanhamamura/c4/config" + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +func StatusBanner(g *game.Game, myColor 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_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var2 = []any{statusClass(g, myColor)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(statusMessage(g, myColor)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 11, Col: 29} + } + _, 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, 3, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if g.IsFinished() { + if g.RematchGameID != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Join Rematch") + 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 + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func PlayerInfo(g *game.Game, myColor 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, info := range playerInfoPairs(g, myColor) { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 = []any{"player-chip " + info.ColorClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, 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 + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(info.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 38, Col: 22} + } + _, 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, 13, "
") + 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 + } + return nil + }) +} + +func InviteLink(gameID 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_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

Share this link with your opponent:

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(config.Global.AppURL + "/game/" + gameID) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 48, Col: 45} + } + _, 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, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(config.Global.AppURL+"/game/"+gameID)) + 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 + } + return nil + }) +} + +func copyToClipboard(url string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_copyToClipboard_1463`, + Function: `function __templ_copyToClipboard_1463(url){navigator.clipboard.writeText(url) +}`, + Call: templ.SafeScript(`__templ_copyToClipboard_1463`, url), + CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_1463`, url), + } +} + +func statusClass(g *game.Game, myColor int) string { + switch g.Status { + case game.StatusWaitingForPlayer: + return "alert bg-base-200 text-xl font-bold" + case game.StatusInProgress: + if g.CurrentTurn == myColor { + return "alert alert-success text-xl font-bold" + } + return "alert bg-base-200 text-xl font-bold" + case game.StatusWon: + if g.Winner != nil && g.Winner.Color == myColor { + return "alert alert-success text-xl font-bold" + } + return "alert alert-error text-xl font-bold" + case game.StatusDraw: + return "alert alert-warning text-xl font-bold" + } + return "alert bg-base-200 text-xl font-bold" +} + +func statusMessage(g *game.Game, myColor int) string { + switch g.Status { + case game.StatusWaitingForPlayer: + return "Waiting for opponent..." + case game.StatusInProgress: + if g.CurrentTurn == myColor { + return "Your turn!" + } + return opponentName(g, myColor) + "'s turn" + case game.StatusWon: + if g.Winner != nil && g.Winner.Color == myColor { + return "You win!" + } + if g.Winner != nil { + return g.Winner.Nickname + " wins!" + } + return "Game over" + case game.StatusDraw: + return "It's a draw!" + } + return "" +} + +func opponentName(g *game.Game, myColor int) string { + for _, p := range g.Players { + if p != nil && p.Color != myColor { + return p.Nickname + } + } + return "Opponent" +} + +type playerInfoData struct { + ColorClass string + Label string +} + +func playerInfoPairs(g *game.Game, myColor int) []playerInfoData { + var result []playerInfoData + + var myName, oppName string + var myClass, oppClass string + + for _, p := range g.Players { + if p == nil { + continue + } + colorClass := "yellow" + if p.Color == 1 { + colorClass = "red" + } + if p.Color == myColor { + myName = p.Nickname + myClass = colorClass + } else { + oppName = p.Nickname + oppClass = colorClass + } + } + + if oppName == "" { + oppName = "Waiting..." + } + + result = append(result, playerInfoData{ColorClass: myClass, Label: myName + " (You)"}) + result = append(result, playerInfoData{ColorClass: oppClass, Label: oppName}) + return result +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go new file mode 100644 index 0000000..4df8425 --- /dev/null +++ b/features/c4game/handlers.go @@ -0,0 +1,368 @@ +package c4game + +import ( + "encoding/json" + "net/http" + "strconv" + "sync" + "time" + + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/db" + "github.com/ryanhamamura/c4/features/c4game/components" + "github.com/ryanhamamura/c4/features/c4game/pages" + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + + gi, exists := store.Get(gameID) + if !exists { + http.Redirect(w, r, "/", http.StatusFound) + return + } + + playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + if playerID == "" { + playerID = game.PlayerID(game.GenerateID(8)) + sessions.Put(r.Context(), "player_id", string(playerID)) + } + + userID := sessions.GetString(r.Context(), "user_id") + if userID != "" { + playerID = game.PlayerID(userID) + } + + nickname := sessions.GetString(r.Context(), "nickname") + + // Auto-join if player has a nickname but isn't in the game yet + if nickname != "" && gi.GetPlayerColor(playerID) == 0 { + player := &game.Player{ + ID: playerID, + Nickname: nickname, + } + if userID != "" { + player.UserID = &userID + } + gi.Join(&game.PlayerSession{Player: player}) + } + + myColor := gi.GetPlayerColor(playerID) + + if myColor == 0 { + // Player not in game + isGuest := r.URL.Query().Get("guest") == "1" + if userID == "" && !isGuest { + // Show join prompt (login vs guest) + if err := pages.JoinPage(gameID).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return + } + // Show nickname prompt + if err := pages.NicknamePage(gameID).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return + } + + // Player is in the game — render full game page + g := gi.GetGame() + uiMsgs, _ := chatPersister.LoadChatMessages(gameID) + msgs := uiChatToComponents(uiMsgs) + + if err := pages.GamePage(g, myColor, msgs).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + } +} + +func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + + gi, exists := store.Get(gameID) + if !exists { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + userID := sessions.GetString(r.Context(), "user_id") + if userID != "" { + playerID = game.PlayerID(userID) + } + + myColor := gi.GetPlayerColor(playerID) + + sse := datastar.NewSSE(w, r) + + // Load initial chat messages + uiMsgs, _ := chatPersister.LoadChatMessages(gameID) + var chatMu sync.Mutex + chatMessages := uiChatToComponents(uiMsgs) + + // Send initial render of all components + sendGameComponents(sse, gi, myColor, chatMessages, &chatMu, gameID) + + // Subscribe to game state updates + gameCh := make(chan *nats.Msg, 64) + gameSub, err := nc.ChanSubscribe("game."+gameID, gameCh) + if err != nil { + return + } + defer gameSub.Unsubscribe() + + // Subscribe to chat messages + chatCh := make(chan *nats.Msg, 64) + chatSub, err := nc.ChanSubscribe("game.chat."+gameID, chatCh) + if err != nil { + return + } + defer chatSub.Unsubscribe() + + ctx := r.Context() + for { + select { + case <-ctx.Done(): + return + case <-gameCh: + // Re-read player color in case we just joined + myColor = gi.GetPlayerColor(playerID) + sendGameComponents(sse, gi, myColor, chatMessages, &chatMu, gameID) + case msg := <-chatCh: + var uiMsg game.ChatMessage + if err := json.Unmarshal(msg.Data, &uiMsg); err != nil { + continue + } + cm := components.ChatMessage{ + Nickname: uiMsg.Nickname, + Color: uiMsg.Color, + Message: uiMsg.Message, + Time: uiMsg.Time, + } + chatMu.Lock() + chatMessages = append(chatMessages, cm) + if len(chatMessages) > 50 { + chatMessages = chatMessages[len(chatMessages)-50:] + } + chatMu.Unlock() + + chatMu.Lock() + msgs := make([]components.ChatMessage, len(chatMessages)) + copy(msgs, chatMessages) + chatMu.Unlock() + + if err := sse.PatchElementTempl(components.Chat(msgs, gameID), datastar.WithSelectorID("c4-chat")); err != nil { + return + } + } + } + } +} + +func HandleDropPiece(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + + gi, exists := store.Get(gameID) + if !exists { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + colStr := r.URL.Query().Get("col") + col, err := strconv.Atoi(colStr) + if err != nil { + http.Error(w, "invalid column", http.StatusBadRequest) + return + } + + playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + userID := sessions.GetString(r.Context(), "user_id") + if userID != "" { + playerID = game.PlayerID(userID) + } + + myColor := gi.GetPlayerColor(playerID) + if myColor == 0 { + http.Error(w, "not in game", http.StatusForbidden) + return + } + + gi.DropPiece(col, myColor) + + // The store's notifyFunc publishes to NATS, which triggers SSE updates. + // Return empty SSE response. + datastar.NewSSE(w, r) + } +} + +func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + + gi, exists := store.Get(gameID) + if !exists { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + type ChatSignals struct { + ChatMsg string `json:"chatMsg"` + } + var signals ChatSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if signals.ChatMsg == "" { + datastar.NewSSE(w, r) + return + } + + playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + userID := sessions.GetString(r.Context(), "user_id") + if userID != "" { + playerID = game.PlayerID(userID) + } + + myColor := gi.GetPlayerColor(playerID) + if myColor == 0 { + datastar.NewSSE(w, r) + return + } + + g := gi.GetGame() + nick := "" + for _, p := range g.Players { + if p != nil && p.ID == playerID { + nick = p.Nickname + break + } + } + + cm := game.ChatMessage{ + Nickname: nick, + Color: myColor, + Message: signals.ChatMsg, + Time: time.Now().UnixMilli(), + } + chatPersister.SaveChatMessage(gameID, cm) + + data, err := json.Marshal(cm) + if err != nil { + datastar.NewSSE(w, r) + return + } + nc.Publish("game.chat."+gameID, data) + + // Clear the chat input + sse := datastar.NewSSE(w, r) + sse.MarshalAndPatchSignals(map[string]any{"chatMsg": ""}) //nolint:errcheck + } +} + +func HandleSetNickname(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + + gi, exists := store.Get(gameID) + if !exists { + sse := datastar.NewSSE(w, r) + sse.Redirect("/") //nolint:errcheck + return + } + + type NicknameSignals struct { + Nickname string `json:"nickname"` + } + var signals NicknameSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if signals.Nickname == "" { + datastar.NewSSE(w, r) + return + } + + sessions.Put(r.Context(), "nickname", signals.Nickname) + + playerID := game.PlayerID(sessions.GetString(r.Context(), "player_id")) + userID := sessions.GetString(r.Context(), "user_id") + if userID != "" { + playerID = game.PlayerID(userID) + } + + if gi.GetPlayerColor(playerID) == 0 { + player := &game.Player{ + ID: playerID, + Nickname: signals.Nickname, + } + if userID != "" { + player.UserID = &userID + } + gi.Join(&game.PlayerSession{Player: player}) + } + + sse := datastar.NewSSE(w, r) + sse.Redirect("/game/" + gameID) //nolint:errcheck + } +} + +func HandleRematch(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + + gi, exists := store.Get(gameID) + if !exists { + sse := datastar.NewSSE(w, r) + sse.Redirect("/") //nolint:errcheck + return + } + + newGI := gi.CreateRematch(store) + sse := datastar.NewSSE(w, r) + if newGI != nil { + sse.Redirectf("/game/%s", newGI.ID()) //nolint:errcheck + } + } +} + +// sendGameComponents patches all game-related SSE components. +func sendGameComponents(sse *datastar.ServerSentEventGenerator, gi *game.GameInstance, myColor int, chatMessages []components.ChatMessage, chatMu *sync.Mutex, gameID string) { + g := gi.GetGame() + + sse.PatchElementTempl(components.Board(g, myColor), datastar.WithSelectorID("c4-board")) //nolint:errcheck + sse.PatchElementTempl(components.StatusBanner(g, myColor), datastar.WithSelectorID("c4-status")) //nolint:errcheck + sse.PatchElementTempl(components.PlayerInfo(g, myColor), datastar.WithSelectorID("c4-players")) //nolint:errcheck + + chatMu.Lock() + msgs := make([]components.ChatMessage, len(chatMessages)) + copy(msgs, chatMessages) + chatMu.Unlock() + + sse.PatchElementTempl(components.Chat(msgs, gameID), datastar.WithSelectorID("c4-chat")) //nolint:errcheck +} + +// uiChatToComponents converts ui.C4ChatMessage slice to components.ChatMessage slice. +func uiChatToComponents(uiMsgs []game.ChatMessage) []components.ChatMessage { + msgs := make([]components.ChatMessage, len(uiMsgs)) + for i, m := range uiMsgs { + msgs[i] = components.ChatMessage{ + Nickname: m.Nickname, + Color: m.Color, + Message: m.Message, + Time: m.Time, + } + } + return msgs +} diff --git a/features/c4game/pages/game_templ.go b/features/c4game/pages/game_templ.go new file mode 100644 index 0000000..3662c23 --- /dev/null +++ b/features/c4game/pages/game_templ.go @@ -0,0 +1,219 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package pages + +//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/ryanhamamura/c4/features/c4game/components" + sharedcomponents "github.com/ryanhamamura/c4/features/common/components" + "github.com/ryanhamamura/c4/features/common/layouts" + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +func GamePage(g *game.Game, myColor int, messages []components.ChatMessage) 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = sharedcomponents.BackToLobby().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = sharedcomponents.StealthTitle("text-3xl font-bold").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.PlayerInfo(g, myColor).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.StatusBanner(g, myColor).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 + } + templ_7745c5c3_Err = components.Board(g, myColor).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Chat(messages, g.ID).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 g.Status == game.StatusWaitingForPlayer { + templ_7745c5c3_Err = components.InviteLink(g.ID).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 + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Connect 4").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func JoinPage(gameID 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var5 := 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 = sharedcomponents.GameJoinPrompt( + "/login?return_url=/game/"+gameID, + "/register?return_url=/game/"+gameID, + "/game/"+gameID, + ).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Connect 4 - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func NicknamePage(gameID 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_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var7 := 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 = sharedcomponents.NicknamePrompt("/api/game/"+gameID+"/join").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Connect 4 - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/c4game/routes.go b/features/c4game/routes.go new file mode 100644 index 0000000..99783f6 --- /dev/null +++ b/features/c4game/routes.go @@ -0,0 +1,29 @@ +package c4game + +import ( + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/db" + "github.com/ryanhamamura/c4/game" +) + +func SetupRoutes( + router chi.Router, + store *game.GameStore, + nc *nats.Conn, + sessions *scs.SessionManager, + chatPersister *db.ChatPersister, +) error { + router.Get("/game/{game_id}", HandleGamePage(store, sessions, chatPersister)) + router.Get("/game/{game_id}/events", HandleGameEvents(store, nc, sessions, chatPersister)) + + router.Route("/api/game/{game_id}", func(r chi.Router) { + r.Post("/drop", HandleDropPiece(store, sessions)) + r.Post("/chat", HandleSendChat(store, nc, sessions, chatPersister)) + r.Post("/join", HandleSetNickname(store, sessions)) + r.Post("/rematch", HandleRematch(store, sessions)) + }) + + return nil +} diff --git a/features/common/components/shared_templ.go b/features/common/components/shared_templ.go new file mode 100644 index 0000000..9c74d6c --- /dev/null +++ b/features/common/components/shared_templ.go @@ -0,0 +1,199 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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/starfederation/datastar-go/datastar" + +func BackToLobby() 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, "← Back") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func StealthTitle(class 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_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var3 = []any{class} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...) + 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 + } + return nil + }) +} + +func NicknamePrompt(returnPath 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_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

Join Game

Enter your nickname to join the game.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func GameJoinPrompt(loginURL string, registerURL string, gamePath 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Join Game

Log in to track your game history, or continue as a guest.

Login Continue as Guest

Don't have an account? Register

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/common/layouts/base_templ.go b/features/common/layouts/base_templ.go new file mode 100644 index 0000000..2f5437f --- /dev/null +++ b/features/common/layouts/base_templ.go @@ -0,0 +1,69 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package layouts + +//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/ryanhamamura/c4/config" + +func Base(title 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 + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/common/layouts/base.templ`, Line: 9, Col: 17} + } + _, 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 + } + if config.Global.Environment == config.Dev { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.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 + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/lobby/components/gamelist_templ.go b/features/lobby/components/gamelist_templ.go new file mode 100644 index 0000000..c8539c8 --- /dev/null +++ b/features/lobby/components/gamelist_templ.go @@ -0,0 +1,239 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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" + "time" + + "github.com/ryanhamamura/c4/game" + "github.com/starfederation/datastar-go/datastar" +) + +func GameList(games []GameListItem) 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) + if len(games) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Your Games

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, g := range games { + templ_7745c5c3_Err = gameListEntry(g).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + } + return nil + }) +} + +func gameListEntry(g GameListItem) 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_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(opponentDisplay(g)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 31, Col: 48} + } + _, 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, 5, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 = []any{statusClass(g)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...) + 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 + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(statusText(g)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 32, Col: 50} + } + _, 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, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(formatTimeAgo(g.LastPlayed)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 35, Col: 66} + } + _, 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, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func statusText(g GameListItem) string { + switch game.GameStatus(g.Status) { + case game.StatusWaitingForPlayer: + return "Waiting for opponent" + case game.StatusInProgress: + if g.IsMyTurn { + return "Your turn!" + } + return "Opponent's turn" + } + return "" +} + +func statusClass(g GameListItem) string { + switch game.GameStatus(g.Status) { + case game.StatusWaitingForPlayer: + return "text-sm opacity-60" + case game.StatusInProgress: + if g.IsMyTurn { + return "text-sm text-success font-bold" + } + return "text-sm" + } + return "" +} + +func opponentDisplay(g GameListItem) string { + if g.OpponentName == "" { + return "Waiting for opponent..." + } + return "vs " + g.OpponentName +} + +func formatTimeAgo(t time.Time) string { + if t.IsZero() { + return "" + } + duration := time.Since(t) + + if duration < time.Minute { + return "just now" + } + if duration < time.Hour { + mins := int(duration.Minutes()) + if mins == 1 { + return "1 minute ago" + } + return fmt.Sprintf("%d minutes ago", mins) + } + if duration < 24*time.Hour { + hours := int(duration.Hours()) + if hours == 1 { + return "1 hour ago" + } + return fmt.Sprintf("%d hours ago", hours) + } + days := int(duration.Hours() / 24) + if days == 1 { + return "yesterday" + } + return fmt.Sprintf("%d days ago", days) +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/lobby/components/types.go b/features/lobby/components/types.go new file mode 100644 index 0000000..2608c95 --- /dev/null +++ b/features/lobby/components/types.go @@ -0,0 +1,12 @@ +package components + +import "time" + +// GameListItem represents a connect4 game in the user's active game list. +type GameListItem struct { + ID string + Status int + OpponentName string + IsMyTurn bool + LastPlayed time.Time +} diff --git a/features/lobby/handlers.go b/features/lobby/handlers.go new file mode 100644 index 0000000..4c8a86a --- /dev/null +++ b/features/lobby/handlers.go @@ -0,0 +1,168 @@ +package lobby + +import ( + "context" + "database/sql" + "fmt" + "net/http" + "strconv" + + "github.com/ryanhamamura/c4/db/repository" + lobbycomponents "github.com/ryanhamamura/c4/features/lobby/components" + "github.com/ryanhamamura/c4/features/lobby/pages" + "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/snake" + + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/starfederation/datastar-go/datastar" +) + +// HandleLobbyPage renders the main lobby page with active games for logged-in users. +func HandleLobbyPage(queries *repository.Queries, sessions *scs.SessionManager, snakeStore *snake.SnakeStore) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + userID := sessions.GetString(r.Context(), "user_id") + username := sessions.GetString(r.Context(), "username") + isLoggedIn := userID != "" + + var userGames []lobbycomponents.GameListItem + if isLoggedIn { + ctx := context.Background() + games, err := queries.GetUserActiveGames(ctx, sql.NullString{String: userID, Valid: true}) + if err == nil { + for _, g := range games { + isMyTurn := g.Status == 1 && g.CurrentTurn == g.MyColor + userGames = append(userGames, lobbycomponents.GameListItem{ + ID: g.ID, + Status: int(g.Status), + OpponentName: g.OpponentNickname.String, + IsMyTurn: isMyTurn, + LastPlayed: g.UpdatedAt.Time, + }) + } + } + } + + var activeSnakeGames []pages.SnakeGameListItem + for _, g := range snakeStore.ActiveGames() { + statusLabel := "Waiting" + if g.Status == snake.StatusCountdown { + statusLabel = "Starting soon" + } + activeSnakeGames = append(activeSnakeGames, pages.SnakeGameListItem{ + ID: g.ID, + Width: g.State.Width, + Height: g.State.Height, + PlayerCount: g.PlayerCount(), + StatusLabel: statusLabel, + }) + } + + data := pages.LobbyData{ + IsLoggedIn: isLoggedIn, + Username: username, + UserGames: userGames, + ActiveSnakeGames: activeSnakeGames, + } + + if err := pages.LobbyPage(data).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + } +} + +// HandleCreateGame reads the nickname signal, creates a connect4 game, and redirects via SSE. +func HandleCreateGame(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + type Signals struct { + Nickname string `json:"nickname"` + } + signals := &Signals{} + if err := datastar.ReadSignals(r, signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if signals.Nickname == "" { + return + } + + sessions.Put(r.Context(), "nickname", signals.Nickname) + + gi := store.Create() + sse := datastar.NewSSE(w, r) + sse.ExecuteScript(fmt.Sprintf("window.location.href='/game/%s'", gi.ID())) + } +} + +// HandleDeleteGame deletes a connect4 game and redirects to the lobby. +func HandleDeleteGame(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "id") + if gameID == "" { + http.Error(w, "missing game id", http.StatusBadRequest) + return + } + + store.Delete(gameID) + + sse := datastar.NewSSE(w, r) + sse.ExecuteScript("window.location.href='/'") + } +} + +// HandleCreateSnakeGame reads nickname, grid preset, speed, and mode from the request, +// creates a snake game, and redirects via SSE. +func HandleCreateSnakeGame(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + type Signals struct { + Nickname string `json:"nickname"` + SelectedSpeed int `json:"selectedSpeed"` + } + signals := &Signals{} + if err := datastar.ReadSignals(r, signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if signals.Nickname == "" { + return + } + + sessions.Put(r.Context(), "nickname", signals.Nickname) + + mode := snake.ModeMultiplayer + if r.URL.Query().Get("mode") == "solo" { + mode = snake.ModeSinglePlayer + } + + presetIdx, _ := strconv.Atoi(r.URL.Query().Get("preset")) + if presetIdx < 0 || presetIdx >= len(snake.GridPresets) { + presetIdx = 0 + } + preset := snake.GridPresets[presetIdx] + + speed := snake.DefaultSpeed + if signals.SelectedSpeed >= 0 && signals.SelectedSpeed < len(snake.SpeedPresets) { + speed = snake.SpeedPresets[signals.SelectedSpeed].Speed + } + + si := snakeStore.Create(preset.Width, preset.Height, mode, speed) + + sse := datastar.NewSSE(w, r) + sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) + } +} + +// HandleLogout clears the session and redirects to the lobby. +func HandleLogout(sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := sessions.Destroy(r.Context()); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + sse := datastar.NewSSE(w, r) + sse.ExecuteScript("window.location.href='/'") + } +} diff --git a/features/lobby/pages/lobby_templ.go b/features/lobby/pages/lobby_templ.go new file mode 100644 index 0000000..bc1aeb2 --- /dev/null +++ b/features/lobby/pages/lobby_templ.go @@ -0,0 +1,339 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package pages + +//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/ryanhamamura/c4/features/common/components" + "github.com/ryanhamamura/c4/features/common/layouts" + lobbycomponents "github.com/ryanhamamura/c4/features/lobby/components" + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +func LobbyPage(data LobbyData) 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.IsLoggedIn { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Logged in as ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 22, Col: 47} + } + _, 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 + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
Playing as guest. Login or Register to save your games.
") + 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 = components.StealthTitle("").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Start a new session

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = lobbycomponents.GameList(data.UserGames).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + for i, preset := range snake.SpeedPresets { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Play Solo

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, preset := range snake.GridPresets { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

Create Multiplayer Game

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, preset := range snake.GridPresets { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + 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 + } + if len(data.ActiveSnakeGames) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

Join a Game

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, g := range data.ActiveSnakeGames { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d\u00d7%d \u2014 %d/8 players", g.Width, g.Height, g.PlayerCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 161, Col: 96} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(g.StatusLabel) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 162, Col: 57} + } + _, 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, 29, "") + 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 + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Game Lobby").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/features/lobby/pages/types.go b/features/lobby/pages/types.go new file mode 100644 index 0000000..a386a6f --- /dev/null +++ b/features/lobby/pages/types.go @@ -0,0 +1,20 @@ +package pages + +import "github.com/ryanhamamura/c4/features/lobby/components" + +// SnakeGameListItem represents a joinable snake game in the lobby. +type SnakeGameListItem struct { + ID string + Width int + Height int + PlayerCount int + StatusLabel string +} + +// LobbyData holds all data needed to render the lobby page. +type LobbyData struct { + IsLoggedIn bool + Username string + UserGames []components.GameListItem + ActiveSnakeGames []SnakeGameListItem +} diff --git a/features/lobby/routes.go b/features/lobby/routes.go new file mode 100644 index 0000000..016eb75 --- /dev/null +++ b/features/lobby/routes.go @@ -0,0 +1,29 @@ +package lobby + +import ( + "github.com/ryanhamamura/c4/db/repository" + "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/snake" + + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" +) + +func SetupRoutes( + router chi.Router, + queries *repository.Queries, + sessions *scs.SessionManager, + store *game.GameStore, + snakeStore *snake.SnakeStore, +) error { + router.Get("/", HandleLobbyPage(queries, sessions, snakeStore)) + + router.Route("/api/lobby", func(r chi.Router) { + r.Post("/create-game", HandleCreateGame(store, sessions)) + r.Delete("/game/{id}", HandleDeleteGame(store, sessions)) + r.Post("/create-snake", HandleCreateSnakeGame(snakeStore, sessions)) + r.Post("/logout", HandleLogout(sessions)) + }) + + return nil +} diff --git a/features/snakegame/components/board_templ.go b/features/snakegame/components/board_templ.go new file mode 100644 index 0000000..cbe1237 --- /dev/null +++ b/features/snakegame/components/board_templ.go @@ -0,0 +1,295 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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/ryanhamamura/c4/snake" +) + +func cellSizeForGrid(width, height int) int { + maxDim := width + if height > maxDim { + maxDim = height + } + switch { + case maxDim <= 15: + return 28 + case maxDim <= 20: + return 24 + case maxDim <= 30: + return 20 + case maxDim <= 40: + return 16 + default: + return 14 + } +} + +type cellInfo struct { + snakeIdx int // -1 = empty, -2 = food + isHead bool +} + +func Board(sg *snake.SnakeGame) 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 + } + if sg.State != nil && (sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished) { + templ_7745c5c3_Err = boardCells(sg).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 + } + return nil + }) +} + +func boardCells(sg *snake.SnakeGame) 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) + state := sg.State + grid := buildGrid(state) + cellSize := cellSizeForGrid(state.Width, state.Height) + for y := 0; y < state.Height; y++ { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for x := 0; x < state.Width; x++ { + ci := grid[y][x] + if ci.snakeIdx == -2 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if ci.snakeIdx >= 0 { + s := state.Snakes[ci.snakeIdx] + bg := snakeColor(ci.snakeIdx) + if ci.isHead { + if s.Alive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + if s.Alive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func buildGrid(state *snake.GameState) [][]cellInfo { + grid := make([][]cellInfo, state.Height) + for y := 0; y < state.Height; y++ { + grid[y] = make([]cellInfo, state.Width) + for x := 0; x < state.Width; x++ { + grid[y][x] = cellInfo{snakeIdx: -1} + } + } + for fi := range state.Food { + f := state.Food[fi] + if f.X >= 0 && f.X < state.Width && f.Y >= 0 && f.Y < state.Height { + grid[f.Y][f.X] = cellInfo{snakeIdx: -2} + } + } + for si, s := range state.Snakes { + if s == nil { + continue + } + for bi, bp := range s.Body { + if bp.X >= 0 && bp.X < state.Width && bp.Y >= 0 && bp.Y < state.Height { + grid[bp.Y][bp.X] = cellInfo{snakeIdx: si, isHead: bi == 0} + } + } + } + return grid +} + +func snakeColor(idx int) string { + if idx >= 0 && idx < len(snake.SnakeColors) { + return snake.SnakeColors[idx] + } + return "#666" +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/components/chat_templ.go b/features/snakegame/components/chat_templ.go new file mode 100644 index 0000000..396e9a5 --- /dev/null +++ b/features/snakegame/components/chat_templ.go @@ -0,0 +1,173 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +type ChatMessage struct { + Nickname string `json:"nickname"` + Slot int `json:"slot"` + Message string `json:"message"` + Time int64 `json:"time"` +} + +func Chat(messages []ChatMessage, gameID 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 + } + for _, m := range messages { + 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(m.Nickname + ": ") + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 23, Col: 25} + } + _, 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 + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(m.Message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 25, Col: 22} + } + _, 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, 5, "
") + 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 = chatAutoScroll().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func chatAutoScroll() 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func chatColor(slot int) string { + if slot >= 0 && slot < len(snake.SnakeColors) { + return snake.SnakeColors[slot] + } + return "#666" +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/components/status_templ.go b/features/snakegame/components/status_templ.go new file mode 100644 index 0000000..b1733c0 --- /dev/null +++ b/features/snakegame/components/status_templ.go @@ -0,0 +1,470 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +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" + "math" + "time" + + "github.com/ryanhamamura/c4/config" + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +func StatusBanner(sg *snake.SnakeGame, mySlot int, gameID 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 + } + switch sg.Status { + case snake.StatusWaitingForPlayers: + if sg.Mode == snake.ModeSinglePlayer { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
Ready?
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
Waiting for players...
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + case snake.StatusCountdown: + remaining := time.Until(sg.CountdownEnd) + secs := int(math.Ceil(remaining.Seconds())) + if secs < 0 { + secs = 0 + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Starting in %d...", secs)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 29, Col: 45} + } + _, 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, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case snake.StatusInProgress: + if sg.State != nil && mySlot >= 0 && mySlot < len(sg.State.Snakes) && sg.State.Snakes[mySlot] != nil && !sg.State.Snakes[mySlot].Alive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
You're out!
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if sg.Mode == snake.ModeSinglePlayer { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Score: %d", sg.Score)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 36, Col: 42} + } + _, 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, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
Go!
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + case snake.StatusFinished: + templ_7745c5c3_Err = finishedBanner(sg, mySlot, gameID).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + return nil + }) +} + +func finishedBanner(sg *snake.SnakeGame, mySlot int, gameID 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_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if sg.Mode == snake.ModeSinglePlayer { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Game Over! Score: %d", sg.Score)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 50, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = rematchOrJoin(sg, gameID).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + } else if sg.Winner != nil { + if sg.Winner.Slot == mySlot { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
You win!") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = rematchOrJoin(sg, gameID).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(sg.Winner.Nickname + " wins!") + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 61, Col: 35} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = rematchOrJoin(sg, gameID).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 + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
It's a draw!") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = rematchOrJoin(sg, gameID).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 + } + } + return nil + }) +} + +func rematchOrJoin(sg *snake.SnakeGame, gameID 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + if sg.RematchGameID != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Join Rematch") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func PlayerList(sg *snake.SnakeGame, mySlot 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_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, p := range sg.Players { + if p != nil { + 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(p.Nickname) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 96, Col: 18} + } + _, 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, 26, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if i == mySlot { + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(" (You)") + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 98, Col: 17} + } + _, 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, 27, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { + if sg.State != nil && i < len(sg.State.Snakes) && sg.State.Snakes[i] != nil { + if sg.State.Snakes[i].Alive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("(%d)", len(sg.State.Snakes[i].Body))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 105, Col: 60} + } + _, 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, 29, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "(dead)") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + 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 + }) +} + +func InviteLink(gameID 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_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + fullURL := config.Global.AppURL + "/snake/" + gameID + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

Share this link to invite players:

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fullURL) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 123, Col: 12} + } + _, 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, 34, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(fullURL)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func copyToClipboard(url string) templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_copyToClipboard_1463`, + Function: `function __templ_copyToClipboard_1463(url){navigator.clipboard.writeText(url) +}`, + Call: templ.SafeScript(`__templ_copyToClipboard_1463`, url), + CallInline: templ.SafeScriptInline(`__templ_copyToClipboard_1463`, url), + } +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go new file mode 100644 index 0000000..8fe9675 --- /dev/null +++ b/features/snakegame/handlers.go @@ -0,0 +1,321 @@ +package snakegame + +import ( + "encoding/json" + "net/http" + "strconv" + "sync" + + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/features/snakegame/components" + "github.com/ryanhamamura/c4/features/snakegame/pages" + "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +func getPlayerID(sessions *scs.SessionManager, r *http.Request) snake.PlayerID { + pid := sessions.GetString(r.Context(), "player_id") + if pid == "" { + pid = game.GenerateID(8) + sessions.Put(r.Context(), "player_id", pid) + } + userID := sessions.GetString(r.Context(), "user_id") + if userID != "" { + return snake.PlayerID(userID) + } + return snake.PlayerID(pid) +} + +func HandleSnakePage(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + si, ok := snakeStore.Get(gameID) + if !ok { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + + playerID := getPlayerID(sessions, r) + nickname := sessions.GetString(r.Context(), "nickname") + userID := sessions.GetString(r.Context(), "user_id") + + // Auto-join if nickname exists and not already in game + if nickname != "" && si.GetPlayerSlot(playerID) < 0 { + player := &snake.Player{ + ID: playerID, + Nickname: nickname, + } + if userID != "" { + player.UserID = &userID + } + si.Join(player) + } + + mySlot := si.GetPlayerSlot(playerID) + + if mySlot < 0 { + // Not in game yet + isGuest := r.URL.Query().Get("guest") == "1" + if userID == "" && !isGuest { + if err := pages.JoinPage(gameID).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return + } + if err := pages.NicknamePage(gameID).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + return + } + + sg := si.GetGame() + if err := pages.GamePage(sg, mySlot, nil, gameID).Render(r.Context(), w); err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + } + } +} + +func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + si, ok := snakeStore.Get(gameID) + if !ok { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + playerID := getPlayerID(sessions, r) + mySlot := si.GetPlayerSlot(playerID) + + sse := datastar.NewSSE(w, r) + + // Send initial render + sg := si.GetGame() + sse.PatchElementTempl(components.Board(sg)) //nolint:errcheck + sse.PatchElementTempl(components.StatusBanner(sg, mySlot, gameID)) //nolint:errcheck + sse.PatchElementTempl(components.PlayerList(sg, mySlot)) //nolint:errcheck + if sg.Mode == snake.ModeMultiplayer { + sse.PatchElementTempl(components.Chat(nil, gameID)) //nolint:errcheck + if sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown { + sse.PatchElementTempl(components.InviteLink(gameID)) //nolint:errcheck + } + } + + // Subscribe to game updates via NATS + gameCh := make(chan *nats.Msg, 64) + gameSub, err := nc.ChanSubscribe("snake."+gameID, gameCh) + if err != nil { + return + } + defer gameSub.Unsubscribe() + + // Chat subscription (multiplayer only) + var chatCh chan *nats.Msg + var chatSub *nats.Subscription + var chatMessages []components.ChatMessage + var chatMu sync.Mutex + + if sg.Mode == snake.ModeMultiplayer { + chatCh = make(chan *nats.Msg, 64) + chatSub, err = nc.ChanSubscribe("snake.chat."+gameID, chatCh) + if err != nil { + return + } + defer chatSub.Unsubscribe() + } + + ctx := r.Context() + for { + select { + case <-ctx.Done(): + return + + case <-gameCh: + // Drain backed-up game updates + for { + select { + case <-gameCh: + default: + goto drained + } + } + drained: + si, ok = snakeStore.Get(gameID) + if !ok { + return + } + mySlot = si.GetPlayerSlot(playerID) + sg = si.GetGame() + if err := sse.PatchElementTempl(components.Board(sg)); err != nil { + return + } + if err := sse.PatchElementTempl(components.StatusBanner(sg, mySlot, gameID)); err != nil { + return + } + if err := sse.PatchElementTempl(components.PlayerList(sg, mySlot)); err != nil { + return + } + + case msg := <-chatCh: + if msg == nil { + continue + } + var cm components.ChatMessage + if err := json.Unmarshal(msg.Data, &cm); err != nil { + continue + } + chatMu.Lock() + chatMessages = append(chatMessages, cm) + if len(chatMessages) > 50 { + chatMessages = chatMessages[len(chatMessages)-50:] + } + msgs := make([]components.ChatMessage, len(chatMessages)) + copy(msgs, chatMessages) + chatMu.Unlock() + + if err := sse.PatchElementTempl(components.Chat(msgs, gameID)); err != nil { + return + } + } + } + } +} + +func HandleSetDirection(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + si, ok := snakeStore.Get(gameID) + if !ok { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + playerID := getPlayerID(sessions, r) + slot := si.GetPlayerSlot(playerID) + if slot < 0 { + http.Error(w, "not in game", http.StatusForbidden) + return + } + + dStr := r.URL.Query().Get("d") + d, err := strconv.Atoi(dStr) + if err != nil || d < 0 || d > 3 { + http.Error(w, "invalid direction", http.StatusBadRequest) + return + } + + si.SetDirection(slot, snake.Direction(d)) + w.WriteHeader(http.StatusOK) + } +} + +type chatSignals struct { + ChatMsg string `json:"chatMsg"` +} + +func HandleSendChat(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + si, ok := snakeStore.Get(gameID) + if !ok { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + var signals chatSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if signals.ChatMsg == "" { + return + } + + playerID := getPlayerID(sessions, r) + slot := si.GetPlayerSlot(playerID) + if slot < 0 { + http.Error(w, "not in game", http.StatusForbidden) + return + } + + sg := si.GetGame() + cm := components.ChatMessage{ + Nickname: sg.Players[slot].Nickname, + Slot: slot, + Message: signals.ChatMsg, + } + data, err := json.Marshal(cm) + if err != nil { + return + } + nc.Publish("snake.chat."+gameID, data) //nolint:errcheck + + sse := datastar.NewSSE(w, r) + sse.MarshalAndPatchSignals(map[string]any{"chatMsg": ""}) //nolint:errcheck + } +} + +type nicknameSignals struct { + Nickname string `json:"nickname"` +} + +func HandleSetNickname(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + si, ok := snakeStore.Get(gameID) + if !ok { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + var signals nicknameSignals + if err := datastar.ReadSignals(r, &signals); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if signals.Nickname == "" { + return + } + + sessions.Put(r.Context(), "nickname", signals.Nickname) + + playerID := getPlayerID(sessions, r) + userID := sessions.GetString(r.Context(), "user_id") + + if si.GetPlayerSlot(playerID) < 0 { + player := &snake.Player{ + ID: playerID, + Nickname: signals.Nickname, + } + if userID != "" { + player.UserID = &userID + } + si.Join(player) + } + + sse := datastar.NewSSE(w, r) + sse.Redirect("/snake/" + gameID) //nolint:errcheck + } +} + +func HandleRematch(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + gameID := chi.URLParam(r, "game_id") + si, ok := snakeStore.Get(gameID) + if !ok { + http.Error(w, "game not found", http.StatusNotFound) + return + } + + newSI := si.CreateRematch() + sse := datastar.NewSSE(w, r) + if newSI != nil { + sse.Redirect("/snake/" + newSI.ID()) //nolint:errcheck + } + } +} diff --git a/features/snakegame/pages/game_templ.go b/features/snakegame/pages/game_templ.go new file mode 100644 index 0000000..a7023cb --- /dev/null +++ b/features/snakegame/pages/game_templ.go @@ -0,0 +1,277 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package pages + +//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/ryanhamamura/c4/features/common/components" + "github.com/ryanhamamura/c4/features/common/layouts" + snakecomponents "github.com/ryanhamamura/c4/features/snakegame/components" + "github.com/ryanhamamura/c4/snake" + "github.com/starfederation/datastar-go/datastar" +) + +// keydownScript builds the inline JS for a single data-on:keydown handler +// that dispatches WASD/arrow keys to direction POST endpoints. +func keydownScript(gameID string) string { + return fmt.Sprintf( + "const k=evt.key;"+ + "if(k==='w'||k==='ArrowUp'){evt.preventDefault();%s}"+ + "else if(k==='s'||k==='ArrowDown'){evt.preventDefault();%s}"+ + "else if(k==='a'||k==='ArrowLeft'){evt.preventDefault();%s}"+ + "else if(k==='d'||k==='ArrowRight'){evt.preventDefault();%s}", + datastar.PostSSE("/api/snake/%s/dir?d=0", gameID), + datastar.PostSSE("/api/snake/%s/dir?d=1", gameID), + datastar.PostSSE("/api/snake/%s/dir?d=2", gameID), + datastar.PostSSE("/api/snake/%s/dir?d=3", gameID), + ) +} + +func GamePage(sg *snake.SnakeGame, mySlot int, messages []snakecomponents.ChatMessage, gameID 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_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 = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.BackToLobby().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 + } + templ_7745c5c3_Err = snakecomponents.PlayerList(sg, mySlot).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = snakecomponents.StatusBanner(sg, mySlot, gameID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { + if sg.Mode == snake.ModeMultiplayer { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = snakecomponents.Board(sg).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = snakecomponents.Chat(messages, gameID).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + } else { + templ_7745c5c3_Err = snakecomponents.Board(sg).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else if sg.Mode == snake.ModeMultiplayer { + templ_7745c5c3_Err = snakecomponents.Chat(messages, gameID).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if sg.Mode == snake.ModeMultiplayer && (sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown) { + templ_7745c5c3_Err = snakecomponents.InviteLink(gameID).Render(ctx, templ_7745c5c3_Buffer) + 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 + }) + templ_7745c5c3_Err = layouts.Base("Snake").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func JoinPage(gameID 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_Var6 := templ.GetChildren(ctx) + if templ_7745c5c3_Var6 == nil { + templ_7745c5c3_Var6 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var7 := 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.GameJoinPrompt( + fmt.Sprintf("/login?return=/snake/%s", gameID), + fmt.Sprintf("/register?return=/snake/%s", gameID), + fmt.Sprintf("/snake/%s", gameID), + ).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Snake - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var7), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func NicknamePage(gameID 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_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var9 := 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.NicknamePrompt(fmt.Sprintf("/api/snake/%s/join", gameID)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layouts.Base("Snake - Join").Render(templ.WithChildren(ctx, templ_7745c5c3_Var9), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/features/snakegame/routes.go b/features/snakegame/routes.go new file mode 100644 index 0000000..c4c8334 --- /dev/null +++ b/features/snakegame/routes.go @@ -0,0 +1,22 @@ +package snakegame + +import ( + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/snake" +) + +func SetupRoutes(router chi.Router, snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *scs.SessionManager) error { + router.Get("/snake/{game_id}", HandleSnakePage(snakeStore, sessions)) + router.Get("/snake/{game_id}/events", HandleSnakeEvents(snakeStore, nc, sessions)) + + router.Route("/api/snake/{game_id}", func(r chi.Router) { + r.Post("/dir", HandleSetDirection(snakeStore, sessions)) + r.Post("/chat", HandleSendChat(snakeStore, nc, sessions)) + r.Post("/join", HandleSetNickname(snakeStore, sessions)) + r.Post("/rematch", HandleRematch(snakeStore, sessions)) + }) + + return nil +} diff --git a/game/types.go b/game/types.go index 73cd33e..71f0ae8 100644 --- a/game/types.go +++ b/game/types.go @@ -67,3 +67,11 @@ func (g *Game) WinningCellsFromJSON(data string) error { } return json.Unmarshal([]byte(data), &g.WinningCells) } + +// ChatMessage is the domain type for persisted C4 chat messages. +type ChatMessage struct { + Nickname string `json:"nickname"` + Color int `json:"color"` // 1=Red, 2=Yellow + Message string `json:"message"` + Time int64 `json:"time"` +} diff --git a/go.mod b/go.mod index 5b66895..d7a24c5 100644 --- a/go.mod +++ b/go.mod @@ -3,51 +3,244 @@ module github.com/ryanhamamura/c4 go 1.25.4 require ( + github.com/a-h/templ v0.3.1001 github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de github.com/alexedwards/scs/v2 v2.9.0 + github.com/delaneyj/toolbelt v0.9.1 + github.com/go-chi/chi/v5 v5.2.5 github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 + github.com/nats-io/nats-server/v2 v2.12.2 + github.com/nats-io/nats.go v1.48.0 github.com/pressly/goose/v3 v3.27.0 github.com/rs/zerolog v1.34.0 - github.com/ryanhamamura/via v0.23.0 + github.com/starfederation/datastar-go v1.1.0 golang.org/x/crypto v0.48.0 + golang.org/x/sync v0.19.0 modernc.org/sqlite v1.46.1 ) require ( + cel.dev/expr v0.25.1 // indirect + charm.land/bubbles/v2 v2.0.0-rc.1 // indirect + charm.land/bubbletea/v2 v2.0.0-rc.2 // indirect + charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 // indirect + cloud.google.com/go v0.123.0 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + cloud.google.com/go/iam v1.5.3 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/storage v1.58.0 // indirect + dario.cat/mergo v1.0.2 // indirect + filippo.io/edwards25519 v1.2.0 // indirect github.com/CAFxX/httpcompression v0.0.9 // indirect + github.com/ClickHouse/ch-go v0.71.0 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.43.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect + github.com/Ladicle/tabwriter v1.0.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect + github.com/air-verse/air v1.64.5 // indirect + github.com/alecthomas/chroma/v2 v2.23.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/antithesishq/antithesis-sdk-go v0.5.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.6 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect + github.com/aws/smithy-go v1.24.0 // indirect + github.com/bep/godartsass/v2 v2.5.0 // indirect + github.com/bep/golibsass v1.2.0 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect - github.com/delaneyj/toolbelt v0.9.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chainguard-dev/git-urls v1.0.2 // indirect + github.com/charmbracelet/colorprofile v0.3.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 // indirect + github.com/charmbracelet/x/ansi v0.11.1 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/charmbracelet/x/termios v0.1.1 // indirect + github.com/charmbracelet/x/windows v0.2.2 // indirect + github.com/cli/browser v1.3.0 // indirect + github.com/clipperhouse/displaywidth v0.5.0 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/cubicdaiya/gonp v1.0.4 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/dominikbraun/graph v0.23.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/elastic/go-sysinfo v1.15.4 // indirect + github.com/elastic/go-windows v1.0.2 // indirect + github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-task/task/v3 v3.48.0 // indirect + github.com/go-task/template v0.2.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gohugoio/hugo v0.149.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/cel-go v0.26.1 // indirect github.com/google/go-tpm v0.9.7 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.8.4 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect github.com/hookenz/gotailwind/v4 v4.2.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.8.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/klauspost/compress v1.18.4 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect github.com/mfridman/interpolate v0.0.2 // indirect + github.com/mfridman/xflag v0.1.0 // indirect + github.com/microsoft/go-mssqldb v1.9.6 // indirect github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/natefinch/atomic v1.0.1 // indirect github.com/nats-io/jwt/v2 v2.8.0 // indirect - github.com/nats-io/nats-server/v2 v2.12.2 // indirect - github.com/nats-io/nats.go v1.48.0 // indirect github.com/nats-io/nkeys v0.4.12 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/paulmach/orb v0.12.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pganalyze/pg_query_go/v6 v6.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.25 // indirect + github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect + github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect + github.com/pingcap/log v1.1.0 // indirect + github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/puzpuzpuz/xsync/v4 v4.3.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/riza-io/grpc-go v0.2.0 // indirect + github.com/sajari/fuzzy v1.0.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect - github.com/starfederation/datastar-go v1.0.3 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.9.2 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect + github.com/sqlc-dev/sqlc v1.30.0 // indirect + github.com/stoewer/go-strcase v1.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/tdewolff/parse/v2 v2.8.3 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect + github.com/tursodatabase/libsql-client-go v0.0.0-20251219100830-236aa1ff8acc // indirect + github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 // indirect + github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/vertica/vertica-sql-go v1.3.5 // indirect + github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect + github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/ydb-platform/ydb-go-genproto v0.0.0-20260128080146-c4ed16b24b37 // indirect + github.com/ydb-platform/ydb-go-sdk/v3 v3.127.0 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect + github.com/ziutek/mymysql v1.5.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/sync v0.19.0 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect + golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect - maragu.dev/gomponents v1.2.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/api v0.256.0 // indirect + google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect + google.golang.org/grpc v1.79.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + howett.net/plist v1.0.1 // indirect modernc.org/libc v1.68.0 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect + mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 // indirect + mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b // indirect ) -tool github.com/hookenz/gotailwind/v4 +tool ( + github.com/a-h/templ/cmd/templ + github.com/air-verse/air + github.com/go-task/task/v3/cmd/task + github.com/hookenz/gotailwind/v4 + github.com/pressly/goose/v3/cmd/goose + github.com/sqlc-dev/sqlc/cmd/sqlc +) diff --git a/go.sum b/go.sum index e30bc87..df2ccbd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,82 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +charm.land/bubbles/v2 v2.0.0-rc.1 h1:EiIFVAc3Zi/yY86td+79mPhHR7AqZ1OxF+6ztpOCRaM= +charm.land/bubbles/v2 v2.0.0-rc.1/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4= +charm.land/bubbletea/v2 v2.0.0-rc.2 h1:TdTbUOFzbufDJmSz/3gomL6q+fR6HwfY+P13hXQzD7k= +charm.land/bubbletea/v2 v2.0.0-rc.2/go.mod h1:IXFmnCnMLTWw/KQ9rEatSYqbAPAYi8kA3Yqwa1SFnLk= +charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 h1:059k1h5vvZ4ASinki9nmBguxu9Rq0UDDSa6q8LOUphk= +charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= +cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= +cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= +cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= +cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo= +cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU= +github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/CAFxX/httpcompression v0.0.9 h1:0ue2X8dOLEpxTm8tt+OdHcgA+gbDge0OqFQWGKSqgrg= github.com/CAFxX/httpcompression v0.0.9/go.mod h1:XX8oPZA+4IDcfZ0A71Hz0mZsv/YJOgYygkFhizVPilM= +github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM= +github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw= +github.com/ClickHouse/clickhouse-go/v2 v2.43.0 h1:fUR05TrF1GyvLDa/mAQjkx7KbgwdLRffs2n9O3WobtE= +github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc= +github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg= +github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo= +github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= +github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY= +github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +github.com/air-verse/air v1.64.5 h1:+gs/NgTzYYe+gGPyfHy3XxpJReQWC1pIsiKIg0LgNt4= +github.com/air-verse/air v1.64.5/go.mod h1:OaJZSfZqf7wyjS2oP/CcEVyIt0JmZuPh5x1gdtklmmY= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.23.0 h1:u/Orux1J0eLuZDeQ44froV8smumheieI0EofhbyKhhk= +github.com/alecthomas/chroma/v2 v2.23.0/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= +github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= +github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de h1:c72K9HLu6K442et0j3BUL/9HEYaUJouLkkVANdmqTOo= github.com/alexedwards/scs/sqlite3store v0.0.0-20251002162104-209de6e426de/go.mod h1:Iyk7S76cxGaiEX/mSYmTZzYehp4KfyylcLaV3OnToss= github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90= @@ -7,38 +84,366 @@ github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gv github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antithesishq/antithesis-sdk-go v0.5.0 h1:cudCFF83pDDANcXFzkQPUHHedfnnIbUO3JMr9fqwFJs= github.com/antithesishq/antithesis-sdk-go v0.5.0/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M= +github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= +github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= +github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8= +github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI= +github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A= +github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0 h1:MIWra+MSq53CFaXXAywB2qg9YvVZifkk6vEGl/1Qor0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.95.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= +github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps= +github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU= +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/bep/gitmap v1.9.0 h1:2pyb1ex+cdwF6c4tsrhEgEKfyNfxE34d5K+s2sa9byc= +github.com/bep/gitmap v1.9.0/go.mod h1:Juq6e1qqCRvc1W7nzgadPGI9IGV13ZncEebg5atj4Vo= +github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA= +github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c= +github.com/bep/godartsass/v2 v2.5.0 h1:tKRvwVdyjCIr48qgtLa4gHEdtRkPF8H1OeEhJAEv7xg= +github.com/bep/godartsass/v2 v2.5.0/go.mod h1:rjsi1YSXAl/UbsGL85RLDEjRKdIKUlMQHr6ChUNYOFU= +github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI= +github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= +github.com/bep/goportabletext v0.1.0 h1:8dqym2So1cEqVZiBa4ZnMM1R9l/DnC1h4ONg4J5kujw= +github.com/bep/goportabletext v0.1.0/go.mod h1:6lzSTsSue75bbcyvVc0zqd1CdApuT+xkZQ6Re5DzZFg= +github.com/bep/gowebp v0.4.0 h1:QihuVnvIKbRoeBNQkN0JPMM8ClLmD6V2jMftTFwSK3Q= +github.com/bep/gowebp v0.4.0/go.mod h1:95gtYkAA8iIn1t3HkAPurRCVGV/6NhgaHJ1urz0iIwc= +github.com/bep/helpers v0.6.0 h1:qtqMCK8XPFNM9hp5Ztu9piPjxNNkk8PIyUVjg6v8Bsw= +github.com/bep/helpers v0.6.0/go.mod h1:IOZlgx5PM/R/2wgyCatfsgg5qQ6rNZJNDpWGXqDR044= +github.com/bep/imagemeta v0.12.0 h1:ARf+igs5B7pf079LrqRnwzQ/wEB8Q9v4NSDRZO1/F5k= +github.com/bep/imagemeta v0.12.0/go.mod h1:23AF6O+4fUi9avjiydpKLStUNtJr5hJB4rarG18JpN8= +github.com/bep/lazycache v0.8.0 h1:lE5frnRjxaOFbkPZ1YL6nijzOPPz6zeXasJq8WpG4L8= +github.com/bep/lazycache v0.8.0/go.mod h1:BQ5WZepss7Ko91CGdWz8GQZi/fFnCcyWupv8gyTeKwk= +github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ= +github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0= +github.com/bep/overlayfs v0.10.0 h1:wS3eQ6bRsLX+4AAmwGjvoFSAQoeheamxofFiJ2SthSE= +github.com/bep/overlayfs v0.10.0/go.mod h1:ouu4nu6fFJaL0sPzNICzxYsBeWwrjiTdFZdK4lI3tro= +github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI= +github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ= +github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o= +github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= +github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4= +github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 h1:7Rs87fbKJoIIxsQS8YKJYGYa0tlsDwwb0twQjV1KB+g= +github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38/go.mod h1:6lfcr3MNP+kZR25sF1nQwJFuQnNYBlFy3PGX5rvslXc= +github.com/charmbracelet/x/ansi v0.11.1 h1:iXAC8SyMQDJgtcz9Jnw+HU8WMEctHzoTAETIeA3JXMk= +github.com/charmbracelet/x/ansi v0.11.1/go.mod h1:M49wjzpIujwPceJ+t5w3qh2i87+HRtHohgb5iTyepL0= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= +github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= +github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I= +github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/cubicdaiya/gonp v1.0.4 h1:ky2uIAJh81WiLcGKBVD5R7KsM/36W6IqqTy6Bo6rGws= +github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYYACpOI0I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/delaneyj/toolbelt v0.9.1 h1:QJComn2qoaQ4azl5uRkGpdHSO9e+JtoxDTXCiQHvH8o= github.com/delaneyj/toolbelt v0.9.1/go.mod h1:eNXpPuThjTD4tpRNCBl4JEz9jdg9LpyzNuyG+stnIbs= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= +github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM= +github.com/elastic/go-sysinfo v1.15.4 h1:A3zQcunCxik14MgXu39cXFXcIw2sFXZ0zL886eyiv1Q= +github.com/elastic/go-sysinfo v1.15.4/go.mod h1:ZBVXmqS368dOn/jvijV/zHLfakWTYHBZPk3G244lHrU= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= +github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= +github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= +github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= +github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/evanw/esbuild v0.25.9 h1:aU7GVC4lxJGC1AyaPwySWjSIaNLAdVEEuq3chD0Khxs= +github.com/evanw/esbuild v0.25.9/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-task/task/v3 v3.48.0 h1:HEim5OOpgmob5ONfq7ji3QHUyJdcwqL5ctOT5CPWCzA= +github.com/go-task/task/v3 v3.48.0/go.mod h1:ChDoJV0k919miEJJu1yJ846tg+4Ivv9ZE/1YwQXvIRY= +github.com/go-task/template v0.2.0 h1:xW7ek0o65FUSTbKcSNeg2Vyf/I7wYXFgLUznptvviBE= +github.com/go-task/template v0.2.0/go.mod h1:dbdoUb6qKnHQi1y6o+IdIrs0J4o/SEhSTA6bbzZmdtc= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY= +github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= +github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg= +github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec= +github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs= +github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI= +github.com/gohugoio/hugo v0.149.1 h1:uWOc8Ve4h4e48FyYhBquRoHCJviyxA5yGrFJLT48yio= +github.com/gohugoio/hugo v0.149.1/go.mod h1:HS6BP6e8FGxungP4CHC3zeLDvhBLnTJIjHJZWTZjs7o= +github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0 h1:dco+7YiOryRoPOMXwwaf+kktZSCtlFtreNdiJbETvYE= +github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0/go.mod h1:CRrxQTKeM3imw+UoS4EHKyrqB7Zp6sAJiqHit+aMGTE= +github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1 h1:nUzXfRTszLliZuN0JTKeunXTRaiFX6ksaWP0puLLYAY= +github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1/go.mod h1:Wy8ThAA8p2/w1DY05vEzq6EIeI2mzDjvHsu7ULBVwog= +github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc= +github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4= +github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo= +github.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= +github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f h1:jopqB+UTSdJGEJT8tEqYyE29zN91fi2827oLET8tl7k= github.com/google/brotli/go/cbrotli v0.0.0-20230829110029-ed738e842d2f/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s= +github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= +github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.7 h1:u89J4tUUeDTlH8xxC3CTW7OHZjbjKoHdQ9W7gCUhtxA= github.com/google/go-tpm v0.9.7/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= +github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hairyhenderson/go-codeowners v0.7.0 h1:s0W4wF8bdsBEjTWzwzSlsatSthWtTAF2xLgo4a4RwAo= +github.com/hairyhenderson/go-codeowners v0.7.0/go.mod h1:wUlNgQ3QjqC4z8DnM5nnCYVq/icpqXJyJOukKx5U8/Q= +github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70 h1:0HADrxxqaQkGycO1JoUUA+B4FnIkuo8d2bz/hSaTFFQ= +github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.70/go.mod h1:fm2FdDCzJdtbXF7WKAMvBb5NEPouXPHFbGNYs9ShFns= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.8.4 h1:hGEd2xsuVKgwkMtPVufq73fAmZU/x65PPcqH3cb0D9A= +github.com/hashicorp/go-getter v1.8.4/go.mod h1:x27pPGSg9kzoB147QXI8d/nDvp2IgYGcwuRjpaXE9Yg= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hookenz/gotailwind/v4 v4.2.1 h1:FpZLtAAbHH7wMvyGYT+01vTLFITGMGZGMtEbp7dd2dM= github.com/hookenz/gotailwind/v4 v4.2.1/go.mod h1:IfiJtdp8ExV9HV2XUiVjRBvB3QewVXVKWoAGEcpjfNE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= +github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU= +github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U= +github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE= +github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc= +github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0= +github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -46,13 +451,36 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M= +github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/microsoft/go-mssqldb v1.9.6 h1:1MNQg5UiSsokiPz3++K2KPx4moKrwIqly1wv+RyCKTw= +github.com/microsoft/go-mssqldb v1.9.6/go.mod h1:yYMPDufyoF2vVuVCUGtZARr06DKFIhMrluTcgWlXpr4= github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76 h1:KGuD/pM2JpL9FAYvBrnBBeENKZNh6eNtjqytV6TYjnk= github.com/minio/highwayhash v1.0.4-0.20251030100505-070ab1a87a76/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc= +github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= +github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= +github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g= github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA= github.com/nats-io/nats-server/v2 v2.12.2 h1:4TEQd0Y4zvcW0IsVxjlXnRso1hBkQl3TS0BI+SxgPhE= @@ -65,68 +493,369 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/niklasfasching/go-org v1.9.1 h1:/3s4uTPOF06pImGa2Yvlp24yKXZoTYM+nsIlMzfpg/0= +github.com/niklasfasching/go-org v1.9.1/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8= +github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= +github.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s= +github.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= +github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk= +github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE= +github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4= +github.com/pingcap/log v1.1.0 h1:ELiPxACz7vdo1qAvvaWJg1NrYFoY6gqAh/+Uo6aXdD8= +github.com/pingcap/log v1.1.0/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= +github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 h1:W3rpAI3bubR6VWOcwxDIG0Gz9G5rl5b3SL116T0vBt0= +github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q= +github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= +github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= +github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -github.com/ryanhamamura/via v0.23.0 h1:0e7nytisazcWq7uxs6T27GM3FwzosCMenkxJd+78Lko= -github.com/ryanhamamura/via v0.23.0/go.mod h1:rpJewNVG6tgginZN7Be3qqRuol70+v1sFCKD4UjHsQo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= +github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= +github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= +github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= -github.com/starfederation/datastar-go v1.0.3 h1:DnzgsJ6tDHDM6y5Nxsk0AGW/m8SyKch2vQg3P1xGTcU= -github.com/starfederation/datastar-go v1.0.3/go.mod h1:stm83LQkhZkwa5GzzdPEN6dLuu8FVwxIv0w1DYkbD3w= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= +github.com/sqlc-dev/sqlc v1.30.0 h1:H4HrNwPc0hntxGWzAbhlfplPRN4bQpXFx+CaEMcKz6c= +github.com/sqlc-dev/sqlc v1.30.0/go.mod h1:QnEN+npugyhUg1A+1kkYM3jc2OMOFsNlZ1eh8mdhad0= +github.com/starfederation/datastar-go v1.1.0 h1:UVOYpbNfKPfrEq3MBOa1FRPO/YsxxcIduUxUTJiEQbQ= +github.com/starfederation/datastar-go v1.1.0/go.mod h1:stm83LQkhZkwa5GzzdPEN6dLuu8FVwxIv0w1DYkbD3w= +github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tdewolff/minify/v2 v2.24.2 h1:vnY3nTulEAbCAAlxTxPPDkzG24rsq31SOzp63yT+7mo= +github.com/tdewolff/minify/v2 v2.24.2/go.mod h1:1JrCtoZXaDbqioQZfk3Jdmr0GPJKiU7c1Apmb+7tCeE= +github.com/tdewolff/parse/v2 v2.8.3 h1:5VbvtJ83cfb289A1HzRA9sf02iT8YyUwN84ezjkdY1I= +github.com/tdewolff/parse/v2 v2.8.3/go.mod h1:Hwlni2tiVNKyzR1o6nUs4FOF07URA+JLBLd6dlIXYqo= +github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= +github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tursodatabase/libsql-client-go v0.0.0-20251219100830-236aa1ff8acc h1:lzi/5fg2EfinRlh3v//YyIhnc4tY7BTqazQGwb1ar+0= +github.com/tursodatabase/libsql-client-go v0.0.0-20251219100830-236aa1ff8acc/go.mod h1:08inkKyguB6CGGssc/JzhmQWwBgFQBgjlYFjxjRh7nU= +github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 h1:cq+DjLAjz3ZPwh0+G571O/jMH0c0DzReDPLjQGL2/BA= +github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8/go.mod h1:JNauIV2zopCBv/6o+umxcT3bKe8YUqYJaTZQYSYpKss= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= +github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/gozstd v1.20.1 h1:xPnnnvjmaDDitMFfDxmQ4vpx0+3CdTg2o3lALvXTU/g= github.com/valyala/gozstd v1.20.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= +github.com/vertica/vertica-sql-go v1.3.5 h1:IrfH2WIgzZ45yDHyjVFrXU2LuKNIjF5Nwi90a6cfgUI= +github.com/vertica/vertica-sql-go v1.3.5/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= +github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= +github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20260128080146-c4ed16b24b37 h1:kUXMT/fM/DpDT66WQgRUf3I8VOAWjypkMf52W5PChwA= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20260128080146-c4ed16b24b37/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-sdk/v3 v3.127.0 h1:OfHS9ZkZgCy6y/CJ9N8123DXrgaY2BPxWsQiQ8e3wC8= +github.com/ydb-platform/ydb-go-sdk/v3 v3.127.0/go.mod h1:stS1mQYjbJvwwYaYzKyFY9eMiuVXWWXQA6T+SpOLg9c= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= +github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= +go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go= +go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= +golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9 h1:LvZVVaPE0JSqL+ZWb6ErZfnEOKIqqFWUJE2D0fObSmc= +google.golang.org/genproto v0.0.0-20250922171735-9219d122eba9/go.mod h1:QFOrLhdAe2PsTp3vQY4quuLKTi9j3XG3r6JPPaw7MSc= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -maragu.dev/gomponents v1.2.0 h1:H7/N5htz1GCnhu0HB1GasluWeU2rJZOYztVEyN61iTc= -maragu.dev/gomponents v1.2.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.30.2 h1:4yPaaq9dXYXZ2V8s1UgrC3KIj580l2N4ClrLwnbv2so= @@ -155,3 +884,9 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE= +mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997/go.mod h1:Qy/zdaMDxq9sT72Gi43K3gsV+TtTohyDO3f1cyBVwuo= +mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b h1:PUPnLxbDzRO9kg/03l7TZk7+ywTv7FxmOhDHOtOdOtk= +mvdan.cc/sh/v3 v3.12.1-0.20260124232039-e74afc18e65b/go.mod h1:mencVHx2sy9XZG5wJbCA9nRUOE3zvMtoRXOmXMxH7sc= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/main.go b/main.go index e324692..bb437a1 100644 --- a/main.go +++ b/main.go @@ -2,787 +2,127 @@ package main import ( "context" - "crypto/md5" - "database/sql" "embed" - "encoding/hex" - "encoding/json" - "io/fs" - "sync" + "fmt" + "log/slog" + "net" + "net/http" + "os/signal" + "syscall" "time" - "github.com/ryanhamamura/c4/auth" "github.com/ryanhamamura/c4/config" "github.com/ryanhamamura/c4/db" "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/logging" + appnats "github.com/ryanhamamura/c4/nats" + "github.com/ryanhamamura/c4/router" + "github.com/ryanhamamura/c4/sessions" "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/c4/ui" - "github.com/google/uuid" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "github.com/rs/zerolog/log" - "github.com/ryanhamamura/via" - "github.com/ryanhamamura/via/h" -) - -var ( - store = game.NewGameStore() - snakeStore = snake.NewSnakeStore() - queries *repository.Queries - chatPersister *db.ChatPersister + "golang.org/x/sync/errgroup" ) //go:embed assets var assets embed.FS -func DaisyUIPlugin(v *via.V) { - css, _ := fs.ReadFile(assets, "assets/css/output.css") - sum := md5.Sum(css) - version := hex.EncodeToString(sum[:4]) - v.AppendToHead(h.Link(h.Rel("stylesheet"), h.Href("/assets/css/output.css?v="+version))) +func main() { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + cfg := config.Global + logging.SetupLogger(cfg.Environment, cfg.LogLevel) + + if err := run(ctx); err != nil && err != http.ErrServerClosed { + log.Fatal().Err(err).Msg("server error") + } } -func main() { +func run(ctx context.Context) error { cfg := config.Global - logger := logging.SetupLogger(cfg.Environment, cfg.LogLevel) + addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port) + slog.Info("server starting", "addr", addr) + defer slog.Info("server shutdown complete") + eg, egctx := errgroup.WithContext(ctx) + + // Database cleanupDB, err := db.Init(cfg.DBPath) if err != nil { - log.Fatal().Err(err).Msg("initializing database") + return fmt.Errorf("initializing database: %w", err) } defer cleanupDB() - queries = repository.New(db.DB) - store.SetPersister(db.NewGamePersister(queries)) - snakeStore.SetPersister(db.NewSnakePersister(queries)) - chatPersister = db.NewChatPersister(queries) + queries := repository.New(db.DB) - sessionManager, err := via.NewSQLiteSessionManager(db.DB) + // Sessions + sessionManager, cleanupSessions := sessions.SetupSessionManager(db.DB) + defer cleanupSessions() + + // NATS + nc, cleanupNATS, err := appnats.SetupNATS(egctx) if err != nil { - log.Fatal().Err(err).Msg("creating session manager") + return fmt.Errorf("setting up NATS: %w", err) + } + defer cleanupNATS() + + // Game stores + store := game.NewGameStore() + store.SetPersister(db.NewGamePersister(queries)) + store.SetNotifyFunc(func(gameID string) { + nc.Publish("game."+gameID, nil) //nolint:errcheck // best-effort notification + }) + + snakeStore := snake.NewSnakeStore() + snakeStore.SetPersister(db.NewSnakePersister(queries)) + snakeStore.SetNotifyFunc(func(gameID string) { + nc.Publish("snake."+gameID, nil) //nolint:errcheck // best-effort notification + }) + + chatPersister := db.NewChatPersister(queries) + + // Router + logger := log.Logger + r := chi.NewMux() + r.Use( + logging.RequestLogger(&logger, cfg.Environment), + middleware.Recoverer, + sessionManager.LoadAndSave, + ) + + if err := router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore, chatPersister, assets); err != nil { + return fmt.Errorf("setting up routes: %w", err) } - _ = logger + // HTTP server + srv := &http.Server{ + Addr: addr, + Handler: r, + ReadHeaderTimeout: 10 * time.Second, + BaseContext: func(l net.Listener) context.Context { + return egctx + }, + } - v := via.New() - v.Config(via.Options{ - LogLevel: via.LogLevelDebug, - DocumentTitle: "Game Lobby", - ServerAddress: ":" + cfg.Port, - SessionManager: sessionManager, - Plugins: []via.Plugin{DaisyUIPlugin}, + eg.Go(func() error { + err := srv.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return fmt.Errorf("server error: %w", err) + } + return nil }) - subFS, _ := fs.Sub(assets, "assets") - v.StaticFS("/assets/", subFS) - - store.SetNotifyFunc(func(gameID string) { - v.PubSub().Publish("game."+gameID, nil) - }) - snakeStore.SetNotifyFunc(func(gameID string) { - v.PubSub().Publish("snake."+gameID, nil) + eg.Go(func() error { + <-egctx.Done() + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + slog.Debug("shutting down server...") + return srv.Shutdown(shutdownCtx) }) - // Home page - tabbed lobby - v.Page("/", func(c *via.Context) { - userID := c.Session().GetString("user_id") - username := c.Session().GetString("username") - isLoggedIn := userID != "" - - var userGames []ui.GameListItem - if isLoggedIn { - ctx := context.Background() - games, err := queries.GetUserActiveGames(ctx, sql.NullString{String: userID, Valid: true}) - if err == nil { - for _, g := range games { - isMyTurn := g.Status == 1 && g.CurrentTurn == g.MyColor - userGames = append(userGames, ui.GameListItem{ - ID: g.ID, - Status: int(g.Status), - OpponentName: g.OpponentNickname.String, - IsMyTurn: isMyTurn, - LastPlayed: g.UpdatedAt.Time, - }) - } - } - } - - nickname := c.Signal("") - if isLoggedIn { - nickname = c.Signal(username) - } - activeTab := c.Signal("connect4") - - logout := c.Action(func() { - c.Session().Clear() - c.Redirect("/") - }) - - createGame := c.Action(func() { - name := nickname.String() - if name == "" { - return - } - c.Session().Set("nickname", name) - - gi := store.Create() - c.Redirectf("/game/%s", gi.ID()) - }) - - deleteGame := func(id string) h.H { - return c.Action(func() { - for _, g := range userGames { - if g.ID == id { - store.Delete(id) - break - } - } - c.Redirect("/") - }).OnClick() - } - - tabClickConnect4 := c.Action(func() { - activeTab.SetValue("connect4") - c.Sync() - }) - - tabClickSnake := c.Action(func() { - activeTab.SetValue("snake") - c.Sync() - }) - - snakeNickname := c.Signal("") - if isLoggedIn { - snakeNickname = c.Signal(username) - } - - // Speed selection signal (index into SpeedPresets, default to Normal which is index 1) - selectedSpeedIndex := c.Signal(1) - - // Speed selector actions - var speedSelectClicks []h.H - for i := range snake.SpeedPresets { - idx := i - speedSelectClicks = append(speedSelectClicks, c.Action(func() { - selectedSpeedIndex.SetValue(idx) - c.Sync() - }).OnClick()) - } - - // Snake create game actions — one per preset for solo and multiplayer - var snakeSoloClicks []h.H - var snakeMultiClicks []h.H - for _, preset := range snake.GridPresets { - w, ht := preset.Width, preset.Height - snakeSoloClicks = append(snakeSoloClicks, c.Action(func() { - name := snakeNickname.String() - if name == "" { - return - } - c.Session().Set("nickname", name) - speedIdx := selectedSpeedIndex.Int() - speed := snake.DefaultSpeed - if speedIdx >= 0 && speedIdx < len(snake.SpeedPresets) { - speed = snake.SpeedPresets[speedIdx].Speed - } - si := snakeStore.Create(w, ht, snake.ModeSinglePlayer, speed) - c.Redirectf("/snake/%s", si.ID()) - }).OnClick()) - snakeMultiClicks = append(snakeMultiClicks, c.Action(func() { - name := snakeNickname.String() - if name == "" { - return - } - c.Session().Set("nickname", name) - speedIdx := selectedSpeedIndex.Int() - speed := snake.DefaultSpeed - if speedIdx >= 0 && speedIdx < len(snake.SpeedPresets) { - speed = snake.SpeedPresets[speedIdx].Speed - } - si := snakeStore.Create(w, ht, snake.ModeMultiplayer, speed) - c.Redirectf("/snake/%s", si.ID()) - }).OnClick()) - } - - c.View(func() h.H { - return ui.LobbyView(ui.LobbyProps{ - NicknameBind: nickname.Bind(), - CreateGameKeyDown: createGame.OnKeyDown("Enter"), - CreateGameClick: createGame.OnClick(), - IsLoggedIn: isLoggedIn, - Username: username, - LogoutClick: logout.OnClick(), - UserGames: userGames, - DeleteGameClick: deleteGame, - ActiveTab: activeTab.String(), - TabClickConnect4: tabClickConnect4.OnClick(), - TabClickSnake: tabClickSnake.OnClick(), - SnakeNicknameBind: snakeNickname.Bind(), - SnakeSoloClicks: snakeSoloClicks, - SnakeMultiClicks: snakeMultiClicks, - ActiveSnakeGames: snakeStore.ActiveGames(), - SelectedSpeedIndex: selectedSpeedIndex.Int(), - SpeedSelectClicks: speedSelectClicks, - }) - }) - }) - - // Login page - v.Page("/login", func(c *via.Context) { - username := c.Signal("") - password := c.Signal("") - errorMsg := c.Signal("") - - login := c.Action(func() { - ctx := context.Background() - user, err := queries.GetUserByUsername(ctx, username.String()) - if err == sql.ErrNoRows { - errorMsg.SetValue("Invalid username or password") - c.Sync() - return - } - if err != nil { - errorMsg.SetValue("An error occurred") - c.Sync() - return - } - if !auth.CheckPassword(password.String(), user.PasswordHash) { - errorMsg.SetValue("Invalid username or password") - c.Sync() - return - } - - c.Session().RenewToken() - c.Session().Set("user_id", user.ID) - c.Session().Set("username", user.Username) - c.Session().Set("nickname", user.Username) - - returnURL := c.Session().GetString("return_url") - if returnURL != "" { - c.Session().Set("return_url", "") - c.Redirect(returnURL) - } else { - c.Redirect("/") - } - }) - - c.View(func() h.H { - return ui.LoginView( - username.Bind(), - password.Bind(), - login.OnKeyDown("Enter"), - login.OnClick(), - errorMsg.String(), - ) - }) - }) - - // Register page - v.Page("/register", func(c *via.Context) { - username := c.Signal("") - password := c.Signal("") - confirm := c.Signal("") - errorMsg := c.Signal("") - - register := c.Action(func() { - if err := auth.ValidateUsername(username.String()); err != nil { - errorMsg.SetValue(err.Error()) - c.Sync() - return - } - if err := auth.ValidatePassword(password.String()); err != nil { - errorMsg.SetValue(err.Error()) - c.Sync() - return - } - if password.String() != confirm.String() { - errorMsg.SetValue("Passwords do not match") - c.Sync() - return - } - - hash, err := auth.HashPassword(password.String()) - if err != nil { - errorMsg.SetValue("An error occurred") - c.Sync() - return - } - - ctx := context.Background() - id := uuid.New().String() - user, err := queries.CreateUser(ctx, repository.CreateUserParams{ - ID: id, - Username: username.String(), - PasswordHash: hash, - }) - if err != nil { - errorMsg.SetValue("Username already taken") - c.Sync() - return - } - - c.Session().RenewToken() - c.Session().Set("user_id", user.ID) - c.Session().Set("username", user.Username) - c.Session().Set("nickname", user.Username) - - returnURL := c.Session().GetString("return_url") - if returnURL != "" { - c.Session().Set("return_url", "") - c.Redirect(returnURL) - } else { - c.Redirect("/") - } - }) - - c.View(func() h.H { - return ui.RegisterView( - username.Bind(), - password.Bind(), - confirm.Bind(), - register.OnKeyDown("Enter"), - register.OnClick(), - errorMsg.String(), - ) - }) - }) - - // Connect 4 game page - v.Page("/game/{game_id}", func(c *via.Context) { - gameID := c.GetPathParam("game_id") - sessionNickname := c.Session().GetString("nickname") - sessionUserID := c.Session().GetString("user_id") - - nickname := c.Signal(sessionNickname) - colSignal := c.Signal(0) - showGuestPrompt := c.Signal(false) - chatMsg := c.Signal("") - chatMessages, _ := chatPersister.LoadChatMessages(gameID) - var chatMu sync.Mutex - - goToLogin := c.Action(func() { - c.Session().Set("return_url", "/game/"+gameID) - c.Redirect("/login") - }) - - goToRegister := c.Action(func() { - c.Session().Set("return_url", "/game/"+gameID) - c.Redirect("/register") - }) - - continueAsGuest := c.Action(func() { - showGuestPrompt.SetValue(true) - c.Sync() - }) - - var gi *game.GameInstance - var gameExists bool - - if gameID != "" { - gi, gameExists = store.Get(gameID) - } - - playerID := game.PlayerID(c.Session().GetString("player_id")) - if playerID == "" { - playerID = game.PlayerID(game.GenerateID(8)) - c.Session().Set("player_id", string(playerID)) - } - - if sessionUserID != "" { - playerID = game.PlayerID(sessionUserID) - } - - setNickname := c.Action(func() { - if gi == nil { - return - } - name := nickname.String() - if name == "" { - return - } - c.Session().Set("nickname", name) - - if gi.GetPlayerColor(playerID) == 0 { - player := &game.Player{ - ID: playerID, - Nickname: name, - } - if sessionUserID != "" { - player.UserID = &sessionUserID - } - gi.Join(&game.PlayerSession{ - Player: player, - }) - } - c.Sync() - }) - - dropPiece := c.Action(func() { - if gi == nil { - return - } - myColor := gi.GetPlayerColor(playerID) - if myColor == 0 { - return - } - col := colSignal.Int() - gi.DropPiece(col, myColor) - c.Sync() - }) - - createRematch := c.Action(func() { - if gi == nil { - return - } - newGI := gi.CreateRematch(store) - if newGI != nil { - c.Redirectf("/game/%s", newGI.ID()) - } - }) - - sendChat := c.Action(func() { - msg := chatMsg.String() - if msg == "" || gi == nil { - return - } - color := gi.GetPlayerColor(playerID) - if color == 0 { - return - } - g := gi.GetGame() - nick := "" - for _, p := range g.Players { - if p != nil && p.ID == playerID { - nick = p.Nickname - break - } - } - cm := ui.C4ChatMessage{ - Nickname: nick, - Color: color, - Message: msg, - Time: time.Now().UnixMilli(), - } - chatPersister.SaveChatMessage(gameID, cm) - data, err := json.Marshal(cm) - if err != nil { - return - } - c.Publish("game.chat."+gameID, data) - chatMsg.SetValue("") - }) - - if gameExists { - c.Subscribe("game."+gameID, func(data []byte) { c.Sync() }) - - c.Subscribe("game.chat."+gameID, func(data []byte) { - var cm ui.C4ChatMessage - if err := json.Unmarshal(data, &cm); err != nil { - return - } - chatMu.Lock() - chatMessages = append(chatMessages, cm) - if len(chatMessages) > 50 { - chatMessages = chatMessages[len(chatMessages)-50:] - } - chatMu.Unlock() - c.Sync() - }) - } - - if gameExists && sessionNickname != "" && gi.GetPlayerColor(playerID) == 0 { - player := &game.Player{ - ID: playerID, - Nickname: sessionNickname, - } - if sessionUserID != "" { - player.UserID = &sessionUserID - } - gi.Join(&game.PlayerSession{ - Player: player, - }) - } - - c.View(func() h.H { - if !gameExists { - c.Redirect("/") - return h.Div() - } - - myColor := gi.GetPlayerColor(playerID) - - if myColor == 0 { - if sessionUserID == "" && !showGuestPrompt.Bool() { - return ui.GameJoinPrompt( - goToLogin.OnClick(), - continueAsGuest.OnClick(), - goToRegister.OnClick(), - ) - } - return ui.NicknamePrompt( - nickname.Bind(), - setNickname.OnKeyDown("Enter"), - setNickname.OnClick(), - ) - } - - g := gi.GetGame() - - columnClick := func(col int) h.H { - return dropPiece.OnClick(via.WithSignalInt(colSignal, col)) - } - - chatMu.Lock() - msgs := make([]ui.C4ChatMessage, len(chatMessages)) - copy(msgs, chatMessages) - chatMu.Unlock() - chat := ui.C4Chat(msgs, chatMsg.Bind(), sendChat.OnClick(), sendChat.OnKeyDown("Enter")) - - var content []h.H - content = append(content, - ui.BackToLobby(), - ui.StealthTitle("text-3xl font-bold"), - ui.PlayerInfo(g, myColor), - ui.StatusBanner(g, myColor, createRematch.OnClick()), - h.Div(h.Class("c4-game-area"), ui.BoardComponent(g, columnClick, myColor), chat), - ) - - if g.Status == game.StatusWaitingForPlayer { - content = append(content, ui.InviteLink(g.ID)) - } - - mainAttrs := []h.H{h.Class("flex flex-col items-center gap-4 p-4")} - mainAttrs = append(mainAttrs, content...) - return h.Main(mainAttrs...) - }) - }) - - // Snake game page - v.Page("/snake/{game_id}", func(c *via.Context) { - gameID := c.GetPathParam("game_id") - sessionNickname := c.Session().GetString("nickname") - sessionUserID := c.Session().GetString("user_id") - - nickname := c.Signal(sessionNickname) - showGuestPrompt := c.Signal(false) - - goToLogin := c.Action(func() { - c.Session().Set("return_url", "/snake/"+gameID) - c.Redirect("/login") - }) - - goToRegister := c.Action(func() { - c.Session().Set("return_url", "/snake/"+gameID) - c.Redirect("/register") - }) - - continueAsGuest := c.Action(func() { - showGuestPrompt.SetValue(true) - c.Sync() - }) - - var si *snake.SnakeGameInstance - var gameExists bool - - if gameID != "" { - si, gameExists = snakeStore.Get(gameID) - } - - playerID := snake.PlayerID(c.Session().GetString("player_id")) - if playerID == "" { - pid := game.GenerateID(8) - playerID = snake.PlayerID(pid) - c.Session().Set("player_id", pid) - } - if sessionUserID != "" { - playerID = snake.PlayerID(sessionUserID) - } - - setNickname := c.Action(func() { - if si == nil { - return - } - name := nickname.String() - if name == "" { - return - } - c.Session().Set("nickname", name) - - if si.GetPlayerSlot(playerID) < 0 { - player := &snake.Player{ - ID: playerID, - Nickname: name, - } - if sessionUserID != "" { - player.UserID = &sessionUserID - } - si.Join(player) - } - c.Sync() - }) - - // Direction input: single action with a direction signal - dirSignal := c.Signal(-1) - handleDir := c.Action(func() { - if si == nil { - return - } - slot := si.GetPlayerSlot(playerID) - if slot < 0 { - return - } - dir := snake.Direction(dirSignal.Int()) - si.SetDirection(slot, dir) - }) - - createRematch := c.Action(func() { - if si == nil { - return - } - newSI := si.CreateRematch() - if newSI != nil { - c.Redirectf("/snake/%s", newSI.ID()) - } - }) - - chatMsg := c.Signal("") - var chatMessages []ui.ChatMessage - var chatMu sync.Mutex - - sendChat := c.Action(func() { - msg := chatMsg.String() - if msg == "" || si == nil { - return - } - slot := si.GetPlayerSlot(playerID) - if slot < 0 { - return - } - cm := ui.ChatMessage{ - Nickname: si.GetGame().Players[slot].Nickname, - Slot: slot, - Message: msg, - Time: time.Now().UnixMilli(), - } - data, err := json.Marshal(cm) - if err != nil { - return - } - c.Publish("snake.chat."+gameID, data) - chatMsg.SetValue("") - }) - - if gameExists { - c.Subscribe("snake."+gameID, func(data []byte) { c.Sync() }) - - if si.GetGame().Mode == snake.ModeMultiplayer { - c.Subscribe("snake.chat."+gameID, func(data []byte) { - var cm ui.ChatMessage - if err := json.Unmarshal(data, &cm); err != nil { - return - } - chatMu.Lock() - chatMessages = append(chatMessages, cm) - if len(chatMessages) > 50 { - chatMessages = chatMessages[len(chatMessages)-50:] - } - chatMu.Unlock() - c.Sync() - }) - } - } - - // Auto-join if nickname exists - if gameExists && sessionNickname != "" && si.GetPlayerSlot(playerID) < 0 { - player := &snake.Player{ - ID: playerID, - Nickname: sessionNickname, - } - if sessionUserID != "" { - player.UserID = &sessionUserID - } - si.Join(player) - } - - c.View(func() h.H { - if !gameExists { - c.Redirect("/") - return h.Div() - } - - mySlot := si.GetPlayerSlot(playerID) - - if mySlot < 0 { - if sessionUserID == "" && !showGuestPrompt.Bool() { - return ui.GameJoinPrompt( - goToLogin.OnClick(), - continueAsGuest.OnClick(), - goToRegister.OnClick(), - ) - } - return ui.NicknamePrompt( - nickname.Bind(), - setNickname.OnKeyDown("Enter"), - setNickname.OnClick(), - ) - } - - sg := si.GetGame() - - var content []h.H - content = append(content, - ui.BackToLobby(), - h.H1(h.Class("text-3xl font-bold"), h.Text("~~~~")), - ui.SnakePlayerList(sg, mySlot), - ui.SnakeStatusBanner(sg, mySlot, createRematch.OnClick()), - ) - - if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { - board := ui.SnakeBoard(sg) - - if sg.Mode == snake.ModeMultiplayer { - chatMu.Lock() - msgs := make([]ui.ChatMessage, len(chatMessages)) - copy(msgs, chatMessages) - chatMu.Unlock() - chat := ui.SnakeChat(msgs, chatMsg.Bind(), sendChat.OnClick(), sendChat.OnKeyDown("Enter")) - content = append(content, h.Div(h.Class("snake-game-area"), board, chat)) - } else { - content = append(content, board) - } - } else if sg.Mode == snake.ModeMultiplayer { - // Show chat even before game starts (waiting/countdown) - chatMu.Lock() - msgs := make([]ui.ChatMessage, len(chatMessages)) - copy(msgs, chatMessages) - chatMu.Unlock() - content = append(content, ui.SnakeChat(msgs, chatMsg.Bind(), sendChat.OnClick(), sendChat.OnKeyDown("Enter"))) - } - - // Only show invite link for multiplayer games - if sg.Mode == snake.ModeMultiplayer && (sg.Status == snake.StatusWaitingForPlayers || sg.Status == snake.StatusCountdown) { - content = append(content, ui.SnakeInviteLink(sg.ID)) - } - - wrapperAttrs := []h.H{ - h.Class("snake-wrapper flex flex-col items-center gap-4 p-4"), - via.OnKeyDownMap( - via.KeyBind("w", handleDir, via.WithSignalInt(dirSignal, int(snake.DirUp)), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("a", handleDir, via.WithSignalInt(dirSignal, int(snake.DirLeft)), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("s", handleDir, via.WithSignalInt(dirSignal, int(snake.DirDown)), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("d", handleDir, via.WithSignalInt(dirSignal, int(snake.DirRight)), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("ArrowUp", handleDir, via.WithSignalInt(dirSignal, int(snake.DirUp)), via.WithPreventDefault(), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("ArrowLeft", handleDir, via.WithSignalInt(dirSignal, int(snake.DirLeft)), via.WithPreventDefault(), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("ArrowDown", handleDir, via.WithSignalInt(dirSignal, int(snake.DirDown)), via.WithPreventDefault(), via.WithThrottle(100*time.Millisecond)), - via.KeyBind("ArrowRight", handleDir, via.WithSignalInt(dirSignal, int(snake.DirRight)), via.WithPreventDefault(), via.WithThrottle(100*time.Millisecond)), - ), - } - - wrapperAttrs = append(wrapperAttrs, content...) - return h.Main(wrapperAttrs...) - }) - }) - - v.Start() + return eg.Wait() } diff --git a/nats/nats.go b/nats/nats.go new file mode 100644 index 0000000..9eeabc9 --- /dev/null +++ b/nats/nats.go @@ -0,0 +1,69 @@ +// Package nats sets up an embedded NATS server for real-time pub/sub +// messaging between game clients. +package nats + +import ( + "context" + "fmt" + "log/slog" + "net" + "os" + "strconv" + + "github.com/delaneyj/toolbelt" + "github.com/delaneyj/toolbelt/embeddednats" + natsserver "github.com/nats-io/nats-server/v2/server" + "github.com/nats-io/nats.go" +) + +func SetupNATS(ctx context.Context) (*nats.Conn, func(), error) { + natsPort, err := getFreeNatsPort() + if err != nil { + return nil, nil, fmt.Errorf("obtaining NATS port: %w", err) + } + + ns, err := embeddednats.New(ctx, embeddednats.WithNATSServerOptions(&natsserver.Options{ + NoSigs: true, + Port: natsPort, + })) + if err != nil { + return nil, nil, fmt.Errorf("creating embedded nats server: %w", err) + } + + ns.WaitForServer() + slog.Info("NATS started", "port", natsPort) + + nc, err := ns.Client() + if err != nil { + return nil, nil, fmt.Errorf("creating nats client: %w", err) + } + + cleanup := func() { + nc.Close() + ns.Close() + } + + return nc, cleanup, nil +} + +func getFreeNatsPort() (int, error) { + if p, ok := os.LookupEnv("NATS_PORT"); ok { + natsPort, err := strconv.Atoi(p) + if err != nil { + return 0, fmt.Errorf("parsing NATS_PORT: %w", err) + } + if isPortFree(natsPort) { + return natsPort, nil + } + } + return toolbelt.FreePort() +} + +func isPortFree(port int) bool { + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return false + } + ln.Close() //nolint:errcheck // checking port availability + return true +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..a42a09d --- /dev/null +++ b/router/router.go @@ -0,0 +1,76 @@ +// Package router wires feature routes and middleware into the central chi mux. +package router + +import ( + "embed" + "io/fs" + "net/http" + "sync" + + "github.com/ryanhamamura/c4/config" + "github.com/ryanhamamura/c4/db" + "github.com/ryanhamamura/c4/db/repository" + "github.com/ryanhamamura/c4/features/auth" + "github.com/ryanhamamura/c4/features/c4game" + "github.com/ryanhamamura/c4/features/lobby" + "github.com/ryanhamamura/c4/features/snakegame" + "github.com/ryanhamamura/c4/game" + "github.com/ryanhamamura/c4/snake" + + "github.com/alexedwards/scs/v2" + "github.com/go-chi/chi/v5" + "github.com/nats-io/nats.go" + "github.com/starfederation/datastar-go/datastar" +) + +func SetupRoutes( + router chi.Router, + queries *repository.Queries, + sessions *scs.SessionManager, + nc *nats.Conn, + store *game.GameStore, + snakeStore *snake.SnakeStore, + chatPersister *db.ChatPersister, + assets embed.FS, +) error { + // Static assets + subFS, _ := fs.Sub(assets, "assets") + router.Handle("/assets/*", http.StripPrefix("/assets/", http.FileServerFS(subFS))) + + // Hot-reload for development + if config.Global.Environment == config.Dev { + setupReload(router) + } + + auth.SetupRoutes(router, queries, sessions) + lobby.SetupRoutes(router, queries, sessions, store, snakeStore) + c4game.SetupRoutes(router, store, nc, sessions, chatPersister) + snakegame.SetupRoutes(router, snakeStore, nc, sessions) + + return nil +} + +func setupReload(router chi.Router) { + reloadChan := make(chan struct{}, 1) + var hotReloadOnce sync.Once + + router.Get("/reload", func(w http.ResponseWriter, r *http.Request) { + sse := datastar.NewSSE(w, r) + reload := func() { sse.ExecuteScript("window.location.reload()") } //nolint:errcheck // dev-only + hotReloadOnce.Do(reload) + select { + case <-reloadChan: + reload() + case <-r.Context().Done(): + } + }) + + router.Get("/hotreload", func(w http.ResponseWriter, r *http.Request) { + select { + case reloadChan <- struct{}{}: + default: + } + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) //nolint:errcheck // dev-only + }) +} diff --git a/sessions/sessions.go b/sessions/sessions.go new file mode 100644 index 0000000..489a942 --- /dev/null +++ b/sessions/sessions.go @@ -0,0 +1,31 @@ +// Package sessions configures the SCS session manager backed by SQLite. +package sessions + +import ( + "database/sql" + "log/slog" + "net/http" + "time" + + "github.com/alexedwards/scs/sqlite3store" + "github.com/alexedwards/scs/v2" +) + +// SetupSessionManager creates a configured session manager backed by SQLite. +// Returns the manager and a cleanup function the caller should defer. +func SetupSessionManager(db *sql.DB) (*scs.SessionManager, func()) { + store := sqlite3store.New(db) + cleanup := func() { store.StopCleanup() } + + sessionManager := scs.New() + sessionManager.Store = store + sessionManager.Lifetime = 30 * 24 * time.Hour + sessionManager.Cookie.Path = "/" + sessionManager.Cookie.HttpOnly = true + sessionManager.Cookie.Secure = false + sessionManager.Cookie.SameSite = http.SameSiteLaxMode + + slog.Info("session manager configured") + + return sessionManager, cleanup +} diff --git a/ui/auth.go b/ui/auth.go deleted file mode 100644 index a3bfa24..0000000 --- a/ui/auth.go +++ /dev/null @@ -1,130 +0,0 @@ -package ui - -import ( - "github.com/ryanhamamura/via/h" -) - -func LoginView(usernameBind, passwordBind, loginKeyDown, loginClick h.H, errorMsg string) h.H { - var errorEl h.H - if errorMsg != "" { - errorEl = h.P(h.Class("alert alert-error mb-4"), h.Text(errorMsg)) - } - - return h.Main(h.Class("max-w-sm mx-auto mt-8 text-center"), - h.H1(h.Class("text-3xl font-bold"), h.Text("Login")), - h.P(h.Class("mb-4"), h.Text("Sign in to your account")), - errorEl, - h.Form( - h.FieldSet(h.Class("fieldset"), - h.Label(h.Class("label"), h.Text("Username"), h.Attr("for", "username")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("username"), - h.Type("text"), - h.Placeholder("Enter your username"), - usernameBind, - h.Attr("required"), - h.Attr("autofocus"), - ), - h.Label(h.Class("label"), h.Text("Password"), h.Attr("for", "password")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("password"), - h.Type("password"), - h.Placeholder("Enter your password"), - passwordBind, - h.Attr("required"), - loginKeyDown, - ), - ), - h.Button( - h.Class("btn btn-primary w-full"), - h.Type("button"), - h.Text("Login"), - loginClick, - ), - ), - h.P( - h.Text("Don't have an account? "), - h.A(h.Class("link"), h.Href("/register"), h.Text("Register")), - ), - ) -} - -func RegisterView(usernameBind, passwordBind, confirmBind, registerKeyDown, registerClick h.H, errorMsg string) h.H { - var errorEl h.H - if errorMsg != "" { - errorEl = h.P(h.Class("alert alert-error mb-4"), h.Text(errorMsg)) - } - - return h.Main(h.Class("max-w-sm mx-auto mt-8 text-center"), - h.H1(h.Class("text-3xl font-bold"), h.Text("Register")), - h.P(h.Class("mb-4"), h.Text("Create a new account")), - errorEl, - h.Form( - h.FieldSet(h.Class("fieldset"), - h.Label(h.Class("label"), h.Text("Username"), h.Attr("for", "username")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("username"), - h.Type("text"), - h.Placeholder("Choose a username"), - usernameBind, - h.Attr("required"), - h.Attr("autofocus"), - ), - h.Label(h.Class("label"), h.Text("Password"), h.Attr("for", "password")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("password"), - h.Type("password"), - h.Placeholder("Choose a password (min 8 chars)"), - passwordBind, - h.Attr("required"), - ), - h.Label(h.Class("label"), h.Text("Confirm Password"), h.Attr("for", "confirm")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("confirm"), - h.Type("password"), - h.Placeholder("Confirm your password"), - confirmBind, - h.Attr("required"), - registerKeyDown, - ), - ), - h.Button( - h.Class("btn btn-primary w-full"), - h.Type("button"), - h.Text("Register"), - registerClick, - ), - ), - h.P( - h.Text("Already have an account? "), - h.A(h.Class("link"), h.Href("/login"), h.Text("Login")), - ), - ) -} - -func AuthHeader(username string, logoutClick h.H) h.H { - return h.Div(h.Class("flex justify-center items-center gap-4 mb-4 p-2 bg-base-200 rounded-lg"), - h.Span(h.Text("Logged in as "), h.Strong(h.Text(username))), - h.Button( - h.Type("button"), - h.Class("btn btn-ghost btn-sm"), - h.Text("Logout"), - logoutClick, - ), - ) -} - -func GuestBanner() h.H { - return h.Div(h.Class("alert text-sm mb-4"), - h.Text("Playing as guest. "), - h.A(h.Class("link"), h.Href("/login"), h.Text("Login")), - h.Text(" or "), - h.A(h.Class("link"), h.Href("/register"), h.Text("Register")), - h.Text(" to save your games."), - ) -} diff --git a/ui/board.go b/ui/board.go deleted file mode 100644 index c99b018..0000000 --- a/ui/board.go +++ /dev/null @@ -1,69 +0,0 @@ -package ui - -import ( - "github.com/ryanhamamura/c4/game" - "github.com/ryanhamamura/via/h" -) - -// ColumnClickFn returns an h.H onClick attribute for a given column index -type ColumnClickFn func(col int) h.H - -func BoardComponent(g *game.Game, columnClick ColumnClickFn, myColor int) h.H { - var cols []h.H - - activeTurn := 0 - if g.Status == game.StatusInProgress { - activeTurn = g.CurrentTurn - } - - for col := 0; col < 7; col++ { - var cells []h.H - for row := 0; row < 6; row++ { - cellColor := g.Board[row][col] - isWinning := g.IsWinningCell(row, col) - isActiveTurn := cellColor != 0 && cellColor == activeTurn - cells = append(cells, Cell(cellColor, isWinning, isActiveTurn)) - } - - // Column is clickable only if it's player's turn and game is in progress - canClick := g.Status == game.StatusInProgress && g.CurrentTurn == myColor - cols = append(cols, Column(col, cells, columnClick, canClick)) - } - - boardAttrs := []h.H{h.Class("board")} - boardAttrs = append(boardAttrs, cols...) - return h.Div(boardAttrs...) -} - -func Column(colIdx int, cells []h.H, columnClick ColumnClickFn, canClick bool) h.H { - class := "column" - if canClick { - class += " clickable" - } - - attrs := []h.H{h.Class(class)} - - if canClick && columnClick != nil { - attrs = append(attrs, columnClick(colIdx)) - } - - attrs = append(attrs, cells...) - return h.Div(attrs...) -} - -func Cell(color int, isWinning, isActiveTurn bool) h.H { - class := "cell" - switch color { - case 1: - class += " red" - case 2: - class += " yellow" - } - if isWinning { - class += " winning" - } - if isActiveTurn { - class += " active-turn" - } - return h.Div(h.Class(class)) -} diff --git a/ui/c4chat.go b/ui/c4chat.go deleted file mode 100644 index a796085..0000000 --- a/ui/c4chat.go +++ /dev/null @@ -1,64 +0,0 @@ -package ui - -import ( - "fmt" - - "github.com/ryanhamamura/via/h" -) - -type C4ChatMessage struct { - Nickname string `json:"nickname"` - Color int `json:"color"` // 1=Red, 2=Yellow - Message string `json:"message"` - Time int64 `json:"time"` -} - -var c4ChatColors = map[int]string{ - 1: "#4a2a3a", - 2: "#2a4545", -} - -func C4Chat(messages []C4ChatMessage, msgBind, sendClick, sendKeyDown h.H) h.H { - var msgEls []h.H - for _, m := range messages { - color := "#666" - if c, ok := c4ChatColors[m.Color]; ok { - color = c - } - msgEls = append(msgEls, h.Div(h.Class("c4-chat-msg"), - h.Span( - h.Attr("style", fmt.Sprintf("color:%s;font-weight:bold;", color)), - h.Text(m.Nickname+": "), - ), - h.Span(h.Text(m.Message)), - )) - } - - autoScroll := h.Script(h.Text(` -(function(){ - var el = document.querySelector('.c4-chat-history'); - if (!el) return; - el.scrollTop = el.scrollHeight; - new MutationObserver(function(){ el.scrollTop = el.scrollHeight; }) - .observe(el, {childList:true, subtree:true}); -})(); -`)) - - historyAttrs := []h.H{h.Class("c4-chat-history")} - historyAttrs = append(historyAttrs, msgEls...) - historyAttrs = append(historyAttrs, autoScroll) - - return h.Div(h.Class("c4-chat"), - h.Div(historyAttrs...), - h.Div(h.Class("c4-chat-input"), h.DataIgnoreMorph(), - h.Input( - h.Type("text"), - h.Attr("placeholder", "Chat..."), - h.Attr("autocomplete", "off"), - msgBind, - sendKeyDown, - ), - h.Button(h.Type("button"), h.Text("Send"), sendClick), - ), - ) -} diff --git a/ui/gamelist.go b/ui/gamelist.go deleted file mode 100644 index 0ed4254..0000000 --- a/ui/gamelist.go +++ /dev/null @@ -1,110 +0,0 @@ -package ui - -import ( - "fmt" - "time" - - "github.com/ryanhamamura/c4/game" - "github.com/ryanhamamura/via/h" -) - -type GameListItem struct { - ID string - Status int - OpponentName string - IsMyTurn bool - LastPlayed time.Time -} - -func GameList(games []GameListItem, deleteClick func(id string) h.H) h.H { - if len(games) == 0 { - return nil - } - - var items []h.H - for _, g := range games { - items = append(items, gameListEntry(g, deleteClick)) - } - - listItems := []h.H{h.Class("flex flex-col gap-2")} - listItems = append(listItems, items...) - - return h.Div(h.Class("mt-8 text-left"), - h.H3(h.Class("mb-4 text-center text-lg font-bold"), h.Text("Your Games")), - h.Div(listItems...), - ) -} - -func gameListEntry(g GameListItem, deleteClick func(id string) h.H) h.H { - statusText, statusClass := getStatusDisplay(g) - - return h.Div(h.Class("flex items-center gap-2 p-2 bg-base-200 rounded-lg transition-colors hover:bg-base-300"), - h.A( - h.Href("/game/"+g.ID), - h.Class("flex-1 flex justify-between items-center px-2 py-1 no-underline text-base-content"), - h.Div(h.Class("flex flex-col gap-1"), - h.Span(h.Class("font-bold"), h.Text(getOpponentDisplay(g))), - h.Span(h.Class(statusClass), h.Text(statusText)), - ), - h.Div( - h.Span(h.Class("text-xs opacity-60"), h.Text(formatTimeAgo(g.LastPlayed))), - ), - ), - h.Button( - h.Type("button"), - h.Class("btn btn-ghost btn-sm btn-square hover:btn-error"), - h.Text("\u00d7"), - deleteClick(g.ID), - ), - ) -} - -func getStatusDisplay(g GameListItem) (string, string) { - switch game.GameStatus(g.Status) { - case game.StatusWaitingForPlayer: - return "Waiting for opponent", "text-sm opacity-60" - case game.StatusInProgress: - if g.IsMyTurn { - return "Your turn!", "text-sm text-success font-bold" - } - return "Opponent's turn", "text-sm" - } - return "", "" -} - -func getOpponentDisplay(g GameListItem) string { - if g.OpponentName == "" { - return "Waiting for opponent..." - } - return "vs " + g.OpponentName -} - -func formatTimeAgo(t time.Time) string { - if t.IsZero() { - return "" - } - duration := time.Since(t) - - if duration < time.Minute { - return "just now" - } - if duration < time.Hour { - mins := int(duration.Minutes()) - if mins == 1 { - return "1 minute ago" - } - return fmt.Sprintf("%d minutes ago", mins) - } - if duration < 24*time.Hour { - hours := int(duration.Hours()) - if hours == 1 { - return "1 hour ago" - } - return fmt.Sprintf("%d hours ago", hours) - } - days := int(duration.Hours() / 24) - if days == 1 { - return "yesterday" - } - return fmt.Sprintf("%d days ago", days) -} diff --git a/ui/lobby.go b/ui/lobby.go deleted file mode 100644 index 6421670..0000000 --- a/ui/lobby.go +++ /dev/null @@ -1,153 +0,0 @@ -package ui - -import ( - "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/via/h" -) - -type LobbyProps struct { - NicknameBind h.H - CreateGameKeyDown h.H - CreateGameClick h.H - IsLoggedIn bool - Username string - LogoutClick h.H - UserGames []GameListItem - DeleteGameClick func(id string) h.H - ActiveTab string - TabClickConnect4 h.H - TabClickSnake h.H - SnakeNicknameBind h.H - SnakeSoloClicks []h.H - SnakeMultiClicks []h.H - ActiveSnakeGames []*snake.SnakeGame - SelectedSpeedIndex int - SpeedSelectClicks []h.H -} - -func BackToLobby() h.H { - return h.A(h.Class("link text-sm opacity-70"), h.Href("/"), h.Text("← Back")) -} - -func StealthTitle(class string) h.H { - return h.Span(h.Class(class), - h.Span(h.Style("color:#4a2a3a"), h.Text("●")), - h.Span(h.Style("color:#2a4545"), h.Text("●")), - h.Span(h.Style("color:#4a2a3a"), h.Text("●")), - h.Span(h.Style("color:#2a4545"), h.Text("●")), - ) -} - -func LobbyView(p LobbyProps) h.H { - var authSection h.H - if p.IsLoggedIn { - authSection = AuthHeader(p.Username, p.LogoutClick) - } else { - authSection = GuestBanner() - } - - connect4Class := "tab" - snakeClass := "tab" - if p.ActiveTab == "snake" { - snakeClass += " tab-active" - } else { - connect4Class += " tab-active" - } - - var tabContent h.H - if p.ActiveTab == "snake" { - tabContent = SnakeLobbyTab(p.SnakeNicknameBind, p.SnakeSoloClicks, p.SnakeMultiClicks, p.ActiveSnakeGames, p.SelectedSpeedIndex, p.SpeedSelectClicks) - } else { - tabContent = connect4LobbyContent(p) - } - - return h.Main(h.Class("max-w-md mx-auto mt-8 text-center"), - authSection, - h.H1(h.Class("text-3xl font-bold mb-4"), StealthTitle("")), - h.Div(h.Class("tabs tabs-box mb-6 justify-center"), - h.Button(h.Class(connect4Class), h.Type("button"), StealthTitle(""), p.TabClickConnect4), - h.Button(h.Class(snakeClass), h.Type("button"), h.Text("~~~~"), p.TabClickSnake), - ), - tabContent, - ) -} - -func connect4LobbyContent(p LobbyProps) h.H { - return h.Div( - h.P(h.Class("mb-4"), h.Text("Start a new session")), - h.Form( - h.FieldSet(h.Class("fieldset"), - h.Label(h.Class("label"), h.Text("Your Nickname"), h.Attr("for", "nickname")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("nickname"), - h.Type("text"), - h.Placeholder("Enter your nickname"), - p.NicknameBind, - h.Attr("required"), - p.CreateGameKeyDown, - ), - ), - h.Button( - h.Class("btn btn-primary w-full"), - h.Type("button"), - h.Text("Create Game"), - p.CreateGameClick, - ), - ), - GameList(p.UserGames, p.DeleteGameClick), - ) -} - -func NicknamePrompt(nicknameBind, setNicknameKeyDown, setNicknameClick h.H) h.H { - return h.Main(h.Class("max-w-sm mx-auto mt-8 text-center"), - h.H1(h.Class("text-3xl font-bold"), h.Text("Join Game")), - h.P(h.Class("mb-4"), h.Text("Enter your nickname to join the game.")), - h.Form( - h.FieldSet(h.Class("fieldset"), - h.Label(h.Class("label"), h.Text("Your Nickname"), h.Attr("for", "nickname")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("nickname"), - h.Type("text"), - h.Placeholder("Enter your nickname"), - nicknameBind, - h.Attr("required"), - h.Attr("autofocus"), - setNicknameKeyDown, - ), - ), - h.Button( - h.Class("btn btn-primary w-full"), - h.Type("button"), - h.Text("Join"), - setNicknameClick, - ), - ), - ) -} - -func GameJoinPrompt(loginClick, guestClick, registerClick h.H) h.H { - return h.Main(h.Class("max-w-sm mx-auto mt-8 text-center"), - h.H1(h.Class("text-3xl font-bold"), h.Text("Join Game")), - h.P(h.Class("mb-4"), h.Text("Log in to track your game history, or continue as a guest.")), - h.Div(h.Class("flex flex-col gap-2 my-4"), - h.Button( - h.Class("btn btn-primary w-full"), - h.Type("button"), - h.Text("Login"), - loginClick, - ), - h.Button( - h.Class("btn btn-secondary w-full"), - h.Type("button"), - h.Text("Continue as Guest"), - guestClick, - ), - ), - h.P(h.Class("text-sm opacity-60"), - h.Text("Don't have an account? "), - h.A(h.Class("link"), h.Href("#"), h.Text("Register"), registerClick), - ), - ) -} diff --git a/ui/snakeboard.go b/ui/snakeboard.go deleted file mode 100644 index 15097b8..0000000 --- a/ui/snakeboard.go +++ /dev/null @@ -1,112 +0,0 @@ -package ui - -import ( - "fmt" - - "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/via/h" -) - -func SnakeBoard(sg *snake.SnakeGame) h.H { - state := sg.State - if state == nil || sg.Status != snake.StatusInProgress && sg.Status != snake.StatusFinished { - return nil - } - - // Build a lookup grid for rendering - type cellInfo struct { - snakeIdx int // -1 = empty, -2 = food - isHead bool - } - grid := make([][]cellInfo, state.Height) - for y := 0; y < state.Height; y++ { - grid[y] = make([]cellInfo, state.Width) - for x := 0; x < state.Width; x++ { - grid[y][x] = cellInfo{snakeIdx: -1} - } - } - - for fi := range state.Food { - f := state.Food[fi] - if f.X >= 0 && f.X < state.Width && f.Y >= 0 && f.Y < state.Height { - grid[f.Y][f.X] = cellInfo{snakeIdx: -2} - } - } - - for si, s := range state.Snakes { - if s == nil { - continue - } - for bi, bp := range s.Body { - if bp.X >= 0 && bp.X < state.Width && bp.Y >= 0 && bp.Y < state.Height { - grid[bp.Y][bp.X] = cellInfo{snakeIdx: si, isHead: bi == 0} - } - } - } - - // Cell size scales with grid dimensions - cellSize := cellSizeForGrid(state.Width, state.Height) - - var rows []h.H - for y := 0; y < state.Height; y++ { - var cells []h.H - for x := 0; x < state.Width; x++ { - ci := grid[y][x] - class := "snake-cell" - style := fmt.Sprintf("width:%dpx;height:%dpx;", cellSize, cellSize) - - switch { - case ci.snakeIdx == -2: - class += " snake-food" - case ci.snakeIdx >= 0: - s := state.Snakes[ci.snakeIdx] - colorIdx := ci.snakeIdx - bg := "" - if colorIdx < len(snake.SnakeColors) { - bg = snake.SnakeColors[colorIdx] - style += fmt.Sprintf("background:%s;", bg) - } - if !s.Alive { - class += " snake-dead" - } - if ci.isHead { - class += " snake-head" - if bg != "" { - style += fmt.Sprintf("box-shadow:0 0 8px %s;", bg) - } - } - } - - cells = append(cells, h.Div(h.Class(class), h.Attr("style", style))) - } - rowAttrs := append([]h.H{h.Class("snake-row")}, cells...) - rows = append(rows, h.Div(rowAttrs...)) - } - - boardStyle := fmt.Sprintf("grid-template-columns:repeat(%d,1fr);", state.Width) - attrs := []h.H{ - h.Class("snake-board"), - h.Attr("style", boardStyle), - } - attrs = append(attrs, rows...) - return h.Div(attrs...) -} - -func cellSizeForGrid(width, height int) int { - maxDim := width - if height > maxDim { - maxDim = height - } - switch { - case maxDim <= 15: - return 28 - case maxDim <= 20: - return 24 - case maxDim <= 30: - return 20 - case maxDim <= 40: - return 16 - default: - return 14 - } -} diff --git a/ui/snakechat.go b/ui/snakechat.go deleted file mode 100644 index 66bc663..0000000 --- a/ui/snakechat.go +++ /dev/null @@ -1,63 +0,0 @@ -package ui - -import ( - "fmt" - - "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/via/h" -) - -type ChatMessage struct { - Nickname string `json:"nickname"` - Slot int `json:"slot"` - Message string `json:"message"` - Time int64 `json:"time"` -} - -func SnakeChat(messages []ChatMessage, msgBind, sendClick, sendKeyDown h.H) h.H { - var msgEls []h.H - for _, m := range messages { - color := "#666" - if m.Slot >= 0 && m.Slot < len(snake.SnakeColors) { - color = snake.SnakeColors[m.Slot] - } - msgEls = append(msgEls, h.Div(h.Class("snake-chat-msg"), - h.Span( - h.Attr("style", fmt.Sprintf("color:%s;font-weight:bold;", color)), - h.Text(m.Nickname+": "), - ), - h.Span(h.Text(m.Message)), - )) - } - - // Auto-scroll chat history to bottom on new messages - autoScroll := h.Script(h.Text(` -(function(){ - var el = document.querySelector('.snake-chat-history'); - if (!el) return; - el.scrollTop = el.scrollHeight; - new MutationObserver(function(){ el.scrollTop = el.scrollHeight; }) - .observe(el, {childList:true, subtree:true}); -})(); -`)) - - historyAttrs := []h.H{h.Class("snake-chat-history")} - historyAttrs = append(historyAttrs, msgEls...) - historyAttrs = append(historyAttrs, autoScroll) - - return h.Div(h.Class("snake-chat"), - h.Div(historyAttrs...), - h.Div(h.Class("snake-chat-input"), - h.Input( - h.Type("text"), - h.Attr("placeholder", "Chat..."), - h.Attr("autocomplete", "off"), - // Prevent key events from bubbling to the game's window-level handler - h.Attr("onkeydown", "event.stopPropagation()"), - msgBind, - sendKeyDown, - ), - h.Button(h.Type("button"), h.Text("Send"), sendClick), - ), - ) -} diff --git a/ui/snakelobby.go b/ui/snakelobby.go deleted file mode 100644 index e8b12e5..0000000 --- a/ui/snakelobby.go +++ /dev/null @@ -1,124 +0,0 @@ -package ui - -import ( - "fmt" - - "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/via/h" -) - -func SnakeLobbyTab(nicknameBind h.H, soloClicks, multiClicks []h.H, activeGames []*snake.SnakeGame, selectedSpeedIndex int, speedSelectClicks []h.H) h.H { - // Solo play buttons - var soloButtons []h.H - for i, preset := range snake.GridPresets { - var click h.H - if i < len(soloClicks) { - click = soloClicks[i] - } - soloButtons = append(soloButtons, - h.Button( - h.Class("btn btn-secondary"), - h.Type("button"), - h.Text(fmt.Sprintf("%s (%d×%d)", preset.Name, preset.Width, preset.Height)), - click, - ), - ) - } - - // Multiplayer buttons - var multiButtons []h.H - for i, preset := range snake.GridPresets { - var click h.H - if i < len(multiClicks) { - click = multiClicks[i] - } - multiButtons = append(multiButtons, - h.Button( - h.Class("btn btn-primary"), - h.Type("button"), - h.Text(fmt.Sprintf("%s (%d×%d)", preset.Name, preset.Width, preset.Height)), - click, - ), - ) - } - - nicknameField := h.Div(h.Class("mb-4"), - h.FieldSet(h.Class("fieldset"), - h.Label(h.Class("label"), h.Text("Your Nickname"), h.Attr("for", "snake-nickname")), - h.Input( - h.Class("input input-bordered w-full"), - h.ID("snake-nickname"), - h.Type("text"), - h.Placeholder("Enter your nickname"), - nicknameBind, - h.Attr("required"), - ), - ), - ) - - // Speed selector - var speedButtons []h.H - for i, preset := range snake.SpeedPresets { - btnClass := "btn btn-sm" - if i == selectedSpeedIndex { - btnClass += " btn-active" - } - var click h.H - if i < len(speedSelectClicks) { - click = speedSelectClicks[i] - } - speedButtons = append(speedButtons, h.Button( - h.Class(btnClass), - h.Type("button"), - h.Text(preset.Name), - click, - )) - } - speedSelector := h.Div(h.Class("mb-4"), - h.Label(h.Class("label"), h.Text("Speed")), - h.Div(append([]h.H{h.Class("btn-group")}, speedButtons...)...), - ) - - soloSection := h.Div(h.Class("mb-6"), - h.H3(h.Class("text-lg font-bold mb-2"), h.Text("Play Solo")), - h.Div(append([]h.H{h.Class("flex gap-2 justify-center")}, soloButtons...)...), - ) - - multiSection := h.Div(h.Class("mb-6"), - h.H3(h.Class("text-lg font-bold mb-2"), h.Text("Create Multiplayer Game")), - h.Div(append([]h.H{h.Class("flex gap-2 justify-center")}, multiButtons...)...), - ) - - var gameListEl h.H - if len(activeGames) > 0 { - var items []h.H - for _, g := range activeGames { - playerCount := g.PlayerCount() - sizeLabel := fmt.Sprintf("%d×%d", g.State.Width, g.State.Height) - statusLabel := "Waiting" - if g.Status == snake.StatusCountdown { - statusLabel = "Starting soon" - } - items = append(items, h.A( - h.Href("/snake/"+g.ID), - h.Class("flex justify-between items-center p-3 bg-base-200 rounded-lg hover:bg-base-300 no-underline text-base-content"), - h.Span(h.Text(fmt.Sprintf("%s — %d/8 players", sizeLabel, playerCount))), - h.Span(h.Class("text-sm opacity-60"), h.Text(statusLabel)), - )) - } - listAttrs := []h.H{h.Class("flex flex-col gap-2")} - listAttrs = append(listAttrs, items...) - gameListEl = h.Div(h.Class("mt-6"), - h.H3(h.Class("text-lg font-bold mb-2 text-center"), h.Text("Join a Game")), - h.Div(listAttrs...), - ) - } - - return h.Div( - nicknameField, - speedSelector, - soloSection, - multiSection, - gameListEl, - ) -} diff --git a/ui/snakestatus.go b/ui/snakestatus.go deleted file mode 100644 index e50b989..0000000 --- a/ui/snakestatus.go +++ /dev/null @@ -1,161 +0,0 @@ -package ui - -import ( - "fmt" - "math" - "time" - - "github.com/ryanhamamura/c4/snake" - "github.com/ryanhamamura/via/h" -) - -func SnakeStatusBanner(sg *snake.SnakeGame, mySlot int, rematchClick h.H) h.H { - switch sg.Status { - case snake.StatusWaitingForPlayers: - if sg.Mode == snake.ModeSinglePlayer { - return h.Div(h.Class("alert bg-base-200 text-xl font-bold"), - h.Text("Ready?"), - ) - } - return h.Div(h.Class("alert bg-base-200 text-xl font-bold"), - h.Text("Waiting for players..."), - ) - - case snake.StatusCountdown: - remaining := time.Until(sg.CountdownEnd) - secs := int(math.Ceil(remaining.Seconds())) - if secs < 0 { - secs = 0 - } - return h.Div(h.Class("alert alert-info text-xl font-bold"), - h.Text(fmt.Sprintf("Starting in %d...", secs)), - ) - - case snake.StatusInProgress: - if sg.State != nil && mySlot >= 0 && mySlot < len(sg.State.Snakes) { - s := sg.State.Snakes[mySlot] - if s != nil && !s.Alive { - return h.Div(h.Class("alert alert-error text-xl font-bold"), - h.Text("You're out!"), - ) - } - } - // Show score during single player gameplay - if sg.Mode == snake.ModeSinglePlayer { - return h.Div(h.Class("alert alert-success text-xl font-bold"), - h.Text(fmt.Sprintf("Score: %d", sg.Score)), - ) - } - return h.Div(h.Class("alert alert-success text-xl font-bold"), - h.Text("Go!"), - ) - - case snake.StatusFinished: - var msg string - var class string - - if sg.Mode == snake.ModeSinglePlayer { - msg = fmt.Sprintf("Game Over! Score: %d", sg.Score) - class = "alert alert-info text-xl font-bold" - } else if sg.Winner != nil { - if sg.Winner.Slot == mySlot { - msg = "You win!" - class = "alert alert-success text-xl font-bold" - } else { - msg = sg.Winner.Nickname + " wins!" - class = "alert alert-error text-xl font-bold" - } - } else { - msg = "It's a draw!" - class = "alert alert-warning text-xl font-bold" - } - - content := []h.H{h.Class(class), h.Text(msg)} - - if sg.RematchGameID != nil { - content = append(content, - h.A( - h.Class("btn btn-sm bg-white text-gray-800 border-none ml-4"), - h.Href("/snake/"+*sg.RematchGameID), - h.Text("Join Rematch"), - ), - ) - } else if rematchClick != nil { - content = append(content, - h.Button( - h.Class("btn btn-sm bg-white text-gray-800 border-none ml-4"), - h.Type("button"), - h.Text("Play again"), - rematchClick, - ), - ) - } - - return h.Div(content...) - } - - return nil -} - -func SnakePlayerList(sg *snake.SnakeGame, mySlot int) h.H { - var items []h.H - - for i, p := range sg.Players { - if p == nil { - continue - } - - colorHex := "#666" - if i < len(snake.SnakeColors) { - colorHex = snake.SnakeColors[i] - } - - name := p.Nickname - if i == mySlot { - name += " (You)" - } - - var statusEl h.H - if sg.Status == snake.StatusInProgress || sg.Status == snake.StatusFinished { - if sg.State != nil && i < len(sg.State.Snakes) { - s := sg.State.Snakes[i] - if s != nil { - if s.Alive { - length := len(s.Body) - statusEl = h.Span(h.Class("text-sm opacity-60"), h.Text(fmt.Sprintf(" (%d)", length))) - } else { - statusEl = h.Span(h.Class("text-sm opacity-40"), h.Text(" (dead)")) - } - } - } - } - - chipStyle := fmt.Sprintf("width:16px;height:16px;border-radius:50%%;background:%s;display:inline-block;", colorHex) - - items = append(items, h.Div(h.Class("flex items-center gap-2"), - h.Span(h.Attr("style", chipStyle)), - h.Span(h.Text(name)), - statusEl, - )) - } - - listAttrs := []h.H{h.Class("flex flex-wrap gap-4 mb-2")} - listAttrs = append(listAttrs, items...) - return h.Div(listAttrs...) -} - -func SnakeInviteLink(gameID string) h.H { - fullURL := getBaseURL() + "/snake/" + gameID - return h.Div(h.Class("mt-4 text-center"), - h.P(h.Text("Share this link to invite players:")), - h.Div(h.Class("bg-base-200 p-4 rounded-lg font-mono break-all my-2"), - h.Text(fullURL), - ), - h.Button( - h.Class("btn btn-sm mt-2"), - h.Type("button"), - h.Text("Copy Link"), - h.Attr("onclick", "navigator.clipboard.writeText('"+fullURL+"')"), - ), - ) -} diff --git a/ui/status.go b/ui/status.go deleted file mode 100644 index 3fb3793..0000000 --- a/ui/status.go +++ /dev/null @@ -1,137 +0,0 @@ -package ui - -import ( - "github.com/ryanhamamura/c4/config" - "github.com/ryanhamamura/c4/game" - "github.com/ryanhamamura/via/h" -) - -func StatusBanner(g *game.Game, myColor int, playAgainClick h.H) h.H { - var message string - var class string - - switch g.Status { - case game.StatusWaitingForPlayer: - message = "Waiting for opponent..." - class = "alert bg-base-200 text-xl font-bold" - case game.StatusInProgress: - if g.CurrentTurn == myColor { - message = "Your turn!" - class = "alert alert-success text-xl font-bold" - } else { - opponentName := getOpponentName(g, myColor) - message = opponentName + "'s turn" - class = "alert bg-base-200 text-xl font-bold" - } - case game.StatusWon: - if g.Winner != nil && g.Winner.Color == myColor { - message = "You win!" - class = "alert alert-success text-xl font-bold" - } else if g.Winner != nil { - message = g.Winner.Nickname + " wins!" - class = "alert alert-error text-xl font-bold" - } - case game.StatusDraw: - message = "It's a draw!" - class = "alert alert-warning text-xl font-bold" - } - - content := []h.H{ - h.Class(class), - h.Text(message), - } - - // Show rematch options for finished games - if g.IsFinished() { - if g.RematchGameID != nil { - content = append(content, - h.A( - h.Class("btn btn-sm bg-white text-gray-800 border-none ml-4"), - h.Href("/game/"+*g.RematchGameID), - h.Text("Join Rematch"), - ), - ) - } else if playAgainClick != nil { - content = append(content, - h.Button( - h.Class("btn btn-sm bg-white text-gray-800 border-none ml-4"), - h.Type("button"), - h.Text("Play again"), - playAgainClick, - ), - ) - } - } - - return h.Div(content...) -} - -func getOpponentName(g *game.Game, myColor int) string { - for _, p := range g.Players { - if p != nil && p.Color != myColor { - return p.Nickname - } - } - return "Opponent" -} - -func PlayerInfo(g *game.Game, myColor int) h.H { - var myName, opponentName string - var myColorClass, opponentColorClass string - - for _, p := range g.Players { - if p == nil { - continue - } - if p.Color == myColor { - myName = p.Nickname - if p.Color == 1 { - myColorClass = "red" - } else { - myColorClass = "yellow" - } - } else { - opponentName = p.Nickname - if p.Color == 1 { - opponentColorClass = "red" - } else { - opponentColorClass = "yellow" - } - } - } - - if opponentName == "" { - opponentName = "Waiting..." - } - - return h.Div(h.Class("flex gap-8 mb-2"), - h.Div(h.Class("flex items-center gap-2"), - h.Span(h.Class("player-chip "+myColorClass)), - h.Span(h.Text(myName+" (You)")), - ), - h.Div(h.Class("flex items-center gap-2"), - h.Span(h.Class("player-chip "+opponentColorClass)), - h.Span(h.Text(opponentName)), - ), - ) -} - -func getBaseURL() string { - return config.Global.AppURL -} - -func InviteLink(gameID string) h.H { - fullURL := getBaseURL() + "/game/" + gameID - return h.Div(h.Class("mt-4 text-center"), - h.P(h.Text("Share this link with your opponent:")), - h.Div(h.Class("bg-base-200 p-4 rounded-lg font-mono break-all my-2"), - h.Text(fullURL), - ), - h.Button( - h.Class("btn btn-sm mt-2"), - h.Type("button"), - h.Text("Copy Link"), - h.Attr("onclick", "navigator.clipboard.writeText('"+fullURL+"')"), - ), - ) -} From 2aa026b1d5ea9f2653abd5753cec9eaa4d076651 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:30:33 -1000 Subject: [PATCH 3/9] refactor: remove persister abstraction layer Inline persistence logic directly into game stores and handlers: - game/persist.go: DB mapping methods on GameStore and GameInstance - snake/persist.go: DB mapping methods on SnakeStore and SnakeGameInstance - Chat persistence inlined into c4game handlers - Delete db/persister.go (GamePersister, SnakePersister, ChatPersister) - Stores now take *repository.Queries directly instead of Persister interface --- db/persister.go | 326 ------------------------------------ features/c4game/handlers.go | 58 +++++-- features/c4game/routes.go | 10 +- game/persist.go | 157 +++++++++++++++++ game/store.go | 76 ++++----- main.go | 10 +- router/router.go | 4 +- snake/loop.go | 16 +- snake/persist.go | 186 ++++++++++++++++++++ snake/store.go | 80 ++++----- 10 files changed, 475 insertions(+), 448 deletions(-) delete mode 100644 db/persister.go create mode 100644 game/persist.go create mode 100644 snake/persist.go diff --git a/db/persister.go b/db/persister.go deleted file mode 100644 index f87760b..0000000 --- a/db/persister.go +++ /dev/null @@ -1,326 +0,0 @@ -package db - -import ( - "context" - "database/sql" - "slices" - - "github.com/ryanhamamura/c4/db/repository" - "github.com/ryanhamamura/c4/game" - "github.com/ryanhamamura/c4/snake" -) - -type GamePersister struct { - queries *repository.Queries -} - -func NewGamePersister(q *repository.Queries) *GamePersister { - return &GamePersister{queries: q} -} - -func (p *GamePersister) SaveGame(g *game.Game) error { - ctx := context.Background() - - _, err := p.queries.GetGame(ctx, g.ID) - if err == sql.ErrNoRows { - _, err = p.queries.CreateGame(ctx, repository.CreateGameParams{ - ID: g.ID, - Board: g.BoardToJSON(), - CurrentTurn: int64(g.CurrentTurn), - Status: int64(g.Status), - }) - return err - } - if err != nil { - return err - } - - var winnerUserID sql.NullString - if g.Winner != nil && g.Winner.UserID != nil { - winnerUserID = sql.NullString{String: *g.Winner.UserID, Valid: true} - } - - winningCells := sql.NullString{} - if wc := g.WinningCellsToJSON(); wc != "" { - winningCells = sql.NullString{String: wc, Valid: true} - } - - rematchGameID := sql.NullString{} - if g.RematchGameID != nil { - rematchGameID = sql.NullString{String: *g.RematchGameID, Valid: true} - } - - return p.queries.UpdateGame(ctx, repository.UpdateGameParams{ - Board: g.BoardToJSON(), - CurrentTurn: int64(g.CurrentTurn), - Status: int64(g.Status), - WinnerUserID: winnerUserID, - WinningCells: winningCells, - RematchGameID: rematchGameID, - ID: g.ID, - }) -} - -func (p *GamePersister) LoadGame(id string) (*game.Game, error) { - ctx := context.Background() - row, err := p.queries.GetGame(ctx, id) - if err != nil { - return nil, err - } - - g := &game.Game{ - ID: row.ID, - CurrentTurn: int(row.CurrentTurn), - Status: game.GameStatus(row.Status), - } - - if err := g.BoardFromJSON(row.Board); err != nil { - return nil, err - } - - if row.WinningCells.Valid { - g.WinningCellsFromJSON(row.WinningCells.String) - } - - if row.RematchGameID.Valid { - g.RematchGameID = &row.RematchGameID.String - } - - return g, nil -} - -func (p *GamePersister) SaveGamePlayer(gameID string, player *game.Player, slot int) error { - ctx := context.Background() - - var userID, guestPlayerID sql.NullString - if player.UserID != nil { - userID = sql.NullString{String: *player.UserID, Valid: true} - } else { - guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} - } - - return p.queries.CreateGamePlayer(ctx, repository.CreateGamePlayerParams{ - GameID: gameID, - UserID: userID, - GuestPlayerID: guestPlayerID, - Nickname: player.Nickname, - Color: int64(player.Color), - Slot: int64(slot), - }) -} - -func (p *GamePersister) LoadGamePlayers(gameID string) ([]*game.Player, error) { - ctx := context.Background() - rows, err := p.queries.GetGamePlayers(ctx, gameID) - if err != nil { - return nil, err - } - - players := make([]*game.Player, 0, len(rows)) - for _, row := range rows { - player := &game.Player{ - Nickname: row.Nickname, - Color: int(row.Color), - } - - if row.UserID.Valid { - player.UserID = &row.UserID.String - player.ID = game.PlayerID(row.UserID.String) - } else if row.GuestPlayerID.Valid { - player.ID = game.PlayerID(row.GuestPlayerID.String) - } - - players = append(players, player) - } - - return players, nil -} - -func (p *GamePersister) DeleteGame(id string) error { - ctx := context.Background() - return p.queries.DeleteGame(ctx, id) -} - -// SnakePersister implements snake.Persister -type SnakePersister struct { - queries *repository.Queries -} - -func NewSnakePersister(q *repository.Queries) *SnakePersister { - return &SnakePersister{queries: q} -} - -func (p *SnakePersister) SaveSnakeGame(sg *snake.SnakeGame) error { - ctx := context.Background() - - boardJSON := "{}" - if sg.State != nil { - boardJSON = sg.State.ToJSON() - } - - var gridWidth, gridHeight sql.NullInt64 - if sg.State != nil { - gridWidth = sql.NullInt64{Int64: int64(sg.State.Width), Valid: true} - gridHeight = sql.NullInt64{Int64: int64(sg.State.Height), Valid: true} - } - - _, err := p.queries.GetSnakeGame(ctx, sg.ID) - if err == sql.ErrNoRows { - _, err = p.queries.CreateSnakeGame(ctx, repository.CreateSnakeGameParams{ - ID: sg.ID, - Board: boardJSON, - Status: int64(sg.Status), - GridWidth: gridWidth, - GridHeight: gridHeight, - GameMode: int64(sg.Mode), - SnakeSpeed: int64(sg.Speed), - }) - return err - } - if err != nil { - return err - } - - var winnerUserID sql.NullString - if sg.Winner != nil && sg.Winner.UserID != nil { - winnerUserID = sql.NullString{String: *sg.Winner.UserID, Valid: true} - } - - rematchGameID := sql.NullString{} - if sg.RematchGameID != nil { - rematchGameID = sql.NullString{String: *sg.RematchGameID, Valid: true} - } - - return p.queries.UpdateSnakeGame(ctx, repository.UpdateSnakeGameParams{ - Board: boardJSON, - Status: int64(sg.Status), - WinnerUserID: winnerUserID, - RematchGameID: rematchGameID, - Score: int64(sg.Score), - ID: sg.ID, - }) -} - -func (p *SnakePersister) LoadSnakeGame(id string) (*snake.SnakeGame, error) { - ctx := context.Background() - row, err := p.queries.GetSnakeGame(ctx, id) - if err != nil { - return nil, err - } - - state, err := snake.GameStateFromJSON(row.Board) - if err != nil { - state = &snake.GameState{} - } - if row.GridWidth.Valid { - state.Width = int(row.GridWidth.Int64) - } - if row.GridHeight.Valid { - state.Height = int(row.GridHeight.Int64) - } - - sg := &snake.SnakeGame{ - ID: row.ID, - State: state, - Players: make([]*snake.Player, 8), - Status: snake.Status(row.Status), - Mode: snake.GameMode(row.GameMode), - Score: int(row.Score), - Speed: int(row.SnakeSpeed), - } - - if row.RematchGameID.Valid { - sg.RematchGameID = &row.RematchGameID.String - } - - return sg, nil -} - -func (p *SnakePersister) SaveSnakePlayer(gameID string, player *snake.Player) error { - ctx := context.Background() - - var userID, guestPlayerID sql.NullString - if player.UserID != nil { - userID = sql.NullString{String: *player.UserID, Valid: true} - } else { - guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} - } - - return p.queries.CreateSnakePlayer(ctx, repository.CreateSnakePlayerParams{ - GameID: gameID, - UserID: userID, - GuestPlayerID: guestPlayerID, - Nickname: player.Nickname, - Color: int64(player.Slot + 1), - Slot: int64(player.Slot), - }) -} - -func (p *SnakePersister) LoadSnakePlayers(gameID string) ([]*snake.Player, error) { - ctx := context.Background() - rows, err := p.queries.GetSnakePlayers(ctx, gameID) - if err != nil { - return nil, err - } - - players := make([]*snake.Player, 0, len(rows)) - for _, row := range rows { - player := &snake.Player{ - Nickname: row.Nickname, - Slot: int(row.Slot), - } - - if row.UserID.Valid { - player.UserID = &row.UserID.String - player.ID = snake.PlayerID(row.UserID.String) - } else if row.GuestPlayerID.Valid { - player.ID = snake.PlayerID(row.GuestPlayerID.String) - } - - players = append(players, player) - } - - return players, nil -} - -func (p *SnakePersister) DeleteSnakeGame(id string) error { - ctx := context.Background() - return p.queries.DeleteSnakeGame(ctx, id) -} - -type ChatPersister struct { - queries *repository.Queries -} - -func NewChatPersister(q *repository.Queries) *ChatPersister { - return &ChatPersister{queries: q} -} - -func (p *ChatPersister) SaveChatMessage(gameID string, msg game.ChatMessage) error { - return p.queries.CreateChatMessage(context.Background(), repository.CreateChatMessageParams{ - GameID: gameID, - Nickname: msg.Nickname, - Color: int64(msg.Color), - Message: msg.Message, - CreatedAt: msg.Time, - }) -} - -func (p *ChatPersister) LoadChatMessages(gameID string) ([]game.ChatMessage, error) { - rows, err := p.queries.GetChatMessages(context.Background(), gameID) - if err != nil { - return nil, err - } - msgs := make([]game.ChatMessage, len(rows)) - for i, r := range rows { - msgs[i] = game.ChatMessage{ - Nickname: r.Nickname, - Color: int(r.Color), - Message: r.Message, - Time: r.CreatedAt, - } - } - // Query returns newest-first; reverse to oldest-first for display - slices.Reverse(msgs) - return msgs, nil -} diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go index 4df8425..e0b107b 100644 --- a/features/c4game/handlers.go +++ b/features/c4game/handlers.go @@ -1,8 +1,10 @@ package c4game import ( + "context" "encoding/json" "net/http" + "slices" "strconv" "sync" "time" @@ -10,14 +12,14 @@ import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" - "github.com/ryanhamamura/c4/db" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/features/c4game/components" "github.com/ryanhamamura/c4/features/c4game/pages" "github.com/ryanhamamura/c4/game" "github.com/starfederation/datastar-go/datastar" ) -func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc { +func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") @@ -73,8 +75,8 @@ func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPer // Player is in the game — render full game page g := gi.GetGame() - uiMsgs, _ := chatPersister.LoadChatMessages(gameID) - msgs := uiChatToComponents(uiMsgs) + chatMsgs := loadChatMessages(queries, gameID) + msgs := chatToComponents(chatMsgs) if err := pages.GamePage(g, myColor, msgs).Render(r.Context(), w); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -82,7 +84,7 @@ func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, chatPer } } -func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc { +func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") @@ -103,9 +105,9 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio sse := datastar.NewSSE(w, r) // Load initial chat messages - uiMsgs, _ := chatPersister.LoadChatMessages(gameID) + chatMsgs := loadChatMessages(queries, gameID) var chatMu sync.Mutex - chatMessages := uiChatToComponents(uiMsgs) + chatMessages := chatToComponents(chatMsgs) // Send initial render of all components sendGameComponents(sse, gi, myColor, chatMessages, &chatMu, gameID) @@ -203,7 +205,7 @@ func HandleDropPiece(store *game.GameStore, sessions *scs.SessionManager) http.H } } -func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, chatPersister *db.ChatPersister) http.HandlerFunc { +func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "game_id") @@ -254,7 +256,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionM Message: signals.ChatMsg, Time: time.Now().UnixMilli(), } - chatPersister.SaveChatMessage(gameID, cm) + saveChatMessage(queries, gameID, cm) data, err := json.Marshal(cm) if err != nil { @@ -353,10 +355,40 @@ func sendGameComponents(sse *datastar.ServerSentEventGenerator, gi *game.GameIns sse.PatchElementTempl(components.Chat(msgs, gameID), datastar.WithSelectorID("c4-chat")) //nolint:errcheck } -// uiChatToComponents converts ui.C4ChatMessage slice to components.ChatMessage slice. -func uiChatToComponents(uiMsgs []game.ChatMessage) []components.ChatMessage { - msgs := make([]components.ChatMessage, len(uiMsgs)) - for i, m := range uiMsgs { +// Chat persistence helpers — inlined from the former ChatPersister. + +func saveChatMessage(queries *repository.Queries, gameID string, msg game.ChatMessage) { + queries.CreateChatMessage(context.Background(), repository.CreateChatMessageParams{ //nolint:errcheck + GameID: gameID, + Nickname: msg.Nickname, + Color: int64(msg.Color), + Message: msg.Message, + CreatedAt: msg.Time, + }) +} + +func loadChatMessages(queries *repository.Queries, gameID string) []game.ChatMessage { + rows, err := queries.GetChatMessages(context.Background(), gameID) + if err != nil { + return nil + } + msgs := make([]game.ChatMessage, len(rows)) + for i, r := range rows { + msgs[i] = game.ChatMessage{ + Nickname: r.Nickname, + Color: int(r.Color), + Message: r.Message, + Time: r.CreatedAt, + } + } + // DB returns newest-first; reverse for display + slices.Reverse(msgs) + return msgs +} + +func chatToComponents(chatMsgs []game.ChatMessage) []components.ChatMessage { + msgs := make([]components.ChatMessage, len(chatMsgs)) + for i, m := range chatMsgs { msgs[i] = components.ChatMessage{ Nickname: m.Nickname, Color: m.Color, diff --git a/features/c4game/routes.go b/features/c4game/routes.go index 99783f6..917e5a0 100644 --- a/features/c4game/routes.go +++ b/features/c4game/routes.go @@ -4,7 +4,7 @@ import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" - "github.com/ryanhamamura/c4/db" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" ) @@ -13,14 +13,14 @@ func SetupRoutes( store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, - chatPersister *db.ChatPersister, + queries *repository.Queries, ) error { - router.Get("/game/{game_id}", HandleGamePage(store, sessions, chatPersister)) - router.Get("/game/{game_id}/events", HandleGameEvents(store, nc, sessions, chatPersister)) + router.Get("/game/{game_id}", HandleGamePage(store, sessions, queries)) + router.Get("/game/{game_id}/events", HandleGameEvents(store, nc, sessions, queries)) router.Route("/api/game/{game_id}", func(r chi.Router) { r.Post("/drop", HandleDropPiece(store, sessions)) - r.Post("/chat", HandleSendChat(store, nc, sessions, chatPersister)) + r.Post("/chat", HandleSendChat(store, nc, sessions, queries)) r.Post("/join", HandleSetNickname(store, sessions)) r.Post("/rematch", HandleRematch(store, sessions)) }) diff --git a/game/persist.go b/game/persist.go new file mode 100644 index 0000000..2322adc --- /dev/null +++ b/game/persist.go @@ -0,0 +1,157 @@ +package game + +import ( + "context" + "database/sql" + + "github.com/ryanhamamura/c4/db/repository" +) + +// Persistence methods on GameStore (used during Get to hydrate from DB). + +func (gs *GameStore) saveGame(g *Game) error { + ctx := context.Background() + + _, err := gs.queries.GetGame(ctx, g.ID) + if err == sql.ErrNoRows { + _, err = gs.queries.CreateGame(ctx, repository.CreateGameParams{ + ID: g.ID, + Board: g.BoardToJSON(), + CurrentTurn: int64(g.CurrentTurn), + Status: int64(g.Status), + }) + return err + } + if err != nil { + return err + } + + return gs.queries.UpdateGame(ctx, updateGameParams(g)) +} + +func (gs *GameStore) loadGame(id string) (*Game, error) { + row, err := gs.queries.GetGame(context.Background(), id) + if err != nil { + return nil, err + } + return gameFromRow(row) +} + +func (gs *GameStore) loadGamePlayers(id string) ([]*Player, error) { + rows, err := gs.queries.GetGamePlayers(context.Background(), id) + if err != nil { + return nil, err + } + return playersFromRows(rows), nil +} + +// Persistence methods on GameInstance (used during gameplay mutations). + +func (gi *GameInstance) saveGame(g *Game) error { + ctx := context.Background() + + _, err := gi.queries.GetGame(ctx, g.ID) + if err == sql.ErrNoRows { + _, err = gi.queries.CreateGame(ctx, repository.CreateGameParams{ + ID: g.ID, + Board: g.BoardToJSON(), + CurrentTurn: int64(g.CurrentTurn), + Status: int64(g.Status), + }) + return err + } + if err != nil { + return err + } + + return gi.queries.UpdateGame(ctx, updateGameParams(g)) +} + +func (gi *GameInstance) saveGamePlayer(gameID string, player *Player, slot int) error { + var userID, guestPlayerID sql.NullString + if player.UserID != nil { + userID = sql.NullString{String: *player.UserID, Valid: true} + } else { + guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} + } + + return gi.queries.CreateGamePlayer(context.Background(), repository.CreateGamePlayerParams{ + GameID: gameID, + UserID: userID, + GuestPlayerID: guestPlayerID, + Nickname: player.Nickname, + Color: int64(player.Color), + Slot: int64(slot), + }) +} + +// Shared helpers for domain ↔ DB mapping. + +func updateGameParams(g *Game) repository.UpdateGameParams { + var winnerUserID sql.NullString + if g.Winner != nil && g.Winner.UserID != nil { + winnerUserID = sql.NullString{String: *g.Winner.UserID, Valid: true} + } + + var winningCells sql.NullString + if wc := g.WinningCellsToJSON(); wc != "" { + winningCells = sql.NullString{String: wc, Valid: true} + } + + var rematchGameID sql.NullString + if g.RematchGameID != nil { + rematchGameID = sql.NullString{String: *g.RematchGameID, Valid: true} + } + + return repository.UpdateGameParams{ + Board: g.BoardToJSON(), + CurrentTurn: int64(g.CurrentTurn), + Status: int64(g.Status), + WinnerUserID: winnerUserID, + WinningCells: winningCells, + RematchGameID: rematchGameID, + ID: g.ID, + } +} + +func gameFromRow(row repository.Game) (*Game, error) { + g := &Game{ + ID: row.ID, + CurrentTurn: int(row.CurrentTurn), + Status: GameStatus(row.Status), + } + + if err := g.BoardFromJSON(row.Board); err != nil { + return nil, err + } + + if row.WinningCells.Valid { + g.WinningCellsFromJSON(row.WinningCells.String) + } + + if row.RematchGameID.Valid { + g.RematchGameID = &row.RematchGameID.String + } + + return g, nil +} + +func playersFromRows(rows []repository.GamePlayer) []*Player { + players := make([]*Player, 0, len(rows)) + for _, row := range rows { + player := &Player{ + Nickname: row.Nickname, + Color: int(row.Color), + } + + if row.UserID.Valid { + player.UserID = &row.UserID.String + player.ID = PlayerID(row.UserID.String) + } else if row.GuestPlayerID.Valid { + player.ID = PlayerID(row.GuestPlayerID.String) + } + + players = append(players, player) + } + return players +} diff --git a/game/store.go b/game/store.go index 9e7aae8..e1c9c40 100644 --- a/game/store.go +++ b/game/store.go @@ -1,40 +1,32 @@ package game import ( + "context" "crypto/rand" "encoding/hex" "sync" + + "github.com/ryanhamamura/c4/db/repository" ) type PlayerSession struct { Player *Player } -type Persister interface { - SaveGame(g *Game) error - LoadGame(id string) (*Game, error) - SaveGamePlayer(gameID string, player *Player, slot int) error - LoadGamePlayers(gameID string) ([]*Player, error) - DeleteGame(id string) error -} - type GameStore struct { - games map[string]*GameInstance - gamesMu sync.RWMutex - persister Persister + games map[string]*GameInstance + gamesMu sync.RWMutex + queries *repository.Queries notifyFunc func(gameID string) } -func NewGameStore() *GameStore { +func NewGameStore(queries *repository.Queries) *GameStore { return &GameStore{ - games: make(map[string]*GameInstance), + games: make(map[string]*GameInstance), + queries: queries, } } -func (gs *GameStore) SetPersister(p Persister) { - gs.persister = p -} - func (gs *GameStore) SetNotifyFunc(f func(gameID string)) { gs.notifyFunc = f } @@ -50,14 +42,14 @@ func (gs *GameStore) makeNotify(gameID string) func() { func (gs *GameStore) Create() *GameInstance { id := GenerateID(4) gi := NewGameInstance(id) - gi.persister = gs.persister + gi.queries = gs.queries gi.notify = gs.makeNotify(id) gs.gamesMu.Lock() gs.games[id] = gi gs.gamesMu.Unlock() - if gs.persister != nil { - gs.persister.SaveGame(gi.game) + if gs.queries != nil { + gs.saveGame(gi.game) } return gi @@ -72,28 +64,28 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) { return gi, true } - if gs.persister == nil { + if gs.queries == nil { return nil, false } - game, err := gs.persister.LoadGame(id) - if err != nil || game == nil { + g, err := gs.loadGame(id) + if err != nil || g == nil { return nil, false } - players, _ := gs.persister.LoadGamePlayers(id) + players, _ := gs.loadGamePlayers(id) for _, p := range players { if p.Color == 1 { - game.Players[0] = p + g.Players[0] = p } else if p.Color == 2 { - game.Players[1] = p + g.Players[1] = p } } gi = &GameInstance{ - game: game, - persister: gs.persister, - notify: gs.makeNotify(id), + game: g, + queries: gs.queries, + notify: gs.makeNotify(id), } gs.gamesMu.Lock() @@ -108,8 +100,8 @@ func (gs *GameStore) Delete(id string) error { delete(gs.games, id) gs.gamesMu.Unlock() - if gs.persister != nil { - return gs.persister.DeleteGame(id) + if gs.queries != nil { + return gs.queries.DeleteGame(context.Background(), id) } return nil } @@ -121,10 +113,10 @@ func GenerateID(size int) string { } type GameInstance struct { - game *Game - gameMu sync.RWMutex - notify func() - persister Persister + game *Game + gameMu sync.RWMutex + notify func() + queries *repository.Queries } func NewGameInstance(id string) *GameInstance { @@ -158,9 +150,9 @@ func (gi *GameInstance) Join(ps *PlayerSession) bool { return false } - if gi.persister != nil { - gi.persister.SaveGamePlayer(gi.game.ID, ps.Player, slot) - gi.persister.SaveGame(gi.game) + if gi.queries != nil { + gi.saveGamePlayer(gi.game.ID, ps.Player, slot) + gi.saveGame(gi.game) } gi.notify() @@ -196,8 +188,8 @@ func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance { newID := newGI.ID() gi.game.RematchGameID = &newID - if gi.persister != nil { - if err := gi.persister.SaveGame(gi.game); err != nil { + if gi.queries != nil { + if err := gi.saveGame(gi.game); err != nil { gs.Delete(newID) gi.game.RematchGameID = nil return nil @@ -230,8 +222,8 @@ func (gi *GameInstance) DropPiece(col int, playerColor int) bool { gi.game.SwitchTurn() } - if gi.persister != nil { - gi.persister.SaveGame(gi.game) + if gi.queries != nil { + gi.saveGame(gi.game) } gi.notify() diff --git a/main.go b/main.go index bb437a1..5cae7e6 100644 --- a/main.go +++ b/main.go @@ -71,20 +71,16 @@ func run(ctx context.Context) error { defer cleanupNATS() // Game stores - store := game.NewGameStore() - store.SetPersister(db.NewGamePersister(queries)) + store := game.NewGameStore(queries) store.SetNotifyFunc(func(gameID string) { nc.Publish("game."+gameID, nil) //nolint:errcheck // best-effort notification }) - snakeStore := snake.NewSnakeStore() - snakeStore.SetPersister(db.NewSnakePersister(queries)) + snakeStore := snake.NewSnakeStore(queries) snakeStore.SetNotifyFunc(func(gameID string) { nc.Publish("snake."+gameID, nil) //nolint:errcheck // best-effort notification }) - chatPersister := db.NewChatPersister(queries) - // Router logger := log.Logger r := chi.NewMux() @@ -94,7 +90,7 @@ func run(ctx context.Context) error { sessionManager.LoadAndSave, ) - if err := router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore, chatPersister, assets); err != nil { + if err := router.SetupRoutes(r, queries, sessionManager, nc, store, snakeStore, assets); err != nil { return fmt.Errorf("setting up routes: %w", err) } diff --git a/router/router.go b/router/router.go index a42a09d..c6198fc 100644 --- a/router/router.go +++ b/router/router.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/ryanhamamura/c4/config" - "github.com/ryanhamamura/c4/db" "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/features/auth" "github.com/ryanhamamura/c4/features/c4game" @@ -30,7 +29,6 @@ func SetupRoutes( nc *nats.Conn, store *game.GameStore, snakeStore *snake.SnakeStore, - chatPersister *db.ChatPersister, assets embed.FS, ) error { // Static assets @@ -44,7 +42,7 @@ func SetupRoutes( auth.SetupRoutes(router, queries, sessions) lobby.SetupRoutes(router, queries, sessions, store, snakeStore) - c4game.SetupRoutes(router, store, nc, sessions, chatPersister) + c4game.SetupRoutes(router, store, nc, sessions, queries) snakegame.SetupRoutes(router, snakeStore, nc, sessions) return nil diff --git a/snake/loop.go b/snake/loop.go index 9d46c09..fb1a839 100644 --- a/snake/loop.go +++ b/snake/loop.go @@ -61,16 +61,16 @@ func (si *SnakeGameInstance) countdownPhase() { si.initGame() si.game.Status = StatusInProgress - if si.persister != nil { - si.persister.SaveSnakeGame(si.game) + if si.queries != nil { + si.saveSnakeGame(si.game) } si.gameMu.Unlock() si.notify() return } - if si.persister != nil { - si.persister.SaveSnakeGame(si.game) + if si.queries != nil { + si.saveSnakeGame(si.game) } si.gameMu.Unlock() si.notify() @@ -123,8 +123,8 @@ func (si *SnakeGameInstance) gamePhase() { // Inactivity timeout if time.Since(lastInput) > inactivityLimit { si.game.Status = StatusFinished - if si.persister != nil { - si.persister.SaveSnakeGame(si.game) + if si.queries != nil { + si.saveSnakeGame(si.game) } si.gameMu.Unlock() si.notify() @@ -195,8 +195,8 @@ func (si *SnakeGameInstance) gamePhase() { si.game.Status = StatusFinished } - if si.persister != nil { - si.persister.SaveSnakeGame(si.game) + if si.queries != nil { + si.saveSnakeGame(si.game) } si.gameMu.Unlock() diff --git a/snake/persist.go b/snake/persist.go new file mode 100644 index 0000000..6ef51d0 --- /dev/null +++ b/snake/persist.go @@ -0,0 +1,186 @@ +package snake + +import ( + "context" + "database/sql" + + "github.com/ryanhamamura/c4/db/repository" +) + +// Persistence methods on SnakeStore (used during Get to hydrate from DB). + +func (ss *SnakeStore) saveSnakeGame(sg *SnakeGame) error { + ctx := context.Background() + + boardJSON := "{}" + if sg.State != nil { + boardJSON = sg.State.ToJSON() + } + + var gridWidth, gridHeight sql.NullInt64 + if sg.State != nil { + gridWidth = sql.NullInt64{Int64: int64(sg.State.Width), Valid: true} + gridHeight = sql.NullInt64{Int64: int64(sg.State.Height), Valid: true} + } + + _, err := ss.queries.GetSnakeGame(ctx, sg.ID) + if err == sql.ErrNoRows { + _, err = ss.queries.CreateSnakeGame(ctx, repository.CreateSnakeGameParams{ + ID: sg.ID, + Board: boardJSON, + Status: int64(sg.Status), + GridWidth: gridWidth, + GridHeight: gridHeight, + GameMode: int64(sg.Mode), + SnakeSpeed: int64(sg.Speed), + }) + return err + } + if err != nil { + return err + } + + return ss.queries.UpdateSnakeGame(ctx, updateSnakeGameParams(sg, boardJSON)) +} + +func (ss *SnakeStore) loadSnakeGame(id string) (*SnakeGame, error) { + row, err := ss.queries.GetSnakeGame(context.Background(), id) + if err != nil { + return nil, err + } + return snakeGameFromRow(row) +} + +func (ss *SnakeStore) loadSnakePlayers(id string) ([]*Player, error) { + rows, err := ss.queries.GetSnakePlayers(context.Background(), id) + if err != nil { + return nil, err + } + return snakePlayersFromRows(rows), nil +} + +// Persistence methods on SnakeGameInstance (used during gameplay mutations). + +func (si *SnakeGameInstance) saveSnakeGame(sg *SnakeGame) error { + ctx := context.Background() + + boardJSON := "{}" + if sg.State != nil { + boardJSON = sg.State.ToJSON() + } + + var gridWidth, gridHeight sql.NullInt64 + if sg.State != nil { + gridWidth = sql.NullInt64{Int64: int64(sg.State.Width), Valid: true} + gridHeight = sql.NullInt64{Int64: int64(sg.State.Height), Valid: true} + } + + _, err := si.queries.GetSnakeGame(ctx, sg.ID) + if err == sql.ErrNoRows { + _, err = si.queries.CreateSnakeGame(ctx, repository.CreateSnakeGameParams{ + ID: sg.ID, + Board: boardJSON, + Status: int64(sg.Status), + GridWidth: gridWidth, + GridHeight: gridHeight, + GameMode: int64(sg.Mode), + SnakeSpeed: int64(sg.Speed), + }) + return err + } + if err != nil { + return err + } + + return si.queries.UpdateSnakeGame(ctx, updateSnakeGameParams(sg, boardJSON)) +} + +func (si *SnakeGameInstance) saveSnakePlayer(gameID string, player *Player) error { + var userID, guestPlayerID sql.NullString + if player.UserID != nil { + userID = sql.NullString{String: *player.UserID, Valid: true} + } else { + guestPlayerID = sql.NullString{String: string(player.ID), Valid: true} + } + + return si.queries.CreateSnakePlayer(context.Background(), repository.CreateSnakePlayerParams{ + GameID: gameID, + UserID: userID, + GuestPlayerID: guestPlayerID, + Nickname: player.Nickname, + Color: int64(player.Slot + 1), + Slot: int64(player.Slot), + }) +} + +// Shared helpers for domain ↔ DB mapping. + +func updateSnakeGameParams(sg *SnakeGame, boardJSON string) repository.UpdateSnakeGameParams { + var winnerUserID sql.NullString + if sg.Winner != nil && sg.Winner.UserID != nil { + winnerUserID = sql.NullString{String: *sg.Winner.UserID, Valid: true} + } + + var rematchGameID sql.NullString + if sg.RematchGameID != nil { + rematchGameID = sql.NullString{String: *sg.RematchGameID, Valid: true} + } + + return repository.UpdateSnakeGameParams{ + Board: boardJSON, + Status: int64(sg.Status), + WinnerUserID: winnerUserID, + RematchGameID: rematchGameID, + Score: int64(sg.Score), + ID: sg.ID, + } +} + +func snakeGameFromRow(row repository.Game) (*SnakeGame, error) { + state, err := GameStateFromJSON(row.Board) + if err != nil { + state = &GameState{} + } + if row.GridWidth.Valid { + state.Width = int(row.GridWidth.Int64) + } + if row.GridHeight.Valid { + state.Height = int(row.GridHeight.Int64) + } + + sg := &SnakeGame{ + ID: row.ID, + State: state, + Players: make([]*Player, 8), + Status: Status(row.Status), + Mode: GameMode(row.GameMode), + Score: int(row.Score), + Speed: int(row.SnakeSpeed), + } + + if row.RematchGameID.Valid { + sg.RematchGameID = &row.RematchGameID.String + } + + return sg, nil +} + +func snakePlayersFromRows(rows []repository.GamePlayer) []*Player { + players := make([]*Player, 0, len(rows)) + for _, row := range rows { + player := &Player{ + Nickname: row.Nickname, + Slot: int(row.Slot), + } + + if row.UserID.Valid { + player.UserID = &row.UserID.String + player.ID = PlayerID(row.UserID.String) + } else if row.GuestPlayerID.Valid { + player.ID = PlayerID(row.GuestPlayerID.String) + } + + players = append(players, player) + } + return players +} diff --git a/snake/store.go b/snake/store.go index fac2574..ef1749e 100644 --- a/snake/store.go +++ b/snake/store.go @@ -1,36 +1,28 @@ package snake import ( + "context" "crypto/rand" "encoding/hex" "sync" + + "github.com/ryanhamamura/c4/db/repository" ) -type Persister interface { - SaveSnakeGame(sg *SnakeGame) error - LoadSnakeGame(id string) (*SnakeGame, error) - SaveSnakePlayer(gameID string, player *Player) error - LoadSnakePlayers(gameID string) ([]*Player, error) - DeleteSnakeGame(id string) error -} - type SnakeStore struct { - games map[string]*SnakeGameInstance - gamesMu sync.RWMutex - persister Persister + games map[string]*SnakeGameInstance + gamesMu sync.RWMutex + queries *repository.Queries notifyFunc func(gameID string) } -func NewSnakeStore() *SnakeStore { +func NewSnakeStore(queries *repository.Queries) *SnakeStore { return &SnakeStore{ - games: make(map[string]*SnakeGameInstance), + games: make(map[string]*SnakeGameInstance), + queries: queries, } } -func (ss *SnakeStore) SetPersister(p Persister) { - ss.persister = p -} - func (ss *SnakeStore) SetNotifyFunc(f func(gameID string)) { ss.notifyFunc = f } @@ -60,18 +52,18 @@ func (ss *SnakeStore) Create(width, height int, mode GameMode, speed int) *Snake Speed: speed, } si := &SnakeGameInstance{ - game: sg, - notify: ss.makeNotify(id), - persister: ss.persister, - store: ss, + game: sg, + notify: ss.makeNotify(id), + queries: ss.queries, + store: ss, } ss.gamesMu.Lock() ss.games[id] = si ss.gamesMu.Unlock() - if ss.persister != nil { - ss.persister.SaveSnakeGame(sg) + if ss.queries != nil { + ss.saveSnakeGame(sg) } return si @@ -86,16 +78,16 @@ func (ss *SnakeStore) Get(id string) (*SnakeGameInstance, bool) { return si, true } - if ss.persister == nil { + if ss.queries == nil { return nil, false } - sg, err := ss.persister.LoadSnakeGame(id) + sg, err := ss.loadSnakeGame(id) if err != nil || sg == nil { return nil, false } - players, _ := ss.persister.LoadSnakePlayers(id) + players, _ := ss.loadSnakePlayers(id) if sg.Players == nil { sg.Players = make([]*Player, 8) } @@ -106,10 +98,10 @@ func (ss *SnakeStore) Get(id string) (*SnakeGameInstance, bool) { } si = &SnakeGameInstance{ - game: sg, - notify: ss.makeNotify(id), - persister: ss.persister, - store: ss, + game: sg, + notify: ss.makeNotify(id), + queries: ss.queries, + store: ss, } ss.gamesMu.Lock() @@ -129,8 +121,8 @@ func (ss *SnakeStore) Delete(id string) error { si.Stop() } - if ss.persister != nil { - return ss.persister.DeleteSnakeGame(id) + if ss.queries != nil { + return ss.queries.DeleteSnakeGame(context.Background(), id) } return nil } @@ -158,14 +150,14 @@ func (ss *SnakeStore) ActiveGames() []*SnakeGame { } type SnakeGameInstance struct { - game *SnakeGame - gameMu sync.RWMutex + game *SnakeGame + gameMu sync.RWMutex pendingDirQueue [8][]Direction // queued directions per slot (max 3) - notify func() - persister Persister - store *SnakeStore - stopCh chan struct{} - loopOnce sync.Once + notify func() + queries *repository.Queries + store *SnakeStore + stopCh chan struct{} + loopOnce sync.Once } func (si *SnakeGameInstance) ID() string { @@ -214,9 +206,9 @@ func (si *SnakeGameInstance) Join(player *Player) bool { player.Slot = slot si.game.Players[slot] = player - if si.persister != nil { - si.persister.SaveSnakePlayer(si.game.ID, player) - si.persister.SaveSnakeGame(si.game) + if si.queries != nil { + si.saveSnakePlayer(si.game.ID, player) + si.saveSnakeGame(si.game) } si.notify() @@ -301,8 +293,8 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance { } si.game.RematchGameID = &newID - if si.persister != nil { - si.persister.SaveSnakeGame(si.game) + if si.queries != nil { + si.saveSnakeGame(si.game) } si.gameMu.Unlock() From afd8a3e9d0350121f05f05a2a0b615fd1036130c Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:38:21 -1000 Subject: [PATCH 4/9] fix: resolve all linting errors and add SSE compression - Add brotli compression (level 5) to long-lived SSE event streams (HandleGameEvents, HandleSnakeEvents) to reduce wire payload - Fix all errcheck violations with nolint annotations for best-effort calls - Fix goimports: separate stdlib, third-party, and local import groups - Fix staticcheck: add package comments, use tagged switch - Zero lint issues remaining --- auth/auth.go | 1 + features/auth/handlers.go | 3 ++- features/auth/routes.go | 2 ++ features/c4game/handlers.go | 13 ++++++++----- features/c4game/routes.go | 2 ++ features/lobby/handlers.go | 10 +++++----- features/lobby/routes.go | 1 + features/snakegame/handlers.go | 11 +++++++---- features/snakegame/routes.go | 2 ++ game/logic.go | 1 + game/persist.go | 2 +- game/store.go | 17 +++++++++-------- nats/nats.go | 2 +- router/router.go | 16 ++++++++++++---- snake/logic.go | 1 + snake/loop.go | 8 ++++---- snake/store.go | 10 +++++----- snake/types.go | 6 +++--- 18 files changed, 67 insertions(+), 41 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 17b03d2..16a2d45 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,3 +1,4 @@ +// Package auth provides password hashing and verification using bcrypt. package auth import ( diff --git a/features/auth/handlers.go b/features/auth/handlers.go index 1212e0b..e39e59e 100644 --- a/features/auth/handlers.go +++ b/features/auth/handlers.go @@ -6,10 +6,11 @@ import ( "github.com/alexedwards/scs/v2" "github.com/google/uuid" + "github.com/starfederation/datastar-go/datastar" + "github.com/ryanhamamura/c4/auth" "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/features/auth/pages" - "github.com/starfederation/datastar-go/datastar" ) type LoginSignals struct { diff --git a/features/auth/routes.go b/features/auth/routes.go index 98ad6df..f028547 100644 --- a/features/auth/routes.go +++ b/features/auth/routes.go @@ -1,8 +1,10 @@ +// Package auth handles user authentication routes and handlers. package auth import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" + "github.com/ryanhamamura/c4/db/repository" ) diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go index e0b107b..2e90886 100644 --- a/features/c4game/handlers.go +++ b/features/c4game/handlers.go @@ -12,11 +12,12 @@ import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/starfederation/datastar-go/datastar" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/features/c4game/components" "github.com/ryanhamamura/c4/features/c4game/pages" "github.com/ryanhamamura/c4/game" - "github.com/starfederation/datastar-go/datastar" ) func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { @@ -102,7 +103,9 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio myColor := gi.GetPlayerColor(playerID) - sse := datastar.NewSSE(w, r) + sse := datastar.NewSSE(w, r, datastar.WithCompression( + datastar.WithBrotli(datastar.WithBrotliLevel(5)), + )) // Load initial chat messages chatMsgs := loadChatMessages(queries, gameID) @@ -118,7 +121,7 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio if err != nil { return } - defer gameSub.Unsubscribe() + defer gameSub.Unsubscribe() //nolint:errcheck // Subscribe to chat messages chatCh := make(chan *nats.Msg, 64) @@ -126,7 +129,7 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio if err != nil { return } - defer chatSub.Unsubscribe() + defer chatSub.Unsubscribe() //nolint:errcheck ctx := r.Context() for { @@ -263,7 +266,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionM datastar.NewSSE(w, r) return } - nc.Publish("game.chat."+gameID, data) + nc.Publish("game.chat."+gameID, data) //nolint:errcheck // Clear the chat input sse := datastar.NewSSE(w, r) diff --git a/features/c4game/routes.go b/features/c4game/routes.go index 917e5a0..1bba4b2 100644 --- a/features/c4game/routes.go +++ b/features/c4game/routes.go @@ -1,9 +1,11 @@ +// Package c4game handles Connect 4 game routes, SSE event streaming, and chat. package c4game import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/db/repository" "github.com/ryanhamamura/c4/game" ) diff --git a/features/lobby/handlers.go b/features/lobby/handlers.go index 4c8a86a..a200703 100644 --- a/features/lobby/handlers.go +++ b/features/lobby/handlers.go @@ -91,7 +91,7 @@ func HandleCreateGame(store *game.GameStore, sessions *scs.SessionManager) http. gi := store.Create() sse := datastar.NewSSE(w, r) - sse.ExecuteScript(fmt.Sprintf("window.location.href='/game/%s'", gi.ID())) + sse.ExecuteScript(fmt.Sprintf("window.location.href='/game/%s'", gi.ID())) //nolint:errcheck } } @@ -104,10 +104,10 @@ func HandleDeleteGame(store *game.GameStore, sessions *scs.SessionManager) http. return } - store.Delete(gameID) + store.Delete(gameID) //nolint:errcheck sse := datastar.NewSSE(w, r) - sse.ExecuteScript("window.location.href='/'") + sse.ExecuteScript("window.location.href='/'") //nolint:errcheck } } @@ -150,7 +150,7 @@ func HandleCreateSnakeGame(snakeStore *snake.SnakeStore, sessions *scs.SessionMa si := snakeStore.Create(preset.Width, preset.Height, mode, speed) sse := datastar.NewSSE(w, r) - sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) + sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) //nolint:errcheck } } @@ -163,6 +163,6 @@ func HandleLogout(sessions *scs.SessionManager) http.HandlerFunc { } sse := datastar.NewSSE(w, r) - sse.ExecuteScript("window.location.href='/'") + sse.ExecuteScript("window.location.href='/'") //nolint:errcheck } } diff --git a/features/lobby/routes.go b/features/lobby/routes.go index 016eb75..7f05748 100644 --- a/features/lobby/routes.go +++ b/features/lobby/routes.go @@ -1,3 +1,4 @@ +// Package lobby handles the game lobby page, game creation, and navigation. package lobby import ( diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go index 8fe9675..d97453c 100644 --- a/features/snakegame/handlers.go +++ b/features/snakegame/handlers.go @@ -9,11 +9,12 @@ import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/starfederation/datastar-go/datastar" + "github.com/ryanhamamura/c4/features/snakegame/components" "github.com/ryanhamamura/c4/features/snakegame/pages" "github.com/ryanhamamura/c4/game" "github.com/ryanhamamura/c4/snake" - "github.com/starfederation/datastar-go/datastar" ) func getPlayerID(sessions *scs.SessionManager, r *http.Request) snake.PlayerID { @@ -90,7 +91,9 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc playerID := getPlayerID(sessions, r) mySlot := si.GetPlayerSlot(playerID) - sse := datastar.NewSSE(w, r) + sse := datastar.NewSSE(w, r, datastar.WithCompression( + datastar.WithBrotli(datastar.WithBrotliLevel(5)), + )) // Send initial render sg := si.GetGame() @@ -110,7 +113,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc if err != nil { return } - defer gameSub.Unsubscribe() + defer gameSub.Unsubscribe() //nolint:errcheck // Chat subscription (multiplayer only) var chatCh chan *nats.Msg @@ -124,7 +127,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc if err != nil { return } - defer chatSub.Unsubscribe() + defer chatSub.Unsubscribe() //nolint:errcheck } ctx := r.Context() diff --git a/features/snakegame/routes.go b/features/snakegame/routes.go index c4c8334..d1ccc23 100644 --- a/features/snakegame/routes.go +++ b/features/snakegame/routes.go @@ -1,9 +1,11 @@ +// Package snakegame handles snake game routes, SSE event streaming, and chat. package snakegame import ( "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/nats-io/nats.go" + "github.com/ryanhamamura/c4/snake" ) diff --git a/game/logic.go b/game/logic.go index e8429cd..7a4d167 100644 --- a/game/logic.go +++ b/game/logic.go @@ -1,3 +1,4 @@ +// Package game implements Connect 4 game logic, state management, and persistence. package game // DropPiece attempts to drop a piece in the given column. diff --git a/game/persist.go b/game/persist.go index 2322adc..fb05716 100644 --- a/game/persist.go +++ b/game/persist.go @@ -126,7 +126,7 @@ func gameFromRow(row repository.Game) (*Game, error) { } if row.WinningCells.Valid { - g.WinningCellsFromJSON(row.WinningCells.String) + _ = g.WinningCellsFromJSON(row.WinningCells.String) } if row.RematchGameID.Valid { diff --git a/game/store.go b/game/store.go index e1c9c40..e56e750 100644 --- a/game/store.go +++ b/game/store.go @@ -49,7 +49,7 @@ func (gs *GameStore) Create() *GameInstance { gs.gamesMu.Unlock() if gs.queries != nil { - gs.saveGame(gi.game) + gs.saveGame(gi.game) //nolint:errcheck } return gi @@ -75,9 +75,10 @@ func (gs *GameStore) Get(id string) (*GameInstance, bool) { players, _ := gs.loadGamePlayers(id) for _, p := range players { - if p.Color == 1 { + switch p.Color { + case 1: g.Players[0] = p - } else if p.Color == 2 { + case 2: g.Players[1] = p } } @@ -108,7 +109,7 @@ func (gs *GameStore) Delete(id string) error { func GenerateID(size int) string { b := make([]byte, size) - rand.Read(b) + _, _ = rand.Read(b) return hex.EncodeToString(b) } @@ -151,8 +152,8 @@ func (gi *GameInstance) Join(ps *PlayerSession) bool { } if gi.queries != nil { - gi.saveGamePlayer(gi.game.ID, ps.Player, slot) - gi.saveGame(gi.game) + gi.saveGamePlayer(gi.game.ID, ps.Player, slot) //nolint:errcheck + gi.saveGame(gi.game) //nolint:errcheck } gi.notify() @@ -190,7 +191,7 @@ func (gi *GameInstance) CreateRematch(gs *GameStore) *GameInstance { if gi.queries != nil { if err := gi.saveGame(gi.game); err != nil { - gs.Delete(newID) + gs.Delete(newID) //nolint:errcheck gi.game.RematchGameID = nil return nil } @@ -223,7 +224,7 @@ func (gi *GameInstance) DropPiece(col int, playerColor int) bool { } if gi.queries != nil { - gi.saveGame(gi.game) + gi.saveGame(gi.game) //nolint:errcheck } gi.notify() diff --git a/nats/nats.go b/nats/nats.go index 9eeabc9..86bee3d 100644 --- a/nats/nats.go +++ b/nats/nats.go @@ -40,7 +40,7 @@ func SetupNATS(ctx context.Context) (*nats.Conn, func(), error) { cleanup := func() { nc.Close() - ns.Close() + ns.Close() //nolint:errcheck } return nc, cleanup, nil diff --git a/router/router.go b/router/router.go index c6198fc..f4bded2 100644 --- a/router/router.go +++ b/router/router.go @@ -40,10 +40,18 @@ func SetupRoutes( setupReload(router) } - auth.SetupRoutes(router, queries, sessions) - lobby.SetupRoutes(router, queries, sessions, store, snakeStore) - c4game.SetupRoutes(router, store, nc, sessions, queries) - snakegame.SetupRoutes(router, snakeStore, nc, sessions) + if err := auth.SetupRoutes(router, queries, sessions); err != nil { + return err + } + if err := lobby.SetupRoutes(router, queries, sessions, store, snakeStore); err != nil { + return err + } + if err := c4game.SetupRoutes(router, store, nc, sessions, queries); err != nil { + return err + } + if err := snakegame.SetupRoutes(router, snakeStore, nc, sessions); err != nil { + return err + } return nil } diff --git a/snake/logic.go b/snake/logic.go index c48c6ef..a26a5a6 100644 --- a/snake/logic.go +++ b/snake/logic.go @@ -1,3 +1,4 @@ +// Package snake implements snake game logic, state management, and persistence. package snake import "math/rand" diff --git a/snake/loop.go b/snake/loop.go index fb1a839..cf44f84 100644 --- a/snake/loop.go +++ b/snake/loop.go @@ -62,7 +62,7 @@ func (si *SnakeGameInstance) countdownPhase() { si.game.Status = StatusInProgress if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() si.notify() @@ -70,7 +70,7 @@ func (si *SnakeGameInstance) countdownPhase() { } if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() si.notify() @@ -124,7 +124,7 @@ func (si *SnakeGameInstance) gamePhase() { if time.Since(lastInput) > inactivityLimit { si.game.Status = StatusFinished if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() si.notify() @@ -196,7 +196,7 @@ func (si *SnakeGameInstance) gamePhase() { } if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() diff --git a/snake/store.go b/snake/store.go index ef1749e..670b9da 100644 --- a/snake/store.go +++ b/snake/store.go @@ -63,7 +63,7 @@ func (ss *SnakeStore) Create(width, height int, mode GameMode, speed int) *Snake ss.gamesMu.Unlock() if ss.queries != nil { - ss.saveSnakeGame(sg) + ss.saveSnakeGame(sg) //nolint:errcheck } return si @@ -207,8 +207,8 @@ func (si *SnakeGameInstance) Join(player *Player) bool { si.game.Players[slot] = player if si.queries != nil { - si.saveSnakePlayer(si.game.ID, player) - si.saveSnakeGame(si.game) + si.saveSnakePlayer(si.game.ID, player) //nolint:errcheck + si.saveSnakeGame(si.game) //nolint:errcheck } si.notify() @@ -294,7 +294,7 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance { si.game.RematchGameID = &newID if si.queries != nil { - si.saveSnakeGame(si.game) + si.saveSnakeGame(si.game) //nolint:errcheck } si.gameMu.Unlock() @@ -304,6 +304,6 @@ func (si *SnakeGameInstance) CreateRematch() *SnakeGameInstance { func generateID(size int) string { b := make([]byte, size) - rand.Read(b) + _, _ = rand.Read(b) return hex.EncodeToString(b) } diff --git a/snake/types.go b/snake/types.go index 5bb21a9..8272765 100644 --- a/snake/types.go +++ b/snake/types.go @@ -100,7 +100,7 @@ type SnakeGame struct { Speed int // cells per second } -// Speed presets +// SpeedPreset defines a named speed option for the snake game. type SpeedPreset struct { Name string Speed int @@ -129,7 +129,7 @@ func (sg *SnakeGame) PlayerCount() int { return count } -// Grid presets +// GridPreset defines a named grid size option for the snake game. type GridPreset struct { Name string Width int @@ -163,7 +163,7 @@ func (sg *SnakeGame) snapshot() *SnakeGame { return &cp } -// Snake colors (hex values for CSS) +// SnakeColors are hex color values for CSS, indexed by player slot. var SnakeColors = []string{ "#00b894", // 1: Green "#e17055", // 2: Orange From 67d4dba37f4334fd5c7ffe9d462eb1a1a2ac48c4 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:40:02 -1000 Subject: [PATCH 5/9] fix: suppress gosec G117 on auth form signal structs --- features/auth/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/auth/handlers.go b/features/auth/handlers.go index e39e59e..dc34675 100644 --- a/features/auth/handlers.go +++ b/features/auth/handlers.go @@ -15,12 +15,12 @@ import ( type LoginSignals struct { Username string `json:"username"` - Password string `json:"password"` + Password string `json:"password"` //nolint:gosec // form input, not stored } type RegisterSignals struct { Username string `json:"username"` - Password string `json:"password"` + Password string `json:"password"` //nolint:gosec // form input, not stored Confirm string `json:"confirm"` } From fcc6b70e846139d33484b7cf227409b673714fb6 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:42:10 -1000 Subject: [PATCH 6/9] fix: warn when .env file is missing instead of silently ignoring --- config/config.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 6c09cd9..31774f2 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ package config import ( + "log/slog" "os" "sync" @@ -46,7 +47,9 @@ func getEnv(key, fallback string) string { } func loadBase() *Config { - godotenv.Load() //nolint:errcheck // .env file is optional + if err := godotenv.Load(); err != nil { + slog.Warn("no .env file found, using environment variables and defaults") + } return &Config{ Host: getEnv("HOST", "0.0.0.0"), From 5120eef776f7cfac210cd2f581274d65f531e98e Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 13:19:03 -1000 Subject: [PATCH 7/9] refactor: streamline routes to RESTful naming conventions Remove /api/ prefix and consolidate route groups: - /api/lobby/* -> /games, /snake, /logout (top-level) - /game/{game_id} + /api/game/{game_id}/* -> /games/{id}/* - /snake/{game_id} + /api/snake/{game_id}/* -> /snake/{id}/* - /api/auth/* -> /auth/* - Standardize snake join page to use return_url= (was return=) --- features/auth/pages/login_templ.go | 8 ++++---- features/auth/pages/register_templ.go | 8 ++++---- features/auth/routes.go | 4 ++-- features/c4game/components/board_templ.go | 4 ++-- features/c4game/components/chat_templ.go | 8 ++++---- features/c4game/components/status_templ.go | 16 +++++++-------- features/c4game/handlers.go | 16 +++++++-------- features/c4game/pages/game_templ.go | 12 +++++------ features/c4game/routes.go | 7 +++---- features/lobby/components/gamelist_templ.go | 8 ++++---- features/lobby/handlers.go | 2 +- features/lobby/pages/lobby_templ.go | 20 +++++++++---------- features/lobby/routes.go | 10 ++++------ features/snakegame/components/chat_templ.go | 8 ++++---- features/snakegame/components/status_templ.go | 4 ++-- features/snakegame/handlers.go | 12 +++++------ features/snakegame/pages/game_templ.go | 14 ++++++------- features/snakegame/routes.go | 7 +++---- 18 files changed, 82 insertions(+), 86 deletions(-) diff --git a/features/auth/pages/login_templ.go b/features/auth/pages/login_templ.go index 115ea27..3640508 100644 --- a/features/auth/pages/login_templ.go +++ b/features/auth/pages/login_templ.go @@ -51,9 +51,9 @@ func LoginPage() templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/auth/login")) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/auth/login")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/login.templ`, Line: 34, Col: 69} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/login.templ`, Line: 34, Col: 65} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -64,9 +64,9 @@ func LoginPage() templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/auth/login")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/auth/login")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/login.templ`, Line: 40, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/login.templ`, Line: 40, Col: 52} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/features/auth/pages/register_templ.go b/features/auth/pages/register_templ.go index 89efec3..61c8f21 100644 --- a/features/auth/pages/register_templ.go +++ b/features/auth/pages/register_templ.go @@ -51,9 +51,9 @@ func RegisterPage() templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/auth/register")) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/auth/register")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/register.templ`, Line: 43, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/register.templ`, Line: 43, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -64,9 +64,9 @@ func RegisterPage() templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/auth/register")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/auth/register")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/register.templ`, Line: 49, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/auth/pages/register.templ`, Line: 49, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { diff --git a/features/auth/routes.go b/features/auth/routes.go index f028547..ac50938 100644 --- a/features/auth/routes.go +++ b/features/auth/routes.go @@ -11,8 +11,8 @@ import ( func SetupRoutes(router chi.Router, queries *repository.Queries, sessions *scs.SessionManager) error { router.Get("/login", HandleLoginPage()) router.Get("/register", HandleRegisterPage()) - router.Post("/api/auth/login", HandleLogin(queries, sessions)) - router.Post("/api/auth/register", HandleRegister(queries, sessions)) + router.Post("/auth/login", HandleLogin(queries, sessions)) + router.Post("/auth/register", HandleRegister(queries, sessions)) return nil } diff --git a/features/c4game/components/board_templ.go b/features/c4game/components/board_templ.go index 92f27b5..7c638be 100644 --- a/features/c4game/components/board_templ.go +++ b/features/c4game/components/board_templ.go @@ -81,9 +81,9 @@ func column(g *game.Game, colIdx int, myColor int) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/game/%s/drop?col=%d", g.ID, colIdx)) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/games/%s/drop?col=%d", g.ID, colIdx)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/board.templ`, Line: 22, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/board.templ`, Line: 22, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/features/c4game/components/chat_templ.go b/features/c4game/components/chat_templ.go index c6fa087..b70da12 100644 --- a/features/c4game/components/chat_templ.go +++ b/features/c4game/components/chat_templ.go @@ -105,9 +105,9 @@ func Chat(messages []ChatMessage, gameID string) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/game/%s/chat", gameID)) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/games/%s/chat", gameID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 40, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 40, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -118,9 +118,9 @@ func Chat(messages []ChatMessage, gameID string) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/game/%s/chat", gameID)) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/games/%s/chat", gameID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 44, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/chat.templ`, Line: 44, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { diff --git a/features/c4game/components/status_templ.go b/features/c4game/components/status_templ.go index ba58ad9..c87c799 100644 --- a/features/c4game/components/status_templ.go +++ b/features/c4game/components/status_templ.go @@ -77,9 +77,9 @@ func StatusBanner(g *game.Game, myColor int) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 templ.SafeURL - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/game/" + *g.RematchGameID)) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/games/" + *g.RematchGameID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 16, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 16, Col: 55} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -95,9 +95,9 @@ func StatusBanner(g *game.Game, myColor int) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/game/%s/rematch", g.ID)) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/games/%s/rematch", g.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 24, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 24, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -217,9 +217,9 @@ func InviteLink(gameID string) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(config.Global.AppURL + "/game/" + gameID) + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(config.Global.AppURL + "/games/" + gameID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 48, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/components/status.templ`, Line: 48, Col: 46} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -229,7 +229,7 @@ func InviteLink(gameID string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(config.Global.AppURL+"/game/"+gameID)) + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, copyToClipboard(config.Global.AppURL+"/games/"+gameID)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -237,7 +237,7 @@ func InviteLink(gameID string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 templ.ComponentScript = copyToClipboard(config.Global.AppURL + "/game/" + gameID) + var templ_7745c5c3_Var13 templ.ComponentScript = copyToClipboard(config.Global.AppURL + "/games/" + gameID) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13.Call) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err diff --git a/features/c4game/handlers.go b/features/c4game/handlers.go index 2e90886..6d24ed2 100644 --- a/features/c4game/handlers.go +++ b/features/c4game/handlers.go @@ -22,7 +22,7 @@ import ( func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") gi, exists := store.Get(gameID) if !exists { @@ -87,7 +87,7 @@ func HandleGamePage(store *game.GameStore, sessions *scs.SessionManager, queries func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") gi, exists := store.Get(gameID) if !exists { @@ -173,7 +173,7 @@ func HandleGameEvents(store *game.GameStore, nc *nats.Conn, sessions *scs.Sessio func HandleDropPiece(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") gi, exists := store.Get(gameID) if !exists { @@ -210,7 +210,7 @@ func HandleDropPiece(store *game.GameStore, sessions *scs.SessionManager) http.H func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") gi, exists := store.Get(gameID) if !exists { @@ -276,7 +276,7 @@ func HandleSendChat(store *game.GameStore, nc *nats.Conn, sessions *scs.SessionM func HandleSetNickname(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") gi, exists := store.Get(gameID) if !exists { @@ -319,13 +319,13 @@ func HandleSetNickname(store *game.GameStore, sessions *scs.SessionManager) http } sse := datastar.NewSSE(w, r) - sse.Redirect("/game/" + gameID) //nolint:errcheck + sse.Redirect("/games/" + gameID) //nolint:errcheck } } func HandleRematch(store *game.GameStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") gi, exists := store.Get(gameID) if !exists { @@ -337,7 +337,7 @@ func HandleRematch(store *game.GameStore, sessions *scs.SessionManager) http.Han newGI := gi.CreateRematch(store) sse := datastar.NewSSE(w, r) if newGI != nil { - sse.Redirectf("/game/%s", newGI.ID()) //nolint:errcheck + sse.Redirectf("/games/%s", newGI.ID()) //nolint:errcheck } } } diff --git a/features/c4game/pages/game_templ.go b/features/c4game/pages/game_templ.go index 3662c23..903e9a3 100644 --- a/features/c4game/pages/game_templ.go +++ b/features/c4game/pages/game_templ.go @@ -54,9 +54,9 @@ func GamePage(g *game.Game, myColor int, messages []components.ChatMessage) temp return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.GetSSE("/game/%s/events", g.ID)) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.GetSSE("/games/%s/events", g.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/pages/game.templ`, Line: 16, Col: 55} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/c4game/pages/game.templ`, Line: 16, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -152,9 +152,9 @@ func JoinPage(gameID string) templ.Component { } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Err = sharedcomponents.GameJoinPrompt( - "/login?return_url=/game/"+gameID, - "/register?return_url=/game/"+gameID, - "/game/"+gameID, + "/login?return_url=/games/"+gameID, + "/register?return_url=/games/"+gameID, + "/games/"+gameID, ).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -202,7 +202,7 @@ func NicknamePage(gameID string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = sharedcomponents.NicknamePrompt("/api/game/"+gameID+"/join").Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = sharedcomponents.NicknamePrompt("/games/"+gameID+"/join").Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/c4game/routes.go b/features/c4game/routes.go index 1bba4b2..4cc4641 100644 --- a/features/c4game/routes.go +++ b/features/c4game/routes.go @@ -17,10 +17,9 @@ func SetupRoutes( sessions *scs.SessionManager, queries *repository.Queries, ) error { - router.Get("/game/{game_id}", HandleGamePage(store, sessions, queries)) - router.Get("/game/{game_id}/events", HandleGameEvents(store, nc, sessions, queries)) - - router.Route("/api/game/{game_id}", func(r chi.Router) { + router.Route("/games/{id}", func(r chi.Router) { + r.Get("/", HandleGamePage(store, sessions, queries)) + r.Get("/events", HandleGameEvents(store, nc, sessions, queries)) r.Post("/drop", HandleDropPiece(store, sessions)) r.Post("/chat", HandleSendChat(store, nc, sessions, queries)) r.Post("/join", HandleSetNickname(store, sessions)) diff --git a/features/lobby/components/gamelist_templ.go b/features/lobby/components/gamelist_templ.go index c8539c8..4157630 100644 --- a/features/lobby/components/gamelist_templ.go +++ b/features/lobby/components/gamelist_templ.go @@ -83,9 +83,9 @@ func gameListEntry(g GameListItem) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 templ.SafeURL - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/game/" + g.ID)) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/games/" + g.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 27, Col: 40} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 27, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -157,9 +157,9 @@ func gameListEntry(g GameListItem) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.DeleteSSE("/api/lobby/game/%s", g.ID)) + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.DeleteSSE("/games/%s", g.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 41, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/components/gamelist.templ`, Line: 41, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { diff --git a/features/lobby/handlers.go b/features/lobby/handlers.go index a200703..8144543 100644 --- a/features/lobby/handlers.go +++ b/features/lobby/handlers.go @@ -91,7 +91,7 @@ func HandleCreateGame(store *game.GameStore, sessions *scs.SessionManager) http. gi := store.Create() sse := datastar.NewSSE(w, r) - sse.ExecuteScript(fmt.Sprintf("window.location.href='/game/%s'", gi.ID())) //nolint:errcheck + sse.ExecuteScript(fmt.Sprintf("window.location.href='/games/%s'", gi.ID())) //nolint:errcheck } } diff --git a/features/lobby/pages/lobby_templ.go b/features/lobby/pages/lobby_templ.go index bc1aeb2..ffae496 100644 --- a/features/lobby/pages/lobby_templ.go +++ b/features/lobby/pages/lobby_templ.go @@ -74,9 +74,9 @@ func LobbyPage(data LobbyData) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/lobby/logout")) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/logout")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 26, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 26, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -113,9 +113,9 @@ func LobbyPage(data LobbyData) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/lobby/create-game")) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/games")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 76, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 76, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -126,9 +126,9 @@ func LobbyPage(data LobbyData) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/lobby/create-game")) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/games")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 82, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 82, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -201,9 +201,9 @@ func LobbyPage(data LobbyData) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/lobby/create-snake?mode=solo&preset=%d", i)) + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/snake?mode=solo&preset=%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 129, Col: 90} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 129, Col: 73} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -237,9 +237,9 @@ func LobbyPage(data LobbyData) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/lobby/create-snake?mode=multi&preset=%d", i)) + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/snake?mode=multi&preset=%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 144, Col: 91} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/lobby/pages/lobby.templ`, Line: 144, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { diff --git a/features/lobby/routes.go b/features/lobby/routes.go index 7f05748..5dde496 100644 --- a/features/lobby/routes.go +++ b/features/lobby/routes.go @@ -19,12 +19,10 @@ func SetupRoutes( ) error { router.Get("/", HandleLobbyPage(queries, sessions, snakeStore)) - router.Route("/api/lobby", func(r chi.Router) { - r.Post("/create-game", HandleCreateGame(store, sessions)) - r.Delete("/game/{id}", HandleDeleteGame(store, sessions)) - r.Post("/create-snake", HandleCreateSnakeGame(snakeStore, sessions)) - r.Post("/logout", HandleLogout(sessions)) - }) + router.Post("/games", HandleCreateGame(store, sessions)) + router.Delete("/games/{id}", HandleDeleteGame(store, sessions)) + router.Post("/snake", HandleCreateSnakeGame(snakeStore, sessions)) + router.Post("/logout", HandleLogout(sessions)) return nil } diff --git a/features/snakegame/components/chat_templ.go b/features/snakegame/components/chat_templ.go index 396e9a5..db1946e 100644 --- a/features/snakegame/components/chat_templ.go +++ b/features/snakegame/components/chat_templ.go @@ -97,9 +97,9 @@ func Chat(messages []ChatMessage, gameID string) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/snake/%s/chat", gameID)) + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/snake/%s/chat", gameID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 36, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 36, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -110,9 +110,9 @@ func Chat(messages []ChatMessage, gameID string) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/snake/%s/chat", gameID)) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/snake/%s/chat", gameID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 40, Col: 66} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/chat.templ`, Line: 40, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { diff --git a/features/snakegame/components/status_templ.go b/features/snakegame/components/status_templ.go index b1733c0..57416eb 100644 --- a/features/snakegame/components/status_templ.go +++ b/features/snakegame/components/status_templ.go @@ -266,9 +266,9 @@ func rematchOrJoin(sg *snake.SnakeGame, gameID string) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/api/snake/%s/rematch", gameID)) + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(datastar.PostSSE("/snake/%s/rematch", gameID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 82, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/snakegame/components/status.templ`, Line: 82, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { diff --git a/features/snakegame/handlers.go b/features/snakegame/handlers.go index d97453c..03292a6 100644 --- a/features/snakegame/handlers.go +++ b/features/snakegame/handlers.go @@ -32,7 +32,7 @@ func getPlayerID(sessions *scs.SessionManager, r *http.Request) snake.PlayerID { func HandleSnakePage(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") si, ok := snakeStore.Get(gameID) if !ok { http.Redirect(w, r, "/", http.StatusSeeOther) @@ -81,7 +81,7 @@ func HandleSnakePage(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") si, ok := snakeStore.Get(gameID) if !ok { http.Error(w, "game not found", http.StatusNotFound) @@ -189,7 +189,7 @@ func HandleSnakeEvents(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *sc func HandleSetDirection(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") si, ok := snakeStore.Get(gameID) if !ok { http.Error(w, "game not found", http.StatusNotFound) @@ -221,7 +221,7 @@ type chatSignals struct { func HandleSendChat(snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") si, ok := snakeStore.Get(gameID) if !ok { http.Error(w, "game not found", http.StatusNotFound) @@ -268,7 +268,7 @@ type nicknameSignals struct { func HandleSetNickname(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") si, ok := snakeStore.Get(gameID) if !ok { http.Error(w, "game not found", http.StatusNotFound) @@ -308,7 +308,7 @@ func HandleSetNickname(snakeStore *snake.SnakeStore, sessions *scs.SessionManage func HandleRematch(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - gameID := chi.URLParam(r, "game_id") + gameID := chi.URLParam(r, "id") si, ok := snakeStore.Get(gameID) if !ok { http.Error(w, "game not found", http.StatusNotFound) diff --git a/features/snakegame/pages/game_templ.go b/features/snakegame/pages/game_templ.go index a7023cb..123aaab 100644 --- a/features/snakegame/pages/game_templ.go +++ b/features/snakegame/pages/game_templ.go @@ -27,10 +27,10 @@ func keydownScript(gameID string) string { "else if(k==='s'||k==='ArrowDown'){evt.preventDefault();%s}"+ "else if(k==='a'||k==='ArrowLeft'){evt.preventDefault();%s}"+ "else if(k==='d'||k==='ArrowRight'){evt.preventDefault();%s}", - datastar.PostSSE("/api/snake/%s/dir?d=0", gameID), - datastar.PostSSE("/api/snake/%s/dir?d=1", gameID), - datastar.PostSSE("/api/snake/%s/dir?d=2", gameID), - datastar.PostSSE("/api/snake/%s/dir?d=3", gameID), + datastar.PostSSE("/snake/%s/dir?d=0", gameID), + datastar.PostSSE("/snake/%s/dir?d=1", gameID), + datastar.PostSSE("/snake/%s/dir?d=2", gameID), + datastar.PostSSE("/snake/%s/dir?d=3", gameID), ) } @@ -210,8 +210,8 @@ func JoinPage(gameID string) templ.Component { } ctx = templ.InitializeContext(ctx) templ_7745c5c3_Err = components.GameJoinPrompt( - fmt.Sprintf("/login?return=/snake/%s", gameID), - fmt.Sprintf("/register?return=/snake/%s", gameID), + fmt.Sprintf("/login?return_url=/snake/%s", gameID), + fmt.Sprintf("/register?return_url=/snake/%s", gameID), fmt.Sprintf("/snake/%s", gameID), ).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { @@ -260,7 +260,7 @@ func NicknamePage(gameID string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = components.NicknamePrompt(fmt.Sprintf("/api/snake/%s/join", gameID)).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = components.NicknamePrompt(fmt.Sprintf("/snake/%s/join", gameID)).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/snakegame/routes.go b/features/snakegame/routes.go index d1ccc23..4cdceec 100644 --- a/features/snakegame/routes.go +++ b/features/snakegame/routes.go @@ -10,10 +10,9 @@ import ( ) func SetupRoutes(router chi.Router, snakeStore *snake.SnakeStore, nc *nats.Conn, sessions *scs.SessionManager) error { - router.Get("/snake/{game_id}", HandleSnakePage(snakeStore, sessions)) - router.Get("/snake/{game_id}/events", HandleSnakeEvents(snakeStore, nc, sessions)) - - router.Route("/api/snake/{game_id}", func(r chi.Router) { + router.Route("/snake/{id}", func(r chi.Router) { + r.Get("/", HandleSnakePage(snakeStore, sessions)) + r.Get("/events", HandleSnakeEvents(snakeStore, nc, sessions)) r.Post("/dir", HandleSetDirection(snakeStore, sessions)) r.Post("/chat", HandleSendChat(snakeStore, nc, sessions)) r.Post("/join", HandleSetNickname(snakeStore, sessions)) From 587f392b8b0b8c8ed0895afa47e9912a29bbdeb1 Mon Sep 17 00:00:00 2001 From: Ryan Hamamura <58859899+ryanhamamura@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:34:39 -1000 Subject: [PATCH 8/9] fix: serve datastar locally and clean up session/route config Replace CDN-hosted datastar beta.11 with local v1.0.0-RC.7 to fix client-side expression incompatibilities with the Go SDK. Also fix quoted CSS class keys in data-class expressions, harden session cookie settings (named cookie, Secure flag), simplify SetupRoutes to not return an error, and regenerate templ output. --- assets/js/datastar.js | 9 +++++++++ features/auth/pages/login_templ.go | 10 +++++----- features/auth/pages/register_templ.go | 10 +++++----- features/auth/routes.go | 4 +--- features/c4game/routes.go | 4 +--- features/common/layouts/base_templ.go | 2 +- features/lobby/pages/lobby_templ.go | 8 ++++---- features/lobby/routes.go | 4 +--- features/snakegame/routes.go | 4 +--- main.go | 4 +--- router/router.go | 20 +++++--------------- sessions/sessions.go | 3 ++- 12 files changed, 36 insertions(+), 46 deletions(-) create mode 100644 assets/js/datastar.js diff --git a/assets/js/datastar.js b/assets/js/datastar.js new file mode 100644 index 0000000..bfb85cc --- /dev/null +++ b/assets/js/datastar.js @@ -0,0 +1,9 @@ +// Datastar v1.0.0-RC.7 +var at=/🖕JS_DS🚀/.source,je=at.slice(0,5),Ge=at.slice(4),q="datastar-fetch",Z="datastar-signal-patch";var C=Object.hasOwn??Object.prototype.hasOwnProperty.call;var U=e=>e!==null&&typeof e=="object"&&(Object.getPrototypeOf(e)===Object.prototype||Object.getPrototypeOf(e)===null),ct=e=>{for(let t in e)if(C(e,t))return!1;return!0},Y=(e,t)=>{for(let n in e){let r=e[n];U(r)||Array.isArray(r)?Y(r,t):e[n]=t(r)}},Me=e=>{let t={};for(let[n,r]of e){let s=n.split("."),o=s.pop(),i=s.reduce((a,c)=>a[c]??={},t);i[o]=r}return t};var xe=[],Be=[],Oe=0,Le=0,We=0,Ue,j,Ne=0,M=()=>{Oe++},x=()=>{--Oe||(ft(),J())},F=e=>{Ue=j,j=e},P=()=>{j=Ue,Ue=void 0},pe=e=>Ut.bind(0,{previousValue:e,t:e,e:1}),Je=Symbol("computed"),ke=e=>{let t=Jt.bind(0,{e:17,getter:e});return t[Je]=1,t},S=e=>{let t={d:e,e:2};j&&ze(t,j),F(t),M();try{t.d()}finally{x(),P()}return gt.bind(0,t)},ft=()=>{for(;Le"getter"in e?dt(e):mt(e,e.t),dt=e=>{F(e),ht(e);try{let t=e.t;return t!==(e.t=e.getter(t))}finally{P(),yt(e)}},mt=(e,t)=>(e.e=1,e.previousValue!==(e.previousValue=t)),Ke=e=>{let t=e.e;if(!(t&64)){e.e=t|64;let n=e.r;n?Ke(n.o):Be[We++]=e}},pt=(e,t)=>{if(t&16||t&32&&bt(e.s,e)){F(e),ht(e),M();try{e.d()}finally{x(),P(),yt(e)}return}t&32&&(e.e=t&-33);let n=e.s;for(;n;){let r=n.c,s=r.e;s&64&&pt(r,r.e=s&-65),n=n.i}},Ut=(e,...t)=>{if(t.length){if(e.t!==(e.t=t[0])){e.e=17;let r=e.r;return r&&(Kt(r),Oe||ft()),!0}return!1}let n=e.t;if(e.e&16&&mt(e,n)){let r=e.r;r&&Pe(r)}return j&&ze(e,j),n},Jt=e=>{let t=e.e;if(t&16||t&32&&bt(e.s,e)){if(dt(e)){let n=e.r;n&&Pe(n)}}else t&32&&(e.e=t&-33);return j&&ze(e,j),e.t},gt=e=>{let t=e.s;for(;t;)t=Fe(t,e);let n=e.r;n&&Fe(n),e.e=0},ze=(e,t)=>{let n=t.a;if(n&&n.c===e)return;let r=n?n.i:t.s;if(r&&r.c===e){r.m=Ne,t.a=r;return}let s=e.p;if(s&&s.m===Ne&&s.o===t)return;let o=t.a=e.p={m:Ne,c:e,o:t,l:n,i:r,u:s};r&&(r.l=o),n?n.i=o:t.s=o,s?s.n=o:e.r=o},Fe=(e,t=e.o)=>{let n=e.c,r=e.l,s=e.i,o=e.n,i=e.u;if(s?s.l=r:t.a=r,r?r.i=s:t.s=s,o?o.u=i:n.p=i,i)i.n=o;else if(!(n.r=o))if("getter"in n){let a=n.s;if(a){n.e=17;do a=Fe(a,n);while(a)}}else"previousValue"in n||gt(n);return s},Kt=e=>{let t=e.n,n;e:for(;;){let r=e.o,s=r.e;if(s&60?s&12?s&4?!(s&48)&&zt(e,r)?(r.e=s|40,s&=1):s=0:r.e=s&-9|32:s=0:r.e=s|32,s&2&&Ke(r),s&1){let o=r.r;if(o){let i=(e=o).n;i&&(n={t,f:n},t=i);continue}}if(e=t){t=e.n;continue}for(;n;)if(e=n.t,n=n.f,e){t=e.n;continue e}break}},ht=e=>{Ne++,e.a=void 0,e.e=e.e&-57|4},yt=e=>{let t=e.a,n=t?t.i:e.s;for(;n;)n=Fe(n,e);e.e&=-5},bt=(e,t)=>{let n,r=0,s=!1;e:for(;;){let o=e.c,i=o.e;if(t.e&16)s=!0;else if((i&17)===17){if(lt(o)){let a=o.r;a.n&&Pe(a),s=!0}}else if((i&33)===33){(e.n||e.u)&&(n={t:e,f:n}),e=o.s,t=o,++r;continue}if(!s){let a=e.i;if(a){e=a;continue}}for(;r--;){let a=t.r,c=a.n;if(c?(e=n.t,n=n.f):e=a,s){if(lt(t)){c&&Pe(a),t=e.o;continue}s=!1}else t.e&=-33;if(t=e.o,e.i){e=e.i;continue e}}return s}},Pe=e=>{do{let t=e.o,n=t.e;(n&48)===32&&(t.e=n|16,n&2&&Ke(t))}while(e=e.n)},zt=(e,t)=>{let n=t.a;for(;n;){if(n===e)return!0;n=n.l}return!1},oe=e=>{let t=X,n=e.split(".");for(let r of n){if(t==null||!C(t,r))return;t=t[r]}return t},Ce=(e,t="")=>{let n=Array.isArray(e);if(n||U(e)){let r=n?[]:{};for(let o in e)r[o]=pe(Ce(e[o],`${t+o}.`));let s=pe(0);return new Proxy(r,{get(o,i){if(!(i==="toJSON"&&!C(r,i)))return n&&i in Array.prototype?(s(),r[i]):typeof i=="symbol"?r[i]:((!C(r,i)||r[i]()==null)&&(r[i]=pe(""),J(t+i,""),s(s()+1)),r[i]())},set(o,i,a){let c=t+i;if(n&&i==="length"){let l=r[i]-a;if(r[i]=a,l>0){let u={};for(let d=a;d{if(e!==void 0&&t!==void 0&&xe.push([e,t]),!Oe&&xe.length){let n=Me(xe);xe.length=0,document.dispatchEvent(new CustomEvent(Z,{detail:n}))}},O=(e,{ifMissing:t}={})=>{M();for(let n in e)e[n]==null?t||delete X[n]:vt(e[n],n,X,"",t);x()},T=(e,t)=>O(Me(e),t),vt=(e,t,n,r,s)=>{if(U(e)){C(n,t)&&(U(n[t])||Array.isArray(n[t]))||(n[t]={});for(let o in e)e[o]==null?s||delete n[t][o]:vt(e[o],o,n[t],`${r+t}.`,s)}else s&&C(n,t)||(n[t]=e)},ut=e=>typeof e=="string"?RegExp(e.replace(/^\/|\/$/g,"")):e,_=({include:e=/.*/,exclude:t=/(?!)/}={},n=X)=>{let r=ut(e),s=ut(t),o=[],i=[[n,""]];for(;i.length;){let[a,c]=i.pop();for(let l in a){let u=c+l;U(a[l])?i.push([a[l],`${u}.`]):r.test(u)&&!s.test(u)&&o.push([u,oe(u)])}}return Me(o)},X=Ce({});var K=e=>e instanceof HTMLElement||e instanceof SVGElement||e instanceof MathMLElement;var ge=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").replace(/([a-z])([0-9]+)/gi,"$1-$2").replace(/([0-9]+)([a-z])/gi,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase();var Et=e=>ge(e).replace(/-/g,"_");var ae=e=>{try{return JSON.parse(e)}catch{return Function(`return (${e})`)()}},St={camel:e=>e.replace(/-[a-z]/g,t=>t[1].toUpperCase()),snake:e=>e.replace(/-/g,"_"),pascal:e=>e[0].toUpperCase()+St.camel(e.slice(1))},L=(e,t,n="camel")=>{for(let r of t.get("case")||[n])e=St[r]?.(e)||e;return e},G=e=>`data-${e}`;var Qt="https://data-star.dev/errors",he=(e,t,n={})=>{Object.assign(n,e);let r=new Error,s=Et(t),o=new URLSearchParams({metadata:JSON.stringify(n)}).toString(),i=JSON.stringify(n,null,2);return r.message=`${t} +More info: ${Qt}/${s}?${o} +Context: ${i}`,r},ye=new Map,Qe=new Map,At=new Map,Rt=new Proxy({},{get:(e,t)=>ye.get(t)?.apply,has:(e,t)=>ye.has(t),ownKeys:()=>Reflect.ownKeys(ye),set:()=>!1,deleteProperty:()=>!1}),be=new Map,He=[],Ze=new Set,Zt=new WeakSet,p=e=>{He.push(e),He.length===1&&setTimeout(()=>{for(let t of He)Ze.add(t.name),Qe.set(t.name,t);He.length=0,nn(),Ze.clear()})},k=e=>{ye.set(e.name,e)};document.addEventListener(q,e=>{let t=At.get(e.detail.type);t&&t.apply({error:he.bind(0,{plugin:{type:"watcher",name:t.name},element:{id:e.target.id,tag:e.target.tagName}})},e.detail.argsRaw)});var ve=e=>{At.set(e.name,e)},Tt=e=>{for(let t of e){let n=be.get(t);if(n&&be.delete(t))for(let r of n.values())for(let s of r.values())s()}},wt=G("ignore"),Yt=`[${wt}]`,Mt=e=>e.hasAttribute(`${wt}__self`)||!!e.closest(Yt),_e=(e,t)=>{for(let n of e)if(!Mt(n))for(let r in n.dataset)xt(n,r.replace(/[A-Z]/g,"-$&").toLowerCase(),n.dataset[r],t)},Xt=e=>{for(let{target:t,type:n,attributeName:r,addedNodes:s,removedNodes:o}of e)if(n==="childList"){for(let i of o)K(i)&&(Tt([i]),Tt(i.querySelectorAll("*")));for(let i of s)K(i)&&(_e([i]),_e(i.querySelectorAll("*")))}else if(n==="attributes"&&r.startsWith("data-")&&K(t)&&!Mt(t)){let i=r.slice(5),a=t.getAttribute(r);if(a===null){let c=be.get(t);if(c){let l=c.get(i);if(l){for(let u of l.values())u();c.delete(i)}}}else xt(t,i,a)}},en=new MutationObserver(Xt),tn=e=>{let[t,...n]=e.split("__"),[r,s]=t.split(/:(.+)/),o=new Map;for(let i of n){let[a,...c]=i.split(".");o.set(a,new Set(c))}return{pluginName:r,key:s,mods:o}};var nn=(e=document.documentElement,t=!0)=>{K(e)&&_e([e],!0),_e(e.querySelectorAll("*"),!0),t&&(en.observe(e,{subtree:!0,childList:!0,attributes:!0}),Zt.add(e))},xt=(e,t,n,r)=>{{let s=t,{pluginName:o,key:i,mods:a}=tn(s),c=Qe.get(o);if((!r||Ze.has(o))&&c){let l={el:e,rawKey:s,mods:a,error:he.bind(0,{plugin:{type:"attribute",name:c.name},element:{id:e.id,tag:e.tagName},expression:{rawKey:s,key:i,value:n}}),key:i,value:n,loadedPluginNames:{actions:new Set(ye.keys()),attributes:new Set(Qe.keys())},rx:void 0},u=c.requirement&&(typeof c.requirement=="string"?c.requirement:c.requirement.key)||"allowed",d=c.requirement&&(typeof c.requirement=="string"?c.requirement:c.requirement.value)||"allowed",h=i!=null&&i!=="",f=n!=null&&n!=="";if(h){if(u==="denied")throw l.error("KeyNotAllowed")}else if(u==="must")throw l.error("KeyRequired");if(f){if(d==="denied")throw l.error("ValueNotAllowed")}else if(d==="must")throw l.error("ValueRequired");if(u==="exclusive"||d==="exclusive"){if(h&&f)throw l.error("KeyAndValueProvided");if(!h&&!f)throw l.error("KeyOrValueRequired")}let m=new Map;if(f){let v;l.rx=(...A)=>(v||(v=rn(n,{returnsValue:c.returnsValue,argNames:c.argNames,cleanups:m})),v(e,...A))}let y=c.apply(l);y&&m.set("attribute",y);let b=be.get(e);if(b){let v=b.get(s);if(v)for(let A of v.values())A()}else b=new Map,be.set(e,b);b.set(s,m)}}},rn=(e,{returnsValue:t=!1,argNames:n=[],cleanups:r=new Map}={})=>{let s="";if(t){let c=/(\/(\\\/|[^/])*\/|"(\\"|[^"])*"|'(\\'|[^'])*'|`(\\`|[^`])*`|\(\s*((function)\s*\(\s*\)|(\(\s*\))\s*=>)\s*(?:\{[\s\S]*?\}|[^;){]*)\s*\)\s*\(\s*\)|[^;])+/gm,l=e.trim().match(c);if(l){let u=l.length-1,d=l[u].trim();d.startsWith("return")||(l[u]=`return (${d});`),s=l.join(`; +`)}}else s=e.trim();let o=new Map,i=RegExp(`(?:${je})(.*?)(?:${Ge})`,"gm"),a=0;for(let c of s.matchAll(i)){let l=c[1],u=`__escaped${a++}`;o.set(u,l),s=s.replace(je+l+Ge,u)}s=s.replace(/\$\['([a-zA-Z_$\d][\w$]*)'\]/g,"$$$1").replace(/\$([a-zA-Z_\d]\w*(?:[.-]\w+)*)/g,(c,l)=>l.split(".").reduce((u,d)=>`${u}['${d}']`,"$")),s=s.replaceAll(/@([A-Za-z_$][\w$]*)\(/g,'__action("$1",evt,');for(let[c,l]of o)s=s.replace(c,l);try{let c=Function("el","$","__action","evt",...n,s);return(l,...u)=>{let d=(h,f,...m)=>{let y=he.bind(0,{plugin:{type:"action",name:h},element:{id:l.id,tag:l.tagName},expression:{fnContent:s,value:e}}),b=Rt[h];if(b)return b({el:l,evt:f,error:y,cleanups:r},...m);throw y("UndefinedAction")};try{return c(l,X,d,void 0,...u)}catch(h){throw console.error(h),he({element:{id:l.id,tag:l.tagName},expression:{fnContent:s,value:e},error:h.message},"ExecuteExpression")}}}catch(c){throw console.error(c),he({expression:{fnContent:s,value:e},error:c.message},"GenerateExpression")}};k({name:"peek",apply(e,t){F();try{return t()}finally{P()}}});k({name:"setAll",apply(e,t,n){F();let r=_(n);Y(r,()=>t),O(r),P()}});k({name:"toggleAll",apply(e,t){F();let n=_(t);Y(n,r=>!r),O(n),P()}});var Ee=(e,t,n=!0)=>k({name:e,apply:async({el:r,evt:s,error:o,cleanups:i},a,{selector:c,headers:l,contentType:u="json",filterSignals:{include:d=/.*/,exclude:h=/(^|\.)_/}={},openWhenHidden:f=n,payload:m,requestCancellation:y="auto",retry:b="auto",retryInterval:v=1e3,retryScaler:A=2,retryMaxWaitMs:I=3e4,retryMaxCount:ne=10}={})=>{let de=y instanceof AbortController?y:new AbortController;y==="auto"&&(i.get(`@${e}`)?.(),i.set(`@${e}`,async()=>{de.abort(),await Promise.resolve()}));let D=null;try{if(!a?.length)throw o("FetchNoUrlProvided",{action:k});let V={Accept:"text/event-stream, text/html, application/json","Datastar-Request":!0};u==="json"&&(V["Content-Type"]="application/json");let Ae=Object.assign({},V,l),re={method:t,headers:Ae,openWhenHidden:f,retry:b,retryInterval:v,retryScaler:A,retryMaxWaitMs:I,retryMaxCount:ne,signal:de.signal,onopen:async g=>{g.status>=400&&ee(sn,r,{status:g.status.toString()})},onmessage:g=>{if(!g.event.startsWith("datastar"))return;let B=g.event,E={};for(let R of g.data.split(` +`)){let w=R.indexOf(" "),W=R.slice(0,w),De=R.slice(w+1);(E[W]||=[]).push(De)}let N=Object.fromEntries(Object.entries(E).map(([R,w])=>[R,w.join(` +`)]));ee(B,r,N)},onerror:g=>{if(Lt(g))throw g("FetchExpectedTextEventStream",{url:a});g&&(console.error(g.message),ee(on,r,{message:g.message}))}},se=new URL(a,document.baseURI),ie=new URLSearchParams(se.search);if(u==="json"){F(),m=m!==void 0?m:_({include:d,exclude:h}),P();let g=JSON.stringify(m);t==="GET"?ie.set("datastar",g):re.body=g}else if(u==="form"){let g=c?document.querySelector(c):r.closest("form");if(!g)throw o("FetchFormNotFound",{action:k,selector:c});if(!g.noValidate&&!g.checkValidity()){g.reportValidity();return}let B=new FormData(g),E=r;if(r===g&&s instanceof SubmitEvent)E=s.submitter;else{let w=W=>W.preventDefault();g.addEventListener("submit",w),D=()=>{g.removeEventListener("submit",w)}}if(E instanceof HTMLButtonElement){let w=E.getAttribute("name");w&&B.append(w,E.value)}let N=g.getAttribute("enctype")==="multipart/form-data";N||(Ae["Content-Type"]="application/x-www-form-urlencoded");let R=new URLSearchParams(B);if(t==="GET")for(let[w,W]of R)ie.append(w,W);else N?re.body=B:re.body=R}else throw o("FetchInvalidContentType",{action:k,contentType:u});ee(Ye,r,{}),se.search=ie.toString();try{await dn(se.toString(),r,re)}catch(g){if(!Lt(g))throw o("FetchFailed",{method:t,url:a,error:g.message})}}finally{ee(Xe,r,{}),D?.(),i.delete(`@${e}`)}}});Ee("get","GET",!1);Ee("patch","PATCH");Ee("post","POST");Ee("put","PUT");Ee("delete","DELETE");var Ye="started",Xe="finished",sn="error",on="retrying",an="retries-failed",ee=(e,t,n)=>document.dispatchEvent(new CustomEvent(q,{detail:{type:e,el:t,argsRaw:n}})),Lt=e=>`${e}`.includes("text/event-stream"),cn=async(e,t)=>{let n=e.getReader(),r=await n.read();for(;!r.done;)t(r.value),r=await n.read()},ln=e=>{let t,n,r,s=!1;return o=>{t?t=fn(t,o):(t=o,n=0,r=-1);let i=t.length,a=0;for(;n{let r=Nt(),s=new TextDecoder;return(o,i)=>{if(!o.length)n?.(r),r=Nt();else if(i>0){let a=s.decode(o.subarray(0,i)),c=i+(o[i+1]===32?2:1),l=s.decode(o.subarray(c));switch(a){case"data":r.data=r.data?`${r.data} +${l}`:l;break;case"event":r.event=l;break;case"id":e(r.id=l);break;case"retry":{let u=+l;Number.isNaN(u)||t(r.retry=u);break}}}}},fn=(e,t)=>{let n=new Uint8Array(e.length+t.length);return n.set(e),n.set(t,e.length),n},Nt=()=>({data:"",event:"",id:"",retry:void 0}),dn=(e,t,{signal:n,headers:r,onopen:s,onmessage:o,onclose:i,onerror:a,openWhenHidden:c,fetch:l,retry:u="auto",retryInterval:d=1e3,retryScaler:h=2,retryMaxWaitMs:f=3e4,retryMaxCount:m=10,responseOverrides:y,...b})=>new Promise((v,A)=>{let I={...r},ne,de=()=>{ne.abort(),document.hidden||g()};c||document.addEventListener("visibilitychange",de);let D,V=()=>{document.removeEventListener("visibilitychange",de),clearTimeout(D),ne.abort()};n?.addEventListener("abort",()=>{V(),v()});let Ae=l||window.fetch,re=s||(()=>{}),se=0,ie=d,g=async()=>{ne=new AbortController;let B=ne.signal;try{let E=await Ae(e,{...b,headers:I,signal:B});await re(E);let N=async($,me,$e,Re,...Wt)=>{let ot={[$e]:await me.text()};for(let Ie of Wt){let qe=me.headers.get(`datastar-${ge(Ie)}`);if(Re){let we=Re[Ie];we&&(qe=typeof we=="string"?we:JSON.stringify(we))}qe&&(ot[Ie]=qe)}ee($,t,ot),V(),v()},R=E.status,w=R===204,W=R>=300&&R<400,De=R>=400&&R<600;if(R!==200){if(i?.(),u!=="never"&&!w&&!W&&(u==="always"||u==="error"&&De)){clearTimeout(D),D=setTimeout(g,d);return}V(),v();return}se=0,d=ie;let Ve=E.headers.get("Content-Type");if(Ve?.includes("text/html"))return await N("datastar-patch-elements",E,"elements",y,"selector","mode","namespace","useViewTransition");if(Ve?.includes("application/json"))return await N("datastar-patch-signals",E,"signals",y,"onlyIfMissing");if(Ve?.includes("text/javascript")){let $=document.createElement("script"),me=E.headers.get("datastar-script-attributes");if(me)for(let[$e,Re]of Object.entries(JSON.parse(me)))$.setAttribute($e,Re);$.textContent=await E.text(),document.head.appendChild($),V();return}if(await cn(E.body,ln(un($=>{$?I["last-event-id"]=$:delete I["last-event-id"]},$=>{ie=d=$},o))),i?.(),u==="always"&&!W){clearTimeout(D),D=setTimeout(g,d);return}V(),v()}catch(E){if(!B.aborted)try{let N=a?.(E)||d;clearTimeout(D),D=setTimeout(g,N),d=Math.min(d*h,f),++se>=m?(ee(an,t,{}),V(),A("Max retries reached.")):console.error(`Datastar failed to reach ${e.toString()} retrying in ${N}ms.`)}catch(N){V(),A(N)}}};g()});p({name:"attr",requirement:{value:"must"},returnsValue:!0,apply({el:e,key:t,rx:n}){let r=(a,c)=>{c===""||c===!0?e.setAttribute(a,""):c===!1||c==null?e.removeAttribute(a):typeof c=="string"?e.setAttribute(a,c):e.setAttribute(a,JSON.stringify(c))},s=t?()=>{o.disconnect();let a=n();r(t,a),o.observe(e,{attributeFilter:[t]})}:()=>{o.disconnect();let a=n(),c=Object.keys(a);for(let l of c)r(l,a[l]);o.observe(e,{attributeFilter:c})},o=new MutationObserver(s),i=S(s);return()=>{o.disconnect(),i()}}});var mn=/^data:(?[^;]+);base64,(?.*)$/,Ct=Symbol("empty"),Ft=G("bind");p({name:"bind",requirement:"exclusive",apply({el:e,key:t,mods:n,value:r,error:s}){let o=t!=null?L(t,n):r,i=(f,m)=>m==="number"?+f.value:f.value,a=f=>{e.value=`${f}`};if(e instanceof HTMLInputElement)switch(e.type){case"range":case"number":i=(f,m)=>m==="string"?f.value:+f.value;break;case"checkbox":i=(f,m)=>f.value!=="on"?m==="boolean"?f.checked:f.checked?f.value:"":m==="string"?f.checked?f.value:"":f.checked,a=f=>{e.checked=typeof f=="string"?f===e.value:f};break;case"radio":e.getAttribute("name")?.length||e.setAttribute("name",o),i=(f,m)=>f.checked?m==="number"?+f.value:f.value:Ct,a=f=>{e.checked=f===(typeof f=="number"?+e.value:e.value)};break;case"file":{let f=()=>{let m=[...e.files||[]],y=[];Promise.all(m.map(b=>new Promise(v=>{let A=new FileReader;A.onload=()=>{if(typeof A.result!="string")throw s("InvalidFileResultType",{resultType:typeof A.result});let I=A.result.match(mn);if(!I?.groups)throw s("InvalidDataUri",{result:A.result});y.push({name:b.name,contents:I.groups.contents,mime:I.groups.mime})},A.onloadend=()=>v(),A.readAsDataURL(b)}))).then(()=>{T([[o,y]])})};return e.addEventListener("change",f),e.addEventListener("input",f),()=>{e.removeEventListener("change",f),e.removeEventListener("input",f)}}}else if(e instanceof HTMLSelectElement){if(e.multiple){let f=new Map;i=m=>[...m.selectedOptions].map(y=>{let b=f.get(y.value);return b==="string"||b==null?y.value:+y.value}),a=m=>{for(let y of e.options)m.includes(y.value)?(f.set(y.value,"string"),y.selected=!0):m.includes(+y.value)?(f.set(y.value,"number"),y.selected=!0):y.selected=!1}}}else e instanceof HTMLTextAreaElement||(i=f=>"value"in f?f.value:f.getAttribute("value"),a=f=>{"value"in e?e.value=f:e.setAttribute("value",f)});let c=oe(o),l=typeof c,u=o;if(Array.isArray(c)&&!(e instanceof HTMLSelectElement&&e.multiple)){let f=t||r,m=document.querySelectorAll(`[${Ft}\\:${CSS.escape(f)}],[${Ft}="${CSS.escape(f)}"]`),y=[],b=0;for(let v of m){if(y.push([`${u}.${b}`,i(v,"none")]),e===v)break;b++}T(y,{ifMissing:!0}),u=`${u}.${b}`}else T([[u,i(e,l)]],{ifMissing:!0});let d=()=>{let f=oe(u);if(f!=null){let m=i(e,typeof f);m!==Ct&&T([[u,m]])}};e.addEventListener("input",d),e.addEventListener("change",d);let h=S(()=>{a(oe(u))});return()=>{h(),e.removeEventListener("input",d),e.removeEventListener("change",d)}}});p({name:"class",requirement:{value:"must"},returnsValue:!0,apply({key:e,el:t,mods:n,rx:r}){e&&=L(e,n,"kebab");let s,o=()=>{i.disconnect(),s=e?{[e]:r()}:r();for(let c in s){let l=c.split(/\s+/).filter(u=>u.length>0);if(s[c])for(let u of l)t.classList.contains(u)||t.classList.add(u);else for(let u of l)t.classList.contains(u)&&t.classList.remove(u)}i.observe(t,{attributeFilter:["class"]})},i=new MutationObserver(o),a=S(o);return()=>{i.disconnect(),a();for(let c in s){let l=c.split(/\s+/).filter(u=>u.length>0);for(let u of l)t.classList.remove(u)}}}});p({name:"computed",requirement:{value:"must"},returnsValue:!0,apply({key:e,mods:t,rx:n,error:r}){if(e)T([[L(e,t),ke(n)]]);else{let s=Object.assign({},n());Y(s,o=>{if(typeof o=="function")return ke(o);throw r("ComputedExpectedFunction")}),O(s)}}});p({name:"effect",requirement:{key:"denied",value:"must"},apply:({rx:e})=>S(e)});p({name:"indicator",requirement:"exclusive",apply({el:e,key:t,mods:n,value:r}){let s=t!=null?L(t,n):r;T([[s,!1]]);let o=i=>{let{type:a,el:c}=i.detail;if(c===e)switch(a){case Ye:T([[s,!0]]);break;case Xe:T([[s,!1]]);break}};return document.addEventListener(q,o),()=>{T([[s,!1]]),document.removeEventListener(q,o)}}});var z=e=>{if(!e||e.size<=0)return 0;for(let t of e){if(t.endsWith("ms"))return+t.replace("ms","");if(t.endsWith("s"))return+t.replace("s","")*1e3;try{return Number.parseFloat(t)}catch{}}return 0},te=(e,t,n=!1)=>e?e.has(t.toLowerCase()):n;var et=(e,t)=>(...n)=>{setTimeout(()=>{e(...n)},t)},Pt=(e,t,n=!0,r=!1,s=!1)=>{let o=null,i=0;return(...a)=>{n&&!i?(e(...a),o=null):o=a,(!i||s)&&(i&&clearTimeout(i),i=setTimeout(()=>{r&&o!==null&&e(...o),o=null,i=0},t))}},ce=(e,t)=>{let n=t.get("delay");if(n){let o=z(n);e=et(e,o)}let r=t.get("debounce");if(r){let o=z(r),i=te(r,"leading",!1),a=!te(r,"notrailing",!1);e=Pt(e,o,i,a,!0)}let s=t.get("throttle");if(s){let o=z(s),i=!te(s,"noleading",!1),a=te(s,"trailing",!1);e=Pt(e,o,i,a)}return e};var tt=!!document.startViewTransition,Q=(e,t)=>{if(t.has("viewtransition")&&tt){let n=e;e=(...r)=>document.startViewTransition(()=>n(...r))}return e};p({name:"init",requirement:{key:"denied",value:"must"},apply({rx:e,mods:t}){let n=()=>{M(),e(),x()};n=Q(n,t);let r=0,s=t.get("delay");s&&(r=z(s),r>0&&(n=et(n,r))),n()}});p({name:"json-signals",requirement:{key:"denied"},apply({el:e,value:t,mods:n}){let r=n.has("terse")?0:2,s={};t&&(s=ae(t));let o=()=>{i.disconnect(),e.textContent=JSON.stringify(_(s),null,r),i.observe(e,{childList:!0,characterData:!0,subtree:!0})},i=new MutationObserver(o),a=S(o);return()=>{i.disconnect(),a()}}});p({name:"on",requirement:"must",argNames:["evt"],apply({el:e,key:t,mods:n,rx:r}){let s=e;n.has("window")&&(s=window);let o=c=>{c&&(n.has("prevent")&&c.preventDefault(),n.has("stop")&&c.stopPropagation()),M(),r(c),x()};o=Q(o,n),o=ce(o,n);let i={capture:n.has("capture"),passive:n.has("passive"),once:n.has("once")};if(n.has("outside")){s=document;let c=o;o=l=>{e.contains(l?.target)||c(l)}}let a=L(t,n,"kebab");if((a===q||a===Z)&&(s=document),e instanceof HTMLFormElement&&a==="submit"){let c=o;o=l=>{l?.preventDefault(),c(l)}}return s.addEventListener(a,o,i),()=>{s.removeEventListener(a,o)}}});var Ot=(e,t,n)=>Math.max(t,Math.min(n,e));var nt=new WeakSet;p({name:"on-intersect",requirement:{key:"denied",value:"must"},apply({el:e,mods:t,rx:n}){let r=()=>{M(),n(),x()};r=Q(r,t),r=ce(r,t);let s={threshold:0};t.has("full")?s.threshold=1:t.has("half")?s.threshold=.5:t.get("threshold")&&(s.threshold=Ot(Number(t.get("threshold")),0,100)/100);let o=t.has("exit"),i=new IntersectionObserver(a=>{for(let c of a)c.isIntersecting!==o&&(r(),i&&nt.has(e)&&i.disconnect())},s);return i.observe(e),t.has("once")&&nt.add(e),()=>{t.has("once")||nt.delete(e),i&&(i.disconnect(),i=null)}}});p({name:"on-interval",requirement:{key:"denied",value:"must"},apply({mods:e,rx:t}){let n=()=>{M(),t(),x()};n=Q(n,e);let r=1e3,s=e.get("duration");s&&(r=z(s),te(s,"leading",!1)&&n());let o=setInterval(n,r);return()=>{clearInterval(o)}}});p({name:"on-signal-patch",requirement:{value:"must"},argNames:["patch"],returnsValue:!0,apply({el:e,key:t,mods:n,rx:r,error:s}){if(t&&t!=="filter")throw s("KeyNotAllowed");let o=G(`${this.name}-filter`),i=e.getAttribute(o),a={};i&&(a=ae(i));let c=!1,l=ce(u=>{if(c)return;let d=_(a,u.detail);if(!ct(d)){c=!0,M();try{r(d)}finally{x(),c=!1}}},n);return document.addEventListener(Z,l),()=>{document.removeEventListener(Z,l)}}});p({name:"ref",requirement:"exclusive",apply({el:e,key:t,mods:n,value:r}){let s=t!=null?L(t,n):r;T([[s,e]])}});var kt="none",Ht="display";p({name:"show",requirement:{key:"denied",value:"must"},returnsValue:!0,apply({el:e,rx:t}){let n=()=>{r.disconnect(),t()?e.style.display===kt&&e.style.removeProperty(Ht):e.style.setProperty(Ht,kt),r.observe(e,{attributeFilter:["style"]})},r=new MutationObserver(n),s=S(n);return()=>{r.disconnect(),s()}}});p({name:"signals",returnsValue:!0,apply({key:e,mods:t,rx:n}){let r=t.has("ifmissing");if(e)e=L(e,t),T([[e,n?.()]],{ifMissing:r});else{let s=Object.assign({},n?.());O(s,{ifMissing:r})}}});p({name:"style",requirement:{value:"must"},returnsValue:!0,apply({key:e,el:t,rx:n}){let{style:r}=t,s=new Map,o=(l,u)=>{let d=s.get(l);!u&&u!==0?d!==void 0&&(d?r.setProperty(l,d):r.removeProperty(l)):(d===void 0&&s.set(l,r.getPropertyValue(l)),r.setProperty(l,String(u)))},i=()=>{if(a.disconnect(),e)o(e,n());else{let l=n();for(let[u,d]of s)u in l||(d?r.setProperty(u,d):r.removeProperty(u));for(let u in l)o(ge(u),l[u])}a.observe(t,{attributeFilter:["style"]})},a=new MutationObserver(i),c=S(i);return()=>{a.disconnect(),c();for(let[l,u]of s)u?r.setProperty(l,u):r.removeProperty(l)}}});p({name:"text",requirement:{key:"denied",value:"must"},returnsValue:!0,apply({el:e,rx:t}){let n=()=>{r.disconnect(),e.textContent=`${t()}`,r.observe(e,{childList:!0,characterData:!0,subtree:!0})},r=new MutationObserver(n),s=S(n);return()=>{r.disconnect(),s()}}});var _t=(e,t)=>e.includes(t),pn=["remove","outer","inner","replace","prepend","append","before","after"],gn=["html","svg","mathml"];ve({name:"datastar-patch-elements",apply(e,{selector:t="",mode:n="outer",namespace:r="html",useViewTransition:s="",elements:o=""}){if(!_t(pn,n))throw e.error("PatchElementsInvalidMode",{mode:n});if(!t&&n!=="outer"&&n!=="replace")throw e.error("PatchElementsExpectedSelector");if(!_t(gn,r))throw e.error("PatchElementsInvalidNamespace",{namespace:r});let i={selector:t,mode:n,namespace:r,useViewTransition:s.trim()==="true",elements:o};tt&&s?document.startViewTransition(()=>Dt(e,i)):Dt(e,i)}});var Dt=({error:e},{selector:t,mode:n,namespace:r,elements:s})=>{let o=s.replace(/]*>|>)([\s\S]*?)<\/svg>/gim,""),i=/<\/html>/.test(o),a=/<\/head>/.test(o),c=/<\/body>/.test(o),l=r==="svg"?"svg":r==="mathml"?"math":"",u=l?`<${l}>${s}`:s,d=new DOMParser().parseFromString(i||a||c?s:``,"text/html"),h=document.createDocumentFragment();if(i)h.appendChild(d.documentElement);else if(a&&c)h.appendChild(d.head),h.appendChild(d.body);else if(a)h.appendChild(d.head);else if(c)h.appendChild(d.body);else if(l){let f=d.querySelector("template").content.querySelector(l);for(let m of f.childNodes)h.appendChild(m)}else h=d.querySelector("template").content;if(!t&&(n==="outer"||n==="replace"))for(let f of h.children){let m;if(f instanceof HTMLHtmlElement)m=document.documentElement;else if(f instanceof HTMLBodyElement)m=document.body;else if(f instanceof HTMLHeadElement)m=document.head;else if(m=document.getElementById(f.id),!m){console.warn(e("PatchElementsNoTargetsFound"),{element:{id:f.id}});continue}$t(n,f,[m])}else{let f=document.querySelectorAll(t);if(!f.length){console.warn(e("PatchElementsNoTargetsFound"),{selector:t});return}$t(n,h,f)}},st=new WeakSet;for(let e of document.querySelectorAll("script"))st.add(e);var jt=e=>{let t=e instanceof HTMLScriptElement?[e]:e.querySelectorAll("script");for(let n of t)if(!st.has(n)){let r=document.createElement("script");for(let{name:s,value:o}of n.attributes)r.setAttribute(s,o);r.text=n.text,n.replaceWith(r),st.add(r)}},Vt=(e,t,n)=>{for(let r of e){let s=t.cloneNode(!0);jt(s),r[n](s)}},$t=(e,t,n)=>{switch(e){case"remove":for(let r of n)r.remove();break;case"outer":case"inner":for(let r of n)yn(r,t.cloneNode(!0),e),jt(r);break;case"replace":Vt(n,t,"replaceWith");break;case"prepend":case"append":case"before":case"after":Vt(n,t,e)}},H=new Map,ue=new Set,le=new Map,Se=new Set,fe=document.createElement("div");fe.hidden=!0;var Te=G("ignore-morph"),hn=`[${Te}]`,yn=(e,t,n="outer")=>{if(K(e)&&K(t)&&e.hasAttribute(Te)&&t.hasAttribute(Te)||e.parentElement?.closest(hn))return;let r=document.createElement("div");r.append(t),document.body.insertAdjacentElement("afterend",fe);let s=e.querySelectorAll("[id]");for(let{id:a,tagName:c}of s)le.has(a)?Se.add(a):le.set(a,c);e instanceof Element&&e.id&&(le.has(e.id)?Se.add(e.id):le.set(e.id,e.tagName)),ue.clear();let o=r.querySelectorAll("[id]");for(let{id:a,tagName:c}of o)ue.has(a)?Se.add(a):le.get(a)===c&&ue.add(a);for(let a of Se)ue.delete(a);le.clear(),Se.clear(),H.clear();let i=n==="outer"?e.parentElement:e;qt(i,s),qt(r,o),Gt(i,r,n==="outer"?e:null,e.nextSibling),fe.remove()},Gt=(e,t,n=null,r=null)=>{e instanceof HTMLTemplateElement&&t instanceof HTMLTemplateElement&&(e=e.content,t=t.content),n??=e.firstChild;for(let s of t.childNodes){if(n&&n!==r){let o=bn(s,n,r);if(o){if(o!==n){let i=n;for(;i&&i!==o;){let a=i;i=i.nextSibling,it(a)}}rt(o,s),n=o.nextSibling;continue}}if(s instanceof Element&&ue.has(s.id)){let o=document.getElementById(s.id),i=o;for(;i=i.parentNode;){let a=H.get(i);a&&(a.delete(s.id),a.size||H.delete(i))}Bt(e,o,n),rt(o,s),n=o.nextSibling;continue}if(H.has(s)){let o=s.namespaceURI,i=s.tagName,a=o&&o!=="http://www.w3.org/1999/xhtml"?document.createElementNS(o,i):document.createElement(i);e.insertBefore(a,n),rt(a,s),n=a.nextSibling}else{let o=document.importNode(s,!0);e.insertBefore(o,n),n=o.nextSibling}}for(;n&&n!==r;){let s=n;n=n.nextSibling,it(s)}},bn=(e,t,n)=>{let r=null,s=e.nextSibling,o=0,i=0,a=H.get(e)?.size||0,c=t;for(;c&&c!==n;){if(It(c,e)){let l=!1,u=H.get(c),d=H.get(e);if(d&&u){for(let h of u)if(d.has(h)){l=!0;break}}if(l)return c;if(!r&&!H.has(c)){if(!a)return c;r=c}}if(i+=H.get(c)?.size||0,i>a)break;r===null&&s&&It(c,s)&&(o++,s=s.nextSibling,o>=2&&(r=void 0)),c=c.nextSibling}return r||null},It=(e,t)=>e.nodeType===t.nodeType&&e.tagName===t.tagName&&(!e.id||e.id===t.id),it=e=>{H.has(e)?Bt(fe,e,null):e.parentNode?.removeChild(e)},Bt=it.call.bind(fe.moveBefore??fe.insertBefore),vn=G("preserve-attr"),rt=(e,t)=>{let n=t.nodeType;if(n===1){let r=e,s=t,o=r.hasAttribute("data-scope-children");if(r.hasAttribute(Te)&&s.hasAttribute(Te))return e;r instanceof HTMLInputElement&&s instanceof HTMLInputElement&&s.type!=="file"?s.getAttribute("value")!==r.getAttribute("value")&&(r.value=s.getAttribute("value")??""):r instanceof HTMLTextAreaElement&&s instanceof HTMLTextAreaElement&&(s.value!==r.value&&(r.value=s.value),r.firstChild&&r.firstChild.nodeValue!==s.value&&(r.firstChild.nodeValue=s.value));let i=(t.getAttribute(vn)??"").split(" ");for(let{name:a,value:c}of s.attributes)r.getAttribute(a)!==c&&!i.includes(a)&&r.setAttribute(a,c);for(let a=r.attributes.length-1;a>=0;a--){let{name:c}=r.attributes[a];!s.hasAttribute(c)&&!i.includes(c)&&r.removeAttribute(c)}o&&!r.hasAttribute("data-scope-children")&&r.setAttribute("data-scope-children",""),r.isEqualNode(s)||Gt(r,s),o&&r.dispatchEvent(new CustomEvent("datastar:scope-children",{bubbles:!1}))}return(n===8||n===3)&&e.nodeValue!==t.nodeValue&&(e.nodeValue=t.nodeValue),e},qt=(e,t)=>{for(let n of t)if(ue.has(n.id)){let r=n;for(;r&&r!==e;){let s=H.get(r);s||(s=new Set,H.set(r,s)),s.add(n.id),r=r.parentElement}}};ve({name:"datastar-patch-signals",apply({error:e},{signals:t,onlyIfMissing:n}){if(t){let r=n?.trim()==="true";O(ae(t),{ifMissing:r})}else throw e("PatchSignalsExpectedSignals")}});export{k as action,Rt as actions,p as attribute,M as beginBatch,ke as computed,S as effect,x as endBatch,_ as filtered,oe as getPath,O as mergePatch,T as mergePaths,X as root,pe as signal,F as startPeeking,P as stopPeeking,ve as watcher}; +//# sourceMappingURL=datastar.js.map diff --git a/features/auth/pages/login_templ.go b/features/auth/pages/login_templ.go index 3640508..5d0cec4 100644 --- a/features/auth/pages/login_templ.go +++ b/features/auth/pages/login_templ.go @@ -46,33 +46,33 @@ func LoginPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Login

Sign in to your account

Login

Sign in to your account

Don't have an account? Register

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">Login

Don't have an account? Register

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/auth/pages/register_templ.go b/features/auth/pages/register_templ.go index 61c8f21..7486adb 100644 --- a/features/auth/pages/register_templ.go +++ b/features/auth/pages/register_templ.go @@ -46,33 +46,33 @@ func RegisterPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Register

Create a new account

Register

Create a new account

Already have an account? Login

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">Register

Already have an account? Login

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/auth/routes.go b/features/auth/routes.go index ac50938..16e5e85 100644 --- a/features/auth/routes.go +++ b/features/auth/routes.go @@ -8,11 +8,9 @@ import ( "github.com/ryanhamamura/c4/db/repository" ) -func SetupRoutes(router chi.Router, queries *repository.Queries, sessions *scs.SessionManager) error { +func SetupRoutes(router chi.Router, queries *repository.Queries, sessions *scs.SessionManager) { router.Get("/login", HandleLoginPage()) router.Get("/register", HandleRegisterPage()) router.Post("/auth/login", HandleLogin(queries, sessions)) router.Post("/auth/register", HandleRegister(queries, sessions)) - - return nil } diff --git a/features/c4game/routes.go b/features/c4game/routes.go index 4cc4641..2ffd2dc 100644 --- a/features/c4game/routes.go +++ b/features/c4game/routes.go @@ -16,7 +16,7 @@ func SetupRoutes( nc *nats.Conn, sessions *scs.SessionManager, queries *repository.Queries, -) error { +) { router.Route("/games/{id}", func(r chi.Router) { r.Get("/", HandleGamePage(store, sessions, queries)) r.Get("/events", HandleGameEvents(store, nc, sessions, queries)) @@ -25,6 +25,4 @@ func SetupRoutes( r.Post("/join", HandleSetNickname(store, sessions)) r.Post("/rematch", HandleRematch(store, sessions)) }) - - return nil } diff --git a/features/common/layouts/base_templ.go b/features/common/layouts/base_templ.go index 2f5437f..083a510 100644 --- a/features/common/layouts/base_templ.go +++ b/features/common/layouts/base_templ.go @@ -44,7 +44,7 @@ func Base(title string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/features/lobby/pages/lobby_templ.go b/features/lobby/pages/lobby_templ.go index ffae496..9e11bd7 100644 --- a/features/lobby/pages/lobby_templ.go +++ b/features/lobby/pages/lobby_templ.go @@ -100,7 +100,7 @@ func LobbyPage(data LobbyData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

Start a new session

Start a new session

Date: Mon, 2 Mar 2026 14:34:53 -1000 Subject: [PATCH 9/9] feat: add downloader binary for client-side dependencies Replace the curl-based Taskfile download task with a Go binary that concurrently fetches datastar.js, datastar.js.map, daisyui.mjs, and daisyui-theme.mjs. Update vendored libs to latest versions. --- Taskfile.yml | 5 + assets/css/daisyui-theme.mjs | 2 +- assets/css/daisyui.mjs | 46 +- assets/css/output.css | 2107 +--------------------------------- assets/js/datastar.js.map | 7 + cmd/downloader/main.go | 100 ++ 6 files changed, 138 insertions(+), 2129 deletions(-) create mode 100644 assets/js/datastar.js.map create mode 100644 cmd/downloader/main.go diff --git a/Taskfile.yml b/Taskfile.yml index f5b9d29..7e8f9b6 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,6 +1,11 @@ version: "3" tasks: + download: + desc: Download latest client-side libs + cmds: + - go run cmd/downloader/main.go + build:styles: desc: Build TailwindCSS styles cmds: diff --git a/assets/css/daisyui-theme.mjs b/assets/css/daisyui-theme.mjs index befffdc..26cf6d3 100644 --- a/assets/css/daisyui-theme.mjs +++ b/assets/css/daisyui-theme.mjs @@ -18,7 +18,7 @@ var plugin = { }; // packages/daisyui/theme/object.js -var object_default = { 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% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.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% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, 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% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, 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% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.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% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" } }; +var object_default = { retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.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% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, 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% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.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% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" } }; // packages/daisyui/theme/index.js var theme_default = plugin.withOptions((options = {}) => { diff --git a/assets/css/daisyui.mjs b/assets/css/daisyui.mjs index 2d8fe30..8d9062b 100644 --- a/assets/css/daisyui.mjs +++ b/assets/css/daisyui.mjs @@ -163,7 +163,7 @@ var variables_default = { }; // packages/daisyui/theme/object.js -var object_default = { 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% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.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% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, 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% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, 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% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.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% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" } }; +var object_default = { retro: { "color-scheme": "light", "--color-base-100": "oklch(91.637% 0.034 90.515)", "--color-base-200": "oklch(88.272% 0.049 91.774)", "--color-base-300": "oklch(84.133% 0.065 90.856)", "--color-base-content": "oklch(41% 0.112 45.904)", "--color-primary": "oklch(80% 0.114 19.571)", "--color-primary-content": "oklch(39% 0.141 25.723)", "--color-secondary": "oklch(92% 0.084 155.995)", "--color-secondary-content": "oklch(44% 0.119 151.328)", "--color-accent": "oklch(68% 0.162 75.834)", "--color-accent-content": "oklch(41% 0.112 45.904)", "--color-neutral": "oklch(44% 0.011 73.639)", "--color-neutral-content": "oklch(86% 0.005 56.366)", "--color-info": "oklch(58% 0.158 241.966)", "--color-info-content": "oklch(96% 0.059 95.617)", "--color-success": "oklch(51% 0.096 186.391)", "--color-success-content": "oklch(96% 0.059 95.617)", "--color-warning": "oklch(64% 0.222 41.116)", "--color-warning-content": "oklch(96% 0.059 95.617)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(40% 0.123 38.172)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.199 91.936)", "--color-primary-content": "oklch(42% 0.095 57.708)", "--color-secondary": "oklch(75% 0.183 55.934)", "--color-secondary-content": "oklch(40% 0.123 38.172)", "--color-accent": "oklch(0% 0 0)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(37% 0.01 67.558)", "--color-neutral-content": "oklch(92% 0.003 48.717)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(39% 0.09 240.876)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.029 256.847)", "--color-primary": "oklch(37.45% 0.189 325.02)", "--color-primary-content": "oklch(87.49% 0.037 325.02)", "--color-secondary": "oklch(53.92% 0.162 241.36)", "--color-secondary-content": "oklch(90.784% 0.032 241.36)", "--color-accent": "oklch(75.98% 0.204 56.72)", "--color-accent-content": "oklch(15.196% 0.04 56.72)", "--color-neutral": "oklch(27.807% 0.029 256.847)", "--color-neutral-content": "oklch(85.561% 0.005 256.847)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, aqua: { "color-scheme": "dark", "--color-base-100": "oklch(37% 0.146 265.522)", "--color-base-200": "oklch(28% 0.091 267.935)", "--color-base-300": "oklch(22% 0.091 267.935)", "--color-base-content": "oklch(90% 0.058 230.902)", "--color-primary": "oklch(85.661% 0.144 198.645)", "--color-primary-content": "oklch(40.124% 0.068 197.603)", "--color-secondary": "oklch(60.682% 0.108 309.782)", "--color-secondary-content": "oklch(96% 0.016 293.756)", "--color-accent": "oklch(93.426% 0.102 94.555)", "--color-accent-content": "oklch(18.685% 0.02 94.555)", "--color-neutral": "oklch(27% 0.146 265.522)", "--color-neutral-content": "oklch(80% 0.146 265.522)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(27% 0.077 45.635)", "--color-error": "oklch(73.95% 0.19 27.33)", "--color-error-content": "oklch(14.79% 0.038 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, coffee: { "color-scheme": "dark", "--color-base-100": "oklch(24% 0.023 329.708)", "--color-base-200": "oklch(21% 0.021 329.708)", "--color-base-300": "oklch(16% 0.019 329.708)", "--color-base-content": "oklch(72.354% 0.092 79.129)", "--color-primary": "oklch(71.996% 0.123 62.756)", "--color-primary-content": "oklch(14.399% 0.024 62.756)", "--color-secondary": "oklch(34.465% 0.029 199.194)", "--color-secondary-content": "oklch(86.893% 0.005 199.194)", "--color-accent": "oklch(42.621% 0.074 224.389)", "--color-accent-content": "oklch(88.524% 0.014 224.389)", "--color-neutral": "oklch(16.51% 0.015 326.261)", "--color-neutral-content": "oklch(83.302% 0.003 326.261)", "--color-info": "oklch(79.49% 0.063 184.558)", "--color-info-content": "oklch(15.898% 0.012 184.558)", "--color-success": "oklch(74.722% 0.072 131.116)", "--color-success-content": "oklch(14.944% 0.014 131.116)", "--color-warning": "oklch(88.15% 0.14 87.722)", "--color-warning-content": "oklch(17.63% 0.028 87.722)", "--color-error": "oklch(77.318% 0.128 31.871)", "--color-error-content": "oklch(15.463% 0.025 31.871)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.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% 0.103 205.9)", "--color-info-content": "oklch(15.908% 0.02 205.9)", "--color-success": "oklch(90.13% 0.153 164.14)", "--color-success-content": "oklch(18.026% 0.03 164.14)", "--color-warning": "oklch(88.37% 0.135 79.94)", "--color-warning-content": "oklch(17.674% 0.027 79.94)", "--color-error": "oklch(78.66% 0.15 28.47)", "--color-error-content": "oklch(15.732% 0.03 28.47)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, garden: { "color-scheme": "light", "--color-base-100": "oklch(92.951% 0.002 17.197)", "--color-base-200": "oklch(86.445% 0.002 17.197)", "--color-base-300": "oklch(79.938% 0.001 17.197)", "--color-base-content": "oklch(16.961% 0.001 17.32)", "--color-primary": "oklch(62.45% 0.278 3.836)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(48.495% 0.11 355.095)", "--color-secondary-content": "oklch(89.699% 0.022 355.095)", "--color-accent": "oklch(56.273% 0.054 154.39)", "--color-accent-content": "oklch(100% 0 0)", "--color-neutral": "oklch(24.155% 0.049 89.07)", "--color-neutral-content": "oklch(92.951% 0.002 17.197)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, halloween: { "color-scheme": "dark", "--color-base-100": "oklch(21% 0.006 56.043)", "--color-base-200": "oklch(14% 0.004 49.25)", "--color-base-300": "oklch(0% 0 0)", "--color-base-content": "oklch(84.955% 0 0)", "--color-primary": "oklch(77.48% 0.204 60.62)", "--color-primary-content": "oklch(19.693% 0.004 196.779)", "--color-secondary": "oklch(45.98% 0.248 305.03)", "--color-secondary-content": "oklch(89.196% 0.049 305.03)", "--color-accent": "oklch(64.8% 0.223 136.073)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(24.371% 0.046 65.681)", "--color-neutral-content": "oklch(84.874% 0.009 65.681)", "--color-info": "oklch(54.615% 0.215 262.88)", "--color-info-content": "oklch(90.923% 0.043 262.88)", "--color-success": "oklch(62.705% 0.169 149.213)", "--color-success-content": "oklch(12.541% 0.033 149.213)", "--color-warning": "oklch(66.584% 0.157 58.318)", "--color-warning-content": "oklch(13.316% 0.031 58.318)", "--color-error": "oklch(65.72% 0.199 27.33)", "--color-error-content": "oklch(13.144% 0.039 27.33)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, pastel: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(98.462% 0.001 247.838)", "--color-base-300": "oklch(92.462% 0.001 247.838)", "--color-base-content": "oklch(20% 0 0)", "--color-primary": "oklch(90% 0.063 306.703)", "--color-primary-content": "oklch(49% 0.265 301.924)", "--color-secondary": "oklch(89% 0.058 10.001)", "--color-secondary-content": "oklch(51% 0.222 16.935)", "--color-accent": "oklch(90% 0.093 164.15)", "--color-accent-content": "oklch(50% 0.118 165.612)", "--color-neutral": "oklch(55% 0.046 257.417)", "--color-neutral-content": "oklch(92% 0.013 255.508)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(52% 0.105 223.128)", "--color-success": "oklch(87% 0.15 154.449)", "--color-success-content": "oklch(52% 0.154 150.069)", "--color-warning": "oklch(83% 0.128 66.29)", "--color-warning-content": "oklch(55% 0.195 38.402)", "--color-error": "oklch(80% 0.114 19.571)", "--color-error-content": "oklch(50% 0.213 27.518)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "0", "--noise": "0" }, night: { "color-scheme": "dark", "--color-base-100": "oklch(20.768% 0.039 265.754)", "--color-base-200": "oklch(19.314% 0.037 265.754)", "--color-base-300": "oklch(17.86% 0.034 265.754)", "--color-base-content": "oklch(84.153% 0.007 265.754)", "--color-primary": "oklch(75.351% 0.138 232.661)", "--color-primary-content": "oklch(15.07% 0.027 232.661)", "--color-secondary": "oklch(68.011% 0.158 276.934)", "--color-secondary-content": "oklch(13.602% 0.031 276.934)", "--color-accent": "oklch(72.36% 0.176 350.048)", "--color-accent-content": "oklch(14.472% 0.035 350.048)", "--color-neutral": "oklch(27.949% 0.036 260.03)", "--color-neutral-content": "oklch(85.589% 0.007 260.03)", "--color-info": "oklch(68.455% 0.148 237.251)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(78.452% 0.132 181.911)", "--color-success-content": "oklch(15.69% 0.026 181.911)", "--color-warning": "oklch(83.242% 0.139 82.95)", "--color-warning-content": "oklch(16.648% 0.027 82.95)", "--color-error": "oklch(71.785% 0.17 13.118)", "--color-error-content": "oklch(14.357% 0.034 13.118)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dark: { "color-scheme": "dark", "--color-base-100": "oklch(25.33% 0.016 252.42)", "--color-base-200": "oklch(23.26% 0.014 253.1)", "--color-base-300": "oklch(21.15% 0.012 254.09)", "--color-base-content": "oklch(97.807% 0.029 256.847)", "--color-primary": "oklch(58% 0.233 277.117)", "--color-primary-content": "oklch(96% 0.018 272.314)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, cupcake: { "color-scheme": "light", "--color-base-100": "oklch(97.788% 0.004 56.375)", "--color-base-200": "oklch(93.982% 0.007 61.449)", "--color-base-300": "oklch(91.586% 0.006 53.44)", "--color-base-content": "oklch(23.574% 0.066 313.189)", "--color-primary": "oklch(85% 0.138 181.071)", "--color-primary-content": "oklch(43% 0.078 188.216)", "--color-secondary": "oklch(89% 0.061 343.231)", "--color-secondary-content": "oklch(45% 0.187 3.815)", "--color-accent": "oklch(90% 0.076 70.697)", "--color-accent-content": "oklch(47% 0.157 37.304)", "--color-neutral": "oklch(27% 0.006 286.033)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(68% 0.169 237.323)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(69% 0.17 162.48)", "--color-success-content": "oklch(26% 0.051 172.552)", "--color-warning": "oklch(79% 0.184 86.047)", "--color-warning-content": "oklch(28% 0.066 53.813)", "--color-error": "oklch(64% 0.246 16.439)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, 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% 0.032 262.988)", "--color-primary": "oklch(76.662% 0.135 153.45)", "--color-primary-content": "oklch(33.387% 0.04 162.24)", "--color-secondary": "oklch(61.302% 0.202 261.294)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(72.772% 0.149 33.2)", "--color-accent-content": "oklch(0% 0 0)", "--color-neutral": "oklch(35.519% 0.032 262.988)", "--color-neutral-content": "oklch(98.462% 0.001 247.838)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, synthwave: { "color-scheme": "dark", "--color-base-100": "oklch(15% 0.09 281.288)", "--color-base-200": "oklch(20% 0.09 281.288)", "--color-base-300": "oklch(25% 0.09 281.288)", "--color-base-content": "oklch(78% 0.115 274.713)", "--color-primary": "oklch(71% 0.202 349.761)", "--color-primary-content": "oklch(28% 0.109 3.907)", "--color-secondary": "oklch(82% 0.111 230.318)", "--color-secondary-content": "oklch(29% 0.066 243.157)", "--color-accent": "oklch(75% 0.183 55.934)", "--color-accent-content": "oklch(26% 0.079 36.259)", "--color-neutral": "oklch(45% 0.24 277.023)", "--color-neutral-content": "oklch(87% 0.065 274.039)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(77% 0.152 181.912)", "--color-success-content": "oklch(27% 0.046 192.524)", "--color-warning": "oklch(90% 0.182 98.111)", "--color-warning-content": "oklch(42% 0.095 57.708)", "--color-error": "oklch(73.7% 0.121 32.639)", "--color-error-content": "oklch(23.501% 0.096 290.329)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.006 285.885)", "--color-primary": "oklch(45% 0.24 277.023)", "--color-primary-content": "oklch(93% 0.034 272.788)", "--color-secondary": "oklch(65% 0.241 354.308)", "--color-secondary-content": "oklch(94% 0.028 342.258)", "--color-accent": "oklch(77% 0.152 181.912)", "--color-accent-content": "oklch(38% 0.063 188.416)", "--color-neutral": "oklch(14% 0.005 285.823)", "--color-neutral-content": "oklch(92% 0.004 286.32)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(76% 0.177 163.223)", "--color-success-content": "oklch(37% 0.077 168.94)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(71% 0.194 13.428)", "--color-error-content": "oklch(27% 0.105 12.094)", "--radius-selector": "0.5rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, silk: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.0035 67.78)", "--color-base-200": "oklch(95% 0.0081 61.42)", "--color-base-300": "oklch(90% 0.0081 61.42)", "--color-base-content": "oklch(40% 0.0081 61.42)", "--color-primary": "oklch(23.27% 0.0249 284.3)", "--color-primary-content": "oklch(94.22% 0.2505 117.44)", "--color-secondary": "oklch(23.27% 0.0249 284.3)", "--color-secondary-content": "oklch(73.92% 0.2135 50.94)", "--color-accent": "oklch(23.27% 0.0249 284.3)", "--color-accent-content": "oklch(88.92% 0.2061 189.9)", "--color-neutral": "oklch(20% 0 0)", "--color-neutral-content": "oklch(80% 0.0081 61.42)", "--color-info": "oklch(80.39% 0.1148 241.68)", "--color-info-content": "oklch(30.39% 0.1148 241.68)", "--color-success": "oklch(83.92% 0.0901 136.87)", "--color-success-content": "oklch(23.92% 0.0901 136.87)", "--color-warning": "oklch(83.92% 0.1085 80)", "--color-warning-content": "oklch(43.92% 0.1085 80)", "--color-error": "oklch(75.1% 0.1814 22.37)", "--color-error-content": "oklch(35.1% 0.1814 22.37)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "0" }, nord: { "color-scheme": "light", "--color-base-100": "oklch(95.127% 0.007 260.731)", "--color-base-200": "oklch(93.299% 0.01 261.788)", "--color-base-300": "oklch(89.925% 0.016 262.749)", "--color-base-content": "oklch(32.437% 0.022 264.182)", "--color-primary": "oklch(59.435% 0.077 254.027)", "--color-primary-content": "oklch(11.887% 0.015 254.027)", "--color-secondary": "oklch(69.651% 0.059 248.687)", "--color-secondary-content": "oklch(13.93% 0.011 248.687)", "--color-accent": "oklch(77.464% 0.062 217.469)", "--color-accent-content": "oklch(15.492% 0.012 217.469)", "--color-neutral": "oklch(45.229% 0.035 264.131)", "--color-neutral-content": "oklch(89.925% 0.016 262.749)", "--color-info": "oklch(69.207% 0.062 332.664)", "--color-info-content": "oklch(13.841% 0.012 332.664)", "--color-success": "oklch(76.827% 0.074 131.063)", "--color-success-content": "oklch(15.365% 0.014 131.063)", "--color-warning": "oklch(85.486% 0.089 84.093)", "--color-warning-content": "oklch(17.097% 0.017 84.093)", "--color-error": "oklch(60.61% 0.12 15.341)", "--color-error-content": "oklch(12.122% 0.024 15.341)", "--radius-selector": "1rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, dim: { "color-scheme": "dark", "--color-base-100": "oklch(30.857% 0.023 264.149)", "--color-base-200": "oklch(28.036% 0.019 264.182)", "--color-base-300": "oklch(26.346% 0.018 262.177)", "--color-base-content": "oklch(82.901% 0.031 222.959)", "--color-primary": "oklch(86.133% 0.141 139.549)", "--color-primary-content": "oklch(17.226% 0.028 139.549)", "--color-secondary": "oklch(73.375% 0.165 35.353)", "--color-secondary-content": "oklch(14.675% 0.033 35.353)", "--color-accent": "oklch(74.229% 0.133 311.379)", "--color-accent-content": "oklch(14.845% 0.026 311.379)", "--color-neutral": "oklch(24.731% 0.02 264.094)", "--color-neutral-content": "oklch(82.901% 0.031 222.959)", "--color-info": "oklch(86.078% 0.142 206.182)", "--color-info-content": "oklch(17.215% 0.028 206.182)", "--color-success": "oklch(86.171% 0.142 166.534)", "--color-success-content": "oklch(17.234% 0.028 166.534)", "--color-warning": "oklch(86.163% 0.142 94.818)", "--color-warning-content": "oklch(17.232% 0.028 94.818)", "--color-error": "oklch(82.418% 0.099 33.756)", "--color-error-content": "oklch(16.483% 0.019 33.756)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.099 251.473)", "--color-primary-content": "oklch(88.34% 0.019 251.473)", "--color-secondary": "oklch(64.092% 0.027 229.389)", "--color-secondary-content": "oklch(12.818% 0.005 229.389)", "--color-accent": "oklch(67.271% 0.167 35.791)", "--color-accent-content": "oklch(13.454% 0.033 35.791)", "--color-neutral": "oklch(27.441% 0.013 253.041)", "--color-neutral-content": "oklch(85.488% 0.002 253.041)", "--color-info": "oklch(62.616% 0.143 240.033)", "--color-info-content": "oklch(12.523% 0.028 240.033)", "--color-success": "oklch(70.226% 0.094 156.596)", "--color-success-content": "oklch(14.045% 0.018 156.596)", "--color-warning": "oklch(77.482% 0.115 81.519)", "--color-warning-content": "oklch(15.496% 0.023 81.519)", "--color-error": "oklch(51.61% 0.146 29.674)", "--color-error-content": "oklch(90.322% 0.029 29.674)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, cyberpunk: { "color-scheme": "light", "--color-base-100": "oklch(94.51% 0.179 104.32)", "--color-base-200": "oklch(91.51% 0.179 104.32)", "--color-base-300": "oklch(85.51% 0.179 104.32)", "--color-base-content": "oklch(0% 0 0)", "--color-primary": "oklch(74.22% 0.209 6.35)", "--color-primary-content": "oklch(14.844% 0.041 6.35)", "--color-secondary": "oklch(83.33% 0.184 204.72)", "--color-secondary-content": "oklch(16.666% 0.036 204.72)", "--color-accent": "oklch(71.86% 0.217 310.43)", "--color-accent-content": "oklch(14.372% 0.043 310.43)", "--color-neutral": "oklch(23.04% 0.065 269.31)", "--color-neutral-content": "oklch(94.51% 0.179 104.32)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.357 330.759)", "--color-primary-content": "oklch(14.38% 0.071 330.759)", "--color-secondary": "oklch(73.37% 0.224 48.25)", "--color-secondary-content": "oklch(14.674% 0.044 48.25)", "--color-accent": "oklch(92.78% 0.264 122.962)", "--color-accent-content": "oklch(18.556% 0.052 122.962)", "--color-neutral": "oklch(21.31% 0.128 278.68)", "--color-neutral-content": "oklch(84.262% 0.025 278.68)", "--color-info": "oklch(60.72% 0.227 252.05)", "--color-info-content": "oklch(12.144% 0.045 252.05)", "--color-success": "oklch(85.72% 0.266 158.53)", "--color-success-content": "oklch(17.144% 0.053 158.53)", "--color-warning": "oklch(91.01% 0.212 100.5)", "--color-warning-content": "oklch(18.202% 0.042 100.5)", "--color-error": "oklch(64.84% 0.293 29.349)", "--color-error-content": "oklch(12.968% 0.058 29.349)", "--radius-selector": "1rem", "--radius-field": "1rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, sunset: { "color-scheme": "dark", "--color-base-100": "oklch(22% 0.019 237.69)", "--color-base-200": "oklch(20% 0.019 237.69)", "--color-base-300": "oklch(18% 0.019 237.69)", "--color-base-content": "oklch(77.383% 0.043 245.096)", "--color-primary": "oklch(74.703% 0.158 39.947)", "--color-primary-content": "oklch(14.94% 0.031 39.947)", "--color-secondary": "oklch(72.537% 0.177 2.72)", "--color-secondary-content": "oklch(14.507% 0.035 2.72)", "--color-accent": "oklch(71.294% 0.166 299.844)", "--color-accent-content": "oklch(14.258% 0.033 299.844)", "--color-neutral": "oklch(26% 0.019 237.69)", "--color-neutral-content": "oklch(70% 0.019 237.69)", "--color-info": "oklch(85.559% 0.085 206.015)", "--color-info-content": "oklch(17.111% 0.017 206.015)", "--color-success": "oklch(85.56% 0.085 144.778)", "--color-success-content": "oklch(17.112% 0.017 144.778)", "--color-warning": "oklch(85.569% 0.084 74.427)", "--color-warning-content": "oklch(17.113% 0.016 74.427)", "--color-error": "oklch(85.511% 0.078 16.886)", "--color-error-content": "oklch(17.102% 0.015 16.886)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, forest: { "color-scheme": "dark", "--color-base-100": "oklch(20.84% 0.008 17.911)", "--color-base-200": "oklch(18.522% 0.007 17.911)", "--color-base-300": "oklch(16.203% 0.007 17.911)", "--color-base-content": "oklch(83.768% 0.001 17.911)", "--color-primary": "oklch(68.628% 0.185 148.958)", "--color-primary-content": "oklch(0% 0 0)", "--color-secondary": "oklch(69.776% 0.135 168.327)", "--color-secondary-content": "oklch(13.955% 0.027 168.327)", "--color-accent": "oklch(70.628% 0.119 185.713)", "--color-accent-content": "oklch(14.125% 0.023 185.713)", "--color-neutral": "oklch(30.698% 0.039 171.364)", "--color-neutral-content": "oklch(86.139% 0.007 171.364)", "--color-info": "oklch(72.06% 0.191 231.6)", "--color-info-content": "oklch(0% 0 0)", "--color-success": "oklch(64.8% 0.15 160)", "--color-success-content": "oklch(0% 0 0)", "--color-warning": "oklch(84.71% 0.199 83.87)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(71.76% 0.221 22.18)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, caramellatte: { "color-scheme": "light", "--color-base-100": "oklch(98% 0.016 73.684)", "--color-base-200": "oklch(95% 0.038 75.164)", "--color-base-300": "oklch(90% 0.076 70.697)", "--color-base-content": "oklch(40% 0.123 38.172)", "--color-primary": "oklch(0% 0 0)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(22.45% 0.075 37.85)", "--color-secondary-content": "oklch(90% 0.076 70.697)", "--color-accent": "oklch(46.44% 0.111 37.85)", "--color-accent-content": "oklch(90% 0.076 70.697)", "--color-neutral": "oklch(55% 0.195 38.402)", "--color-neutral-content": "oklch(98% 0.016 73.684)", "--color-info": "oklch(42% 0.199 265.638)", "--color-info-content": "oklch(90% 0.076 70.697)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.076 70.697)", "--color-warning": "oklch(82% 0.189 84.429)", "--color-warning-content": "oklch(41% 0.112 45.904)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(39% 0.141 25.723)", "--radius-selector": "2rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "2px", "--depth": "1", "--noise": "1" }, dracula: { "color-scheme": "dark", "--color-base-100": "oklch(28.822% 0.022 277.508)", "--color-base-200": "oklch(26.805% 0.02 277.508)", "--color-base-300": "oklch(24.787% 0.019 277.508)", "--color-base-content": "oklch(97.747% 0.007 106.545)", "--color-primary": "oklch(75.461% 0.183 346.812)", "--color-primary-content": "oklch(15.092% 0.036 346.812)", "--color-secondary": "oklch(74.202% 0.148 301.883)", "--color-secondary-content": "oklch(14.84% 0.029 301.883)", "--color-accent": "oklch(83.392% 0.124 66.558)", "--color-accent-content": "oklch(16.678% 0.024 66.558)", "--color-neutral": "oklch(39.445% 0.032 275.524)", "--color-neutral-content": "oklch(87.889% 0.006 275.524)", "--color-info": "oklch(88.263% 0.093 212.846)", "--color-info-content": "oklch(17.652% 0.018 212.846)", "--color-success": "oklch(87.099% 0.219 148.024)", "--color-success-content": "oklch(17.419% 0.043 148.024)", "--color-warning": "oklch(95.533% 0.134 112.757)", "--color-warning-content": "oklch(19.106% 0.026 112.757)", "--color-error": "oklch(68.22% 0.206 24.43)", "--color-error-content": "oklch(13.644% 0.041 24.43)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.031 278.072)", "--color-primary": "oklch(58% 0.158 241.966)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(55% 0.046 257.417)", "--color-secondary-content": "oklch(100% 0 0)", "--color-accent": "oklch(60% 0.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% 0.126 221.723)", "--color-info-content": "oklch(100% 0 0)", "--color-success": "oklch(62% 0.194 149.214)", "--color-success-content": "oklch(100% 0 0)", "--color-warning": "oklch(85% 0.199 91.936)", "--color-warning-content": "oklch(0% 0 0)", "--color-error": "oklch(70% 0.191 22.216)", "--color-error-content": "oklch(0% 0 0)", "--radius-selector": "0.25rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.161 17.53)", "--color-primary-content": "oklch(88.144% 0.032 17.53)", "--color-secondary": "oklch(61.676% 0.169 23.865)", "--color-secondary-content": "oklch(12.335% 0.033 23.865)", "--color-accent": "oklch(73.425% 0.094 60.729)", "--color-accent-content": "oklch(14.685% 0.018 60.729)", "--color-neutral": "oklch(54.367% 0.037 51.902)", "--color-neutral-content": "oklch(90.873% 0.007 51.902)", "--color-info": "oklch(69.224% 0.097 207.284)", "--color-info-content": "oklch(13.844% 0.019 207.284)", "--color-success": "oklch(60.995% 0.08 174.616)", "--color-success-content": "oklch(12.199% 0.016 174.616)", "--color-warning": "oklch(70.081% 0.164 56.844)", "--color-warning-content": "oklch(14.016% 0.032 56.844)", "--color-error": "oklch(53.07% 0.241 24.16)", "--color-error-content": "oklch(90.614% 0.048 24.16)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, 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% 0.11 240.79)", "--color-info-content": "oklch(90% 0.058 230.902)", "--color-success": "oklch(43% 0.095 166.913)", "--color-success-content": "oklch(90% 0.093 164.15)", "--color-warning": "oklch(47% 0.137 46.201)", "--color-warning-content": "oklch(92% 0.12 95.746)", "--color-error": "oklch(44% 0.177 26.899)", "--color-error-content": "oklch(88% 0.062 18.334)", "--radius-selector": "0rem", "--radius-field": "0.25rem", "--radius-box": "0.25rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, abyss: { "color-scheme": "dark", "--color-base-100": "oklch(20% 0.08 209)", "--color-base-200": "oklch(15% 0.08 209)", "--color-base-300": "oklch(10% 0.08 209)", "--color-base-content": "oklch(90% 0.076 70.697)", "--color-primary": "oklch(92% 0.2653 125)", "--color-primary-content": "oklch(50% 0.2653 125)", "--color-secondary": "oklch(83.27% 0.0764 298.3)", "--color-secondary-content": "oklch(43.27% 0.0764 298.3)", "--color-accent": "oklch(43% 0 0)", "--color-accent-content": "oklch(98% 0 0)", "--color-neutral": "oklch(30% 0.08 209)", "--color-neutral-content": "oklch(90% 0.076 70.697)", "--color-info": "oklch(74% 0.16 232.661)", "--color-info-content": "oklch(29% 0.066 243.157)", "--color-success": "oklch(79% 0.209 151.711)", "--color-success-content": "oklch(26% 0.065 152.934)", "--color-warning": "oklch(84.8% 0.1962 84.62)", "--color-warning-content": "oklch(44.8% 0.1962 84.62)", "--color-error": "oklch(65% 0.1985 24.22)", "--color-error-content": "oklch(27% 0.1985 24.22)", "--radius-selector": "2rem", "--radius-field": "0.25rem", "--radius-box": "0.5rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, luxury: { "color-scheme": "dark", "--color-base-100": "oklch(14.076% 0.004 285.822)", "--color-base-200": "oklch(20.219% 0.004 308.229)", "--color-base-300": "oklch(23.219% 0.004 308.229)", "--color-base-content": "oklch(75.687% 0.123 76.89)", "--color-primary": "oklch(100% 0 0)", "--color-primary-content": "oklch(20% 0 0)", "--color-secondary": "oklch(27.581% 0.064 261.069)", "--color-secondary-content": "oklch(85.516% 0.012 261.069)", "--color-accent": "oklch(36.674% 0.051 338.825)", "--color-accent-content": "oklch(87.334% 0.01 338.825)", "--color-neutral": "oklch(24.27% 0.057 59.825)", "--color-neutral-content": "oklch(93.203% 0.089 90.861)", "--color-info": "oklch(79.061% 0.121 237.133)", "--color-info-content": "oklch(15.812% 0.024 237.133)", "--color-success": "oklch(78.119% 0.192 132.154)", "--color-success-content": "oklch(15.623% 0.038 132.154)", "--color-warning": "oklch(86.127% 0.136 102.891)", "--color-warning-content": "oklch(17.225% 0.027 102.891)", "--color-error": "oklch(71.753% 0.176 22.568)", "--color-error-content": "oklch(14.35% 0.035 22.568)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "1", "--noise": "0" }, valentine: { "color-scheme": "light", "--color-base-100": "oklch(97% 0.014 343.198)", "--color-base-200": "oklch(94% 0.028 342.258)", "--color-base-300": "oklch(89% 0.061 343.231)", "--color-base-content": "oklch(52% 0.223 3.958)", "--color-primary": "oklch(65% 0.241 354.308)", "--color-primary-content": "oklch(100% 0 0)", "--color-secondary": "oklch(62% 0.265 303.9)", "--color-secondary-content": "oklch(97% 0.014 308.299)", "--color-accent": "oklch(82% 0.111 230.318)", "--color-accent-content": "oklch(39% 0.09 240.876)", "--color-neutral": "oklch(40% 0.153 2.432)", "--color-neutral-content": "oklch(89% 0.061 343.231)", "--color-info": "oklch(86% 0.127 207.078)", "--color-info-content": "oklch(44% 0.11 240.79)", "--color-success": "oklch(84% 0.143 164.978)", "--color-success-content": "oklch(43% 0.095 166.913)", "--color-warning": "oklch(75% 0.183 55.934)", "--color-warning-content": "oklch(26% 0.079 36.259)", "--color-error": "oklch(63% 0.237 25.331)", "--color-error-content": "oklch(97% 0.013 17.38)", "--radius-selector": "1rem", "--radius-field": "2rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.133 239.443)", "--color-primary-content": "oklch(14.354% 0.026 239.443)", "--color-secondary": "oklch(64.476% 0.202 359.339)", "--color-secondary-content": "oklch(12.895% 0.04 359.339)", "--color-accent": "oklch(94.228% 0.189 105.306)", "--color-accent-content": "oklch(18.845% 0.037 105.306)", "--color-neutral": "oklch(21.778% 0 0)", "--color-neutral-content": "oklch(84.355% 0 0)", "--color-info": "oklch(68.475% 0.094 217.284)", "--color-info-content": "oklch(13.695% 0.018 217.284)", "--color-success": "oklch(46.949% 0.162 321.406)", "--color-success-content": "oklch(89.389% 0.032 321.406)", "--color-warning": "oklch(71.236% 0.159 52.023)", "--color-warning-content": "oklch(14.247% 0.031 52.023)", "--color-error": "oklch(62.013% 0.208 28.717)", "--color-error-content": "oklch(12.402% 0.041 28.717)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, winter: { "color-scheme": "light", "--color-base-100": "oklch(100% 0 0)", "--color-base-200": "oklch(97.466% 0.011 259.822)", "--color-base-300": "oklch(93.268% 0.016 262.751)", "--color-base-content": "oklch(41.886% 0.053 255.824)", "--color-primary": "oklch(56.86% 0.255 257.57)", "--color-primary-content": "oklch(91.372% 0.051 257.57)", "--color-secondary": "oklch(42.551% 0.161 282.339)", "--color-secondary-content": "oklch(88.51% 0.032 282.339)", "--color-accent": "oklch(59.939% 0.191 335.171)", "--color-accent-content": "oklch(11.988% 0.038 335.171)", "--color-neutral": "oklch(19.616% 0.063 257.651)", "--color-neutral-content": "oklch(83.923% 0.012 257.651)", "--color-info": "oklch(88.127% 0.085 214.515)", "--color-info-content": "oklch(17.625% 0.017 214.515)", "--color-success": "oklch(80.494% 0.077 197.823)", "--color-success-content": "oklch(16.098% 0.015 197.823)", "--color-warning": "oklch(89.172% 0.045 71.47)", "--color-warning-content": "oklch(17.834% 0.009 71.47)", "--color-error": "oklch(73.092% 0.11 20.076)", "--color-error-content": "oklch(14.618% 0.022 20.076)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, 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% 0.313 264.052)", "--color-info-content": "oklch(89.04% 0.062 264.052)", "--color-success": "oklch(51.975% 0.176 142.495)", "--color-success-content": "oklch(90.395% 0.035 142.495)", "--color-warning": "oklch(96.798% 0.211 109.769)", "--color-warning-content": "oklch(19.359% 0.042 109.769)", "--color-error": "oklch(62.795% 0.257 29.233)", "--color-error-content": "oklch(12.559% 0.051 29.233)", "--radius-selector": "0rem", "--radius-field": "0rem", "--radius-box": "0rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" }, lemonade: { "color-scheme": "light", "--color-base-100": "oklch(98.71% 0.02 123.72)", "--color-base-200": "oklch(91.8% 0.018 123.72)", "--color-base-300": "oklch(84.89% 0.017 123.72)", "--color-base-content": "oklch(19.742% 0.004 123.72)", "--color-primary": "oklch(58.92% 0.199 134.6)", "--color-primary-content": "oklch(11.784% 0.039 134.6)", "--color-secondary": "oklch(77.75% 0.196 111.09)", "--color-secondary-content": "oklch(15.55% 0.039 111.09)", "--color-accent": "oklch(85.39% 0.201 100.73)", "--color-accent-content": "oklch(17.078% 0.04 100.73)", "--color-neutral": "oklch(30.98% 0.075 108.6)", "--color-neutral-content": "oklch(86.196% 0.015 108.6)", "--color-info": "oklch(86.19% 0.047 224.14)", "--color-info-content": "oklch(17.238% 0.009 224.14)", "--color-success": "oklch(86.19% 0.047 157.85)", "--color-success-content": "oklch(17.238% 0.009 157.85)", "--color-warning": "oklch(86.19% 0.047 102.15)", "--color-warning-content": "oklch(17.238% 0.009 102.15)", "--color-error": "oklch(86.19% 0.047 25.85)", "--color-error-content": "oklch(17.238% 0.009 25.85)", "--radius-selector": "1rem", "--radius-field": "0.5rem", "--radius-box": "1rem", "--size-selector": "0.25rem", "--size-field": "0.25rem", "--border": "1px", "--depth": "0", "--noise": "0" } }; // packages/daisyui/base/svg/object.js var object_default2 = { ":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")` } }; @@ -326,8 +326,8 @@ var rootscrolllock_default = ({ addBase, prefix = "" }) => { }; // packages/daisyui/base/rootscrollgutter/object.js -var object_default6 = { ":root": { "--page-scroll-bg-on": `linear-gradient(var(--root-bg), var(--root-bg)) - color-mix(in srgb, var(--root-bg), oklch(0% 0 0) calc(var(--page-has-backdrop, 0) * 40%))`, "--page-scroll-transition-on": "background-color 0.3s ease-out", transition: "var(--page-scroll-transition)", "scrollbar-gutter": ["var(--page-scroll-gutter, unset)", "if(style(--page-has-scroll: 1): var(--page-scroll-gutter, unset) ; else: unset)"] }, ":root:root": { background: "var(--page-scroll-bg, var(--root-bg, var(--color-base-100)))" }, "@keyframes set-page-has-scroll": { "0%, to": { "--page-has-scroll": "1" } } }; +var object_default6 = { ":root": { background: "var(--page-scroll-bg, var(--root-bg))", "--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%))`, "--page-scroll-transition-on": "background-color 0.3s ease-out", transition: "var(--page-scroll-transition)", "scrollbar-gutter": ["var(--page-scroll-gutter, unset)", "if(style(--page-has-scroll: 1): var(--page-scroll-gutter, unset) ; else: unset)"] }, "@keyframes set-page-has-scroll": { "0%, to": { "--page-has-scroll": "1" } } }; // packages/daisyui/base/rootscrollgutter/index.js var rootscrollgutter_default = ({ addBase, prefix = "" }) => { @@ -336,7 +336,7 @@ var rootscrollgutter_default = ({ addBase, prefix = "" }) => { }; // packages/daisyui/base/rootcolor/object.js -var object_default7 = { ":root, [data-theme]": { "background-color": "var(--root-bg, var(--color-base-100))", color: "var(--color-base-content)" }, ":where(:root, [data-theme])": { "--root-bg": "var(--color-base-100)" } }; +var object_default7 = { ":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)" } }; // packages/daisyui/base/rootcolor/index.js var rootcolor_default = ({ addBase, prefix = "" }) => { @@ -382,9 +382,9 @@ var dropdown_default = ({ addComponents, prefix = "" }) => { // packages/daisyui/components/tab/object.js var object_default12 = { ".tabs": { "@layer daisyui.l1.l2.l3": { display: "flex", "flex-wrap": "wrap", "--tabs-height": "auto", "--tabs-direction": "row", "--tab-height": "calc(var(--size-field, 0.25rem) * 10)", height: "var(--tabs-height)", "flex-direction": "var(--tabs-direction)" } }, ".tab": { "@layer daisyui.l1.l2.l3": { position: "relative", display: "inline-flex", cursor: "pointer", appearance: "none", "flex-wrap": "wrap", "align-items": "center", "justify-content": "center", "text-align": "center", "webkit-user-select": "none", "user-select": "none", "&:hover": { "@media (hover: hover)": { color: "var(--color-base-content)" } }, "--tab-p": "0.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(0.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(calc(69% - var(--border)) + 0.25px), - var(--tab-border-color) calc(calc(69% - var(--border)) + var(--border)), - var(--tab-bg) calc(calc(69% - var(--border)) + var(--border) + 0.25px)`, "border-color": "#0000", order: "var(--tab-order)", height: "var(--tab-height)", "font-size": "0.875rem", "padding-inline-start": "var(--tab-p)", "padding-inline-end": "var(--tab-p)", '&:is(input[type="radio"])': { "min-width": "fit-content", "&:after": { "--tw-content": "attr(aria-label)", content: "var(--tw-content)" } }, "&:is(label)": { position: "relative", input: { position: "absolute", inset: "calc(0.25rem * 0)", cursor: "pointer", appearance: "none", opacity: "0%" } }, '&:checked, &:is(label:has(:checked)), &:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "& + .tab-content": { display: "block" } }, '&: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)" }, "&:not(input):empty": { "flex-grow": 1, cursor: "default" }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible, &:is(label:has(:checked:focus-visible))": { outline: "2px solid currentColor", "outline-offset": "-5px" }, "&[disabled]": { "pointer-events": "none", opacity: "40%" } } }, ".tab-content": { "@layer daisyui.l1.l2.l3": { order: [1, "var(--tabcontent-order)"], display: "none", "border-color": "transparent", "--tabcontent-radius-ss": "var(--radius-box)", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)", "--tabcontent-order": "1", width: "100%", height: "calc(100% - var(--tab-height) + var(--border))", margin: "var(--tabcontent-margin)", "border-width": "var(--border)", "border-start-start-radius": "var(--tabcontent-radius-ss)", "border-start-end-radius": "var(--tabcontent-radius-se)", "border-end-start-radius": "var(--tabcontent-radius-es)", "border-end-end-radius": "var(--tabcontent-radius-ee)" } }, ".tab-disabled": { "@layer daisyui.l1.l2": { "pointer-events": "none", opacity: "40%" } }, ".tabs-border": { "@layer daisyui.l1.l2": { "> .tab": { "--tab-border-color": "#0000 #0000 var(--tab-border-color) #0000", position: "relative", "border-radius": "var(--radius-field)", "&:before": { content: '""', "background-color": "var(--tab-border-color)", transition: "background-color 0.2s ease", width: "80%", height: "3px", "border-radius": "var(--radius-field)", bottom: "0", left: "10%", position: "absolute" }, '&:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), &:is(input:checked), &:is(label:has(:checked))': { "&:before": { "--tab-border-color": "currentColor", "border-top": "3px solid" } } } } }, ".tabs-lift": { "@layer daisyui.l1.l2": { "--tabs-height": "auto", "--tabs-direction": "row", "> .tab": { "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "var(--tab-radius-limit)", "--tab-radius-se": "var(--tab-radius-limit)", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + var(--tab-radius-limit) * 2)", "--tab-corner-height": "var(--tab-radius-limit)", "--tab-corner-position": "top left, top right", "border-width": "var(--tab-border)", "border-start-start-radius": "var(--tab-radius-ss)", "border-start-end-radius": "var(--tab-radius-se)", "border-end-start-radius": "var(--tab-radius-es)", "border-end-end-radius": "var(--tab-radius-ee)", padding: "var(--tab-paddings)", "border-color": "var(--tab-border-colors)", '&:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), &:is(input:checked, label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 + var(--tab-border-color) calc(69% - var(--border) + 0.25px), + var(--tab-border-color) 69%, + var(--tab-bg) calc(69% + 0.25px)`, "border-color": "#0000", order: "var(--tab-order)", height: "var(--tab-height)", "font-size": "0.875rem", "padding-inline": "var(--tab-p)", '&:is(input[type="radio"])': { "min-width": "fit-content", "&:after": { "--tw-content": "attr(aria-label)", content: "var(--tw-content)" } }, "&:is(label)": { position: "relative", input: { position: "absolute", inset: "calc(0.25rem * 0)", cursor: "pointer", appearance: "none", opacity: "0%" } }, '&:checked, &:is(label:has(:checked)), &:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "& + .tab-content": { display: "block" } }, '&: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)" }, "&:not(input):empty": { "flex-grow": 1, cursor: "default" }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible, &:is(label:has(:checked:focus-visible))": { outline: "2px solid currentColor", "outline-offset": "-5px" }, "&[disabled]": { "pointer-events": "none", opacity: "40%" } } }, ".tab-content": { "@layer daisyui.l1.l2.l3": { order: [1, "var(--tabcontent-order)"], display: "none", "border-color": "transparent", "--tabcontent-radius-ss": "var(--radius-box)", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)", "--tabcontent-order": "1", width: "100%", height: "calc(100% - var(--tab-height) + var(--border))", margin: "var(--tabcontent-margin)", "border-width": "var(--border)", "border-start-start-radius": "var(--tabcontent-radius-ss)", "border-start-end-radius": "var(--tabcontent-radius-se)", "border-end-start-radius": "var(--tabcontent-radius-es)", "border-end-end-radius": "var(--tabcontent-radius-ee)" } }, ".tab-disabled": { "@layer daisyui.l1.l2": { "pointer-events": "none", opacity: "40%" } }, ".tabs-border": { "@layer daisyui.l1.l2": { "> .tab": { "--tab-border-color": "#0000 #0000 var(--tab-border-color) #0000", position: "relative", "border-radius": "var(--radius-field)", "&:before": { content: '""', "background-color": "var(--tab-border-color)", transition: "background-color 0.2s ease", width: "80%", height: "3px", "border-radius": "var(--radius-field)", bottom: "0", left: "10%", position: "absolute" }, '&:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), &:is(input:checked), &:is(label:has(:checked))': { "&:before": { "--tab-border-color": "currentColor", "border-top": "3px solid" } } } } }, ".tabs-lift": { "@layer daisyui.l1.l2": { "--tabs-height": "auto", "--tabs-direction": "row", "> .tab": { "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "var(--tab-radius-limit)", "--tab-radius-se": "var(--tab-radius-limit)", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + var(--tab-radius-limit) * 2)", "--tab-corner-height": "var(--tab-radius-limit)", "--tab-corner-position": "top left, top right", "border-width": "var(--tab-border)", "border-start-start-radius": "var(--tab-radius-ss)", "border-start-end-radius": "var(--tab-radius-se)", "border-end-start-radius": "var(--tab-radius-es)", "border-end-end-radius": "var(--tab-radius-ee)", padding: "var(--tab-paddings)", "border-color": "var(--tab-border-colors)", '&:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), &:is(input:checked, label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 var(--tab-border-color)`, "--tab-paddings": `0 calc(var(--tab-p) - var(--border)) var(--border) calc(var(--tab-p) - var(--border))`, "--tab-inset": "auto auto 0 auto", "--radius-start": "radial-gradient(circle at top left, var(--tab-radius-grad))", "--radius-end": "radial-gradient(circle at top right, var(--tab-radius-grad))", "background-color": "var(--tab-bg)", "&:before": { "z-index": 1, content: '""', display: "block", position: "absolute", width: "var(--tab-corner-width)", height: "var(--tab-corner-height)", "background-position": "var(--tab-corner-position)", "background-image": "var(--radius-start), var(--radius-end)", "background-size": "var(--tab-radius-limit) var(--tab-radius-limit)", "background-repeat": "no-repeat", inset: "var(--tab-inset)" }, "&:first-child:before": { "--radius-start": "none" }, '[dir="rtl"] &:first-child:before': { transform: "rotateY(180deg)" }, "&:last-child:before": { "--radius-end": "none" }, '[dir="rtl"] &:last-child:before': { transform: "rotateY(180deg)" } } }, "&:has(> .tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 var(--tab-border-color)` } } }, "> .tab-content": { "--tabcontent-margin": "calc(-1 * var(--border)) 0 0 0", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "& + .tab-content": { "&:nth-child(1), &:nth-child(n + 3)": { "--tabcontent-radius-ss": "var(--radius-box)" } } } } }, ".tabs-top": { "@layer daisyui.l1.l2": { "--tabs-height": "auto", "--tabs-direction": "row", "> .tab": { "--tab-order": "0", "--tab-border": "0 0 var(--border) 0", "--tab-radius-ss": "var(--tab-radius-limit)", "--tab-radius-se": "var(--tab-radius-limit)", "--tab-radius-es": "0", "--tab-radius-ee": "0", "--tab-paddings": "var(--border) var(--tab-p) 0 var(--tab-p)", "--tab-border-colors": "#0000 #0000 var(--tab-border-color) #0000", "--tab-corner-width": "calc(100% + var(--tab-radius-limit) * 2)", "--tab-corner-height": "var(--tab-radius-limit)", "--tab-corner-position": "top left, top right", '&:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), &:is(input:checked), &:is(label:has(:checked))': { "--tab-border": "var(--border) var(--border) 0 var(--border)", "--tab-border-colors": `var(--tab-border-color) var(--tab-border-color) #0000 @@ -393,7 +393,7 @@ var object_default12 = { ".tabs": { "@layer daisyui.l1.l2.l3": { display: "flex" var(--tab-border-color)` } } }, "> .tab-content": { "--tabcontent-order": "1", "--tabcontent-margin": "calc(-1 * var(--border)) 0 0 0", "--tabcontent-radius-ss": "0", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "var(--radius-box)", "--tabcontent-radius-ee": "var(--radius-box)" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "& + .tab-content": { "&:nth-child(1), &:nth-child(n + 3)": { "--tabcontent-radius-ss": "var(--radius-box)" } } } } }, ".tabs-bottom": { "@layer daisyui.l1.l2": { "--tabs-height": "auto", "--tabs-direction": "row", "> .tab": { "--tab-order": "1", "--tab-border": "var(--border) 0 0 0", "--tab-radius-ss": "0", "--tab-radius-se": "0", "--tab-radius-es": "var(--tab-radius-limit)", "--tab-radius-ee": "var(--tab-radius-limit)", "--tab-border-colors": "var(--tab-border-color) #0000 #0000 #0000", "--tab-paddings": "0 var(--tab-p) var(--border) var(--tab-p)", "--tab-corner-width": "calc(100% + var(--tab-radius-limit) * 2)", "--tab-corner-height": "var(--tab-radius-limit)", "--tab-corner-position": "top left, top right", '&:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), &:is(input:checked), &:is(label:has(:checked))': { "--tab-border": "0 var(--border) var(--border) var(--border)", "--tab-border-colors": `#0000 var(--tab-border-color) var(--tab-border-color) var(--tab-border-color)`, "--tab-paddings": `var(--border) calc(var(--tab-p) - var(--border)) 0 calc(var(--tab-p) - var(--border))`, "--tab-inset": "0 auto auto auto", "--radius-start": "radial-gradient(circle at bottom left, var(--tab-radius-grad))", "--radius-end": "radial-gradient(circle at bottom right, var(--tab-radius-grad))" } }, "&:has(> .tab-content)": { "> .tab:first-child": { '&:not(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "--tab-border-colors": `#0000 var(--tab-border-color) var(--tab-border-color) - var(--tab-border-color)` } } }, "> .tab-content": { "--tabcontent-order": "0", "--tabcontent-margin": "0 0 calc(-1 * var(--border)) 0", "--tabcontent-radius-ss": "var(--radius-box)", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "0", "--tabcontent-radius-ee": "var(--radius-box)" }, '> :checked, > :is(label:has(:checked)), > :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "& + .tab-content:not(:nth-child(2))": { "--tabcontent-radius-es": "var(--radius-box)" } } } }, ".tabs-box": { "@layer daisyui.l1.l2": { "background-color": "var(--color-base-200)", padding: "calc(0.25rem * 1)", "--tabs-box-radius": "calc(3 * var(--radius-field))", "border-radius": "calc( min(calc(var(--tab-height) / 2), var(--radius-field)) + min(0.25rem, var(--tabs-box-radius)) )", "box-shadow": "0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0.5px oklch(0% 0 0 / calc(var(--depth) * 0.05)) inset", "> .tab": { "border-radius": "var(--radius-field)", "border-style": "none", "&:focus-visible, &:is(label:has(:checked:focus-visible))": { "outline-offset": "2px" } }, '> :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), > :is(input:checked), > :is(label:has(:checked))': { "background-color": "var(--tab-bg, var(--color-base-100))", "box-shadow": "0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000)", "@media (forced-colors: active)": { border: "1px solid" } }, "> .tab-content": { "margin-top": "calc(0.25rem * 1)", height: "calc(100% - var(--tab-height) + var(--border) - 0.5rem)", "border-radius": "calc( min(calc(var(--tab-height) / 2), var(--radius-field)) + min(0.25rem, var(--tabs-box-radius)) - var(--border) )" } } }, ".tabs-xs": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 6)", "& > .tab": { "font-size": "0.75rem", "--tab-p": "0.375rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } } }, ".tabs-sm": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 8)", "& > .tab": { "font-size": "0.875rem", "--tab-p": "0.5rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } } }, ".tabs-md": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 10)", "& > .tab": { "font-size": "0.875rem", "--tab-p": "0.75rem", "--tab-radius-min": "calc(0.75rem - var(--border))" } } }, ".tabs-lg": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 12)", "& > .tab": { "font-size": "1.125rem", "--tab-p": "1rem", "--tab-radius-min": "calc(1.5rem - var(--border))" } } }, ".tabs-xl": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 14)", "& > .tab": { "font-size": "1.125rem", "--tab-p": "1.25rem", "--tab-radius-min": "calc(2rem - var(--border))" } } } }; + var(--tab-border-color)` } } }, "> .tab-content": { "--tabcontent-order": "0", "--tabcontent-margin": "0 0 calc(-1 * var(--border)) 0", "--tabcontent-radius-ss": "var(--radius-box)", "--tabcontent-radius-se": "var(--radius-box)", "--tabcontent-radius-es": "0", "--tabcontent-radius-ee": "var(--radius-box)" }, '> :checked, > :is(label:has(:checked)), > :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"])': { "& + .tab-content:not(:nth-child(2))": { "--tabcontent-radius-es": "var(--radius-box)" } } } }, ".tabs-box": { "@layer daisyui.l1.l2": { "background-color": "var(--color-base-200)", padding: "calc(0.25rem * 1)", "--tabs-box-radius": "calc(3 * var(--radius-field))", "border-radius": "calc( min(var(--tab-height) / 2, var(--radius-field)) + min(0.25rem, var(--tabs-box-radius)) )", "box-shadow": "0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0.5px oklch(0% 0 0 / calc(var(--depth) * 0.05)) inset", "> .tab": { "border-radius": "var(--radius-field)", "border-style": "none", "&:focus-visible, &:is(label:has(:checked:focus-visible))": { "outline-offset": "2px" }, "&:focus-visible": { "z-index": 1 } }, '> :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), > :is(input:checked), > :is(label:has(:checked))': { "background-color": "var(--tab-bg, var(--color-base-100))", "box-shadow": "0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000)", "@media (forced-colors: active)": { border: "1px solid" } }, "> .tab-content": { "margin-top": "calc(0.25rem * 1)", height: "calc(100% - var(--tab-height) + var(--border) - 0.5rem)", "border-radius": "calc( min(var(--tab-height) / 2, var(--radius-field)) + min(0.25rem, var(--tabs-box-radius)) - var(--border) )" } } }, ".tabs-xs": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 6)", "& > .tab": { "font-size": "0.75rem", "--tab-p": "0.375rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } } }, ".tabs-sm": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 8)", "& > .tab": { "font-size": "0.875rem", "--tab-p": "0.5rem", "--tab-radius-min": "calc(0.5rem - var(--border))" } } }, ".tabs-md": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 10)", "& > .tab": { "font-size": "0.875rem", "--tab-p": "0.75rem", "--tab-radius-min": "calc(0.75rem - var(--border))" } } }, ".tabs-lg": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 12)", "& > .tab": { "font-size": "1.125rem", "--tab-p": "1rem", "--tab-radius-min": "calc(1.5rem - var(--border))" } } }, ".tabs-xl": { "@layer daisyui.l1.l2": { "--tab-height": "calc(var(--size-field, 0.25rem) * 14)", "& > .tab": { "font-size": "1.125rem", "--tab-p": "1.25rem", "--tab-radius-min": "calc(2rem - var(--border))" } } } }; // packages/daisyui/components/tab/index.js var tab_default = ({ addComponents, prefix = "" }) => { @@ -439,7 +439,7 @@ var checkbox_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/select/object.js -var object_default17 = { ".select": { "@layer daisyui.l1.l2.l3": { border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 1.5)", "background-color": "var(--color-base-100)", "padding-inline-start": "calc(0.25rem * 3)", "padding-inline-end": "calc(0.25rem * 7)", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "0.875rem", "touch-action": "manipulation", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "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-size": "4px 4px, 4px 4px", "background-repeat": "no-repeat", "white-space": "nowrap", overflow: "hidden", "text-overflow": "ellipsis", "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) * 0.1)) inset", "border-color": "var(--input-color)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-field, 0.25rem) * 10)", '[dir="rtl"] &': { "background-position": "calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%)", "&::picker(select), select::picker(select)": { translate: "0.5rem 0" } }, "&[multiple]": { height: "auto", overflow: "auto", "padding-block": "calc(0.25rem * 3)", "padding-inline-end": "calc(0.25rem * 3)", "background-image": "none" }, select: { "margin-inline-start": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -7)", width: "calc(100% + 2.75rem)", appearance: "none", "padding-inline-start": "calc(0.25rem * 3)", "padding-inline-end": "calc(0.25rem * 7)", height: "calc(100% - calc(var(--border) * 2))", "align-items": "center", background: "inherit", "border-radius": "inherit", "border-style": "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:not(:last-child)": { "margin-inline-end": "calc(0.25rem * -5.5)", "background-image": "none" } }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate", "z-index": 1 }, "&:has(> select[disabled]), &:is(:disabled, [disabled]), fieldset:disabled &": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" } }, "&:has(> select[disabled]) > select[disabled]": { cursor: "not-allowed" }, "&, & select": { "@supports (appearance: base-select)": [{ appearance: "base-select" }, { "&::picker(select)": { appearance: "base-select" } }], "&::picker(select)": { color: "inherit", "max-height": "min(24rem, 70dvh)", "margin-inline": "0.5rem", translate: "-0.5rem 0", border: "var(--border) solid var(--color-base-200)", "margin-block": "calc(0.25rem * 2)", "border-radius": "var(--radius-box)", padding: "calc(0.25rem * 2)", "background-color": "inherit", "box-shadow": ["0 2px calc(var(--depth) * 3px) -2px oklch(0% 0 0/0.2)", "0 20px 25px -5px rgb(0 0 0 / calc(var(--depth) * 0.1)), 0 8px 10px -6px rgb(0 0 0 / calc(var(--depth) * 0.1))"] }, "&::picker-icon": { display: "none" }, optgroup: { "padding-top": "0.5em", option: { "&:nth-child(1)": { "margin-top": "0.5em" } } }, option: { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)", "transition-property": "color, background-color", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "white-space": "normal", "&:not(:disabled)": { "&:hover, &:focus-visible": { cursor: "pointer", "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:active": { "background-color": "var(--color-neutral)", color: "var(--color-neutral-content)", "box-shadow": "0 2px calc(var(--depth) * 3px) -2px var(--color-neutral)" } } } } } }, ".select-ghost": { "@layer daisyui.l1.l2": { "background-color": "transparent", transition: "background-color 0.2s", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } } }, ".select-neutral": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } } }, ".select-primary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } } }, ".select-secondary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } } }, ".select-accent": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } } }, ".select-info": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } } }, ".select-success": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } } }, ".select-warning": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } } }, ".select-error": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } } }, ".select-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem", option: { "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)" } } }, ".select-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem", option: { "padding-inline": "calc(0.25rem * 2.5)", "padding-block": "calc(0.25rem * 1)" } } }, ".select-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", option: { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)" } } }, ".select-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", option: { "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 1.5)" } } }, ".select-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.375rem", option: { "padding-inline": "calc(0.25rem * 5)", "padding-block": "calc(0.25rem * 1.5)" } } } }; +var object_default17 = { ".select": { "@layer daisyui.l1.l2.l3": { border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 1.5)", "background-color": "var(--color-base-100)", "padding-inline-start": "calc(0.25rem * 3)", "padding-inline-end": "calc(0.25rem * 7)", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "0.875rem", "touch-action": "manipulation", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "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-size": "4px 4px, 4px 4px", "background-repeat": "no-repeat", "white-space": "nowrap", overflow: "hidden", "text-overflow": "ellipsis", "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) * 0.1)) inset", "border-color": "var(--input-color)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-field, 0.25rem) * 10)", '[dir="rtl"] &': { "background-position": "calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%)", "&::picker(select), select::picker(select)": { translate: "0.5rem 0" } }, "&[multiple]": { height: "auto", overflow: "auto", "padding-block": "calc(0.25rem * 3)", "padding-inline-end": "calc(0.25rem * 3)", "background-image": "none" }, select: { "margin-inline-start": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -7)", width: "calc(100% + 2.75rem)", appearance: "none", "padding-inline-start": "calc(0.25rem * 3)", "padding-inline-end": "calc(0.25rem * 7)", height: "calc(100% - calc(var(--border) * 2))", "align-items": "center", background: "inherit", "border-radius": "inherit", "border-style": "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:not(:last-child)": { "margin-inline-end": "calc(0.25rem * -5.5)", "background-image": "none" } }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate" }, "&:has(> select[disabled]), &:is(:disabled, [disabled]), fieldset:disabled &": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" } }, "&:has(> select[disabled]) > select[disabled]": { cursor: "not-allowed" }, "&, & select": { "@supports (appearance: base-select)": [{ appearance: "base-select" }, { "&::picker(select)": { appearance: "base-select" } }], "&::picker(select)": { color: "inherit", "max-height": "min(24rem, 70dvh)", "margin-inline": "0.5rem", translate: "-0.5rem 0", border: "var(--border) solid var(--color-base-200)", "margin-block": "calc(0.25rem * 2)", "border-radius": "var(--radius-box)", padding: "calc(0.25rem * 2)", "background-color": "inherit", "box-shadow": ["0 2px calc(var(--depth) * 3px) -2px oklch(0% 0 0/0.2)", "0 20px 25px -5px rgb(0 0 0 / calc(var(--depth) * 0.1)), 0 8px 10px -6px rgb(0 0 0 / calc(var(--depth) * 0.1))"] }, "&::picker-icon": { display: "none" }, optgroup: { "padding-top": "0.5em", option: { "&:nth-child(1)": { "margin-top": "0.5em" } } }, option: { "border-radius": "var(--radius-field)", "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)", "transition-property": "color, background-color", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "white-space": "normal", "&:not(:disabled)": { "&:hover, &:focus-visible": { cursor: "pointer", "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:active": { "background-color": "var(--color-neutral)", color: "var(--color-neutral-content)", "box-shadow": "0 2px calc(var(--depth) * 3px) -2px var(--color-neutral)" } } } } } }, ".select-ghost": { "@layer daisyui.l1.l2": { "background-color": "transparent", transition: "background-color 0.2s", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } } }, ".select-neutral": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } } }, ".select-primary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } } }, ".select-secondary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } } }, ".select-accent": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } } }, ".select-info": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } } }, ".select-success": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } } }, ".select-warning": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } } }, ".select-error": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } } }, ".select-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "0.6875rem", option: { "padding-inline": "calc(0.25rem * 2)", "padding-block": "calc(0.25rem * 1)" } } }, ".select-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "0.75rem", option: { "padding-inline": "calc(0.25rem * 2.5)", "padding-block": "calc(0.25rem * 1)" } } }, ".select-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "0.875rem", option: { "padding-inline": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1.5)" } } }, ".select-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "1.125rem", option: { "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 1.5)" } } }, ".select-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "1.375rem", option: { "padding-inline": "calc(0.25rem * 5)", "padding-block": "calc(0.25rem * 1.5)" } } } }; // packages/daisyui/components/select/index.js var select_default = ({ addComponents, prefix = "" }) => { @@ -520,7 +520,7 @@ var divider_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/button/object.js -var object_default26 = { ":where(.btn)": { "@layer daisyui.l1.l2.l3": { width: "unset" } }, '.prose :where(.btn-link):not(:where([class~="not-prose"], [class~="not-prose"] *))': { "text-decoration-line": "none" }, ".btn": { "@layer daisyui.l1.l2.l3": { display: "inline-flex", "flex-shrink": 0, cursor: "pointer", "flex-wrap": "nowrap", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 1.5)", "text-align": "center", "vertical-align": "middle", "outline-offset": "2px", "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, 0.875rem)", "font-weight": 600, "outline-color": "var(--btn-color, var(--color-base-content))", "transition-property": "color, background-color, border-color, box-shadow", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "transition-duration": "0.2s", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "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 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15))", "touch-action": "manipulation", "box-shadow": "0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow)", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--btn-bg": "var(--btn-color, var(--color-base-200))", "--btn-fg": "var(--color-base-content)", "--btn-p": "1rem", "--btn-border": "color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%))", "--btn-shadow": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000), +var object_default26 = { ":where(.btn)": { "@layer daisyui.l1.l2.l3": { width: "unset" } }, '.prose :where(a.btn:not(.btn-link)):not(:where([class~="not-prose"], [class~="not-prose"] *))': { "text-decoration-line": "none" }, ".btn": { "@layer daisyui.l1.l2.l3": { display: "inline-flex", "flex-shrink": 0, cursor: "pointer", "flex-wrap": "nowrap", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 1.5)", "text-align": "center", "vertical-align": "middle", "outline-offset": "2px", "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, 0.875rem)", "font-weight": 600, "outline-color": "var(--btn-color, var(--color-base-content))", "transition-property": "color, background-color, border-color, box-shadow", "transition-timing-function": "cubic-bezier(0, 0, 0.2, 1)", "transition-duration": "0.2s", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "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 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15))", "touch-action": "manipulation", "box-shadow": "0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow)", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--btn-bg": "var(--btn-color, var(--color-base-200))", "--btn-fg": "var(--color-base-content)", "--btn-p": "1rem", "--btn-border": "color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%))", "--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-noise": "var(--fx-noise)", "@media (hover: hover)": { "&:hover": { "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)" } }, "&:focus-visible, &:has(:focus-visible)": { "outline-width": "2px", "outline-style": "solid", isolation: "isolate" }, "&:active:not(.btn-active)": { translate: "0 0.5px", "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%)", "--btn-border": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)", "--btn-shadow": "0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)" }, '&:is(input[type="checkbox"], input[type="radio"])': { appearance: "none", "&[aria-label]::after": { "--tw-content": "attr(aria-label)", content: "var(--tw-content)" } }, "&:where(input:checked:not(.filter .btn))": { "--btn-color": "var(--color-primary)", "--btn-fg": "var(--color-primary-content)", isolation: "isolate" } } }, ".btn-disabled, .btn:disabled, .btn[disabled]": { "@layer daisyui.l1.l2": { "&:not(.btn-link, .btn-ghost)": { "background-color": "color-mix(in oklab, var(--color-base-content) 10%, transparent)", "box-shadow": "none" }, "pointer-events": "none", "--btn-border": "#0000", "--btn-noise": "none", "--btn-fg": "color-mix(in oklch, var(--color-base-content) 20%, #0000)" } }, ".btn-active": { "@layer daisyui.l1.l2": { "--btn-bg": "color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)", "--btn-shadow": "0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)", isolation: "isolate" } }, ".btn-primary": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-primary)", "--btn-fg": "var(--color-primary-content)" } }, ".btn-secondary": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-secondary)", "--btn-fg": "var(--color-secondary-content)" } }, ".btn-accent": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-accent)", "--btn-fg": "var(--color-accent-content)" } }, ".btn-neutral": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-neutral)", "--btn-fg": "var(--color-neutral-content)" } }, ".btn-info": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-info)", "--btn-fg": "var(--color-info-content)" } }, ".btn-success": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-success)", "--btn-fg": "var(--color-success-content)" } }, ".btn-warning": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-warning)", "--btn-fg": "var(--color-warning-content)" } }, ".btn-error": { "@layer daisyui.l1.l2.l3": { "--btn-color": "var(--color-error)", "--btn-fg": "var(--color-error-content)" } }, ".btn-ghost": { "@layer daisyui.l1": { "&:not(.btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn))": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-border": "#0000", "--btn-noise": "none", "&:not(:disabled, [disabled], .btn-disabled)": { "outline-color": "currentcolor", "--btn-fg": "var(--btn-color, currentColor)" } }, "@media (hover: none)": { "&:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover": { "outline-color": "currentcolor", "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color, currentColor)", "--btn-border": "#0000", "--btn-noise": "none" } } } }, ".btn-link": { "@layer daisyui.l1": { "text-decoration-line": "underline", "outline-color": "currentcolor", "--btn-border": "#0000", "--btn-bg": "#0000", "--btn-noise": "none", "--btn-shadow": '""', "&:not(.btn-disabled, .btn:disabled, .btn[disabled])": { "--btn-fg": "var(--btn-color, var(--color-primary))" }, "&:is(.btn-active, :hover, :active:focus, :focus-visible)": { "--btn-border": "#0000", "--btn-bg": "#0000" } } }, ".btn-outline, .btn-dash": { "@layer daisyui.l1": { "&:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled )": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" }, "@media (hover: none)": { "&:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover": { "--btn-shadow": '""', "--btn-bg": "#0000", "--btn-fg": "var(--btn-color)", "--btn-border": "var(--btn-color)", "--btn-noise": "none" } } } }, ".btn-dash": { "border-style": "dashed" }, ".btn-soft": { "@layer daisyui.l1": { "&:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled )": { "--btn-shadow": '""', "--btn-fg": "var(--btn-color, var(--color-base-content))", "--btn-bg": `color-mix( in oklab, var(--btn-color, var(--color-base-content)) 8%, @@ -613,7 +613,7 @@ var card_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/drawer/object.js -var object_default34 = { ".drawer": { "@layer daisyui.l1.l2.l3": { position: "relative", display: "grid", width: "100%", "grid-auto-columns": "max-content auto" } }, ".drawer-content": { "@layer daisyui.l1.l2.l3": { "grid-column-start": "2", "grid-row-start": "1", "min-width": "calc(0.25rem * 0)" } }, ":where(.drawer-side)": { "@layer daisyui.l1.l2.l3": { "overflow-x": "hidden", "overflow-y": "hidden" } }, ".drawer-side": { "@layer daisyui.l1.l2.l3": { "pointer-events": "none", visibility: "hidden", position: "fixed", "inset-inline-start": "calc(0.25rem * 0)", top: "calc(0.25rem * 0)", "z-index": 10, "grid-column-start": "1", "grid-row-start": "1", display: "grid", width: "100%", "grid-template-columns": "repeat(1, minmax(0, 1fr))", "grid-template-rows": "repeat(1, minmax(0, 1fr))", "align-items": "flex-start", "justify-items": "start", "overscroll-behavior": "contain", "background-color": "transparent", opacity: "0%", transition: "opacity 0.2s ease-out 0.1s allow-discrete, visibility 0.3s ease-out 0.1s allow-discrete", height: ["100vh", "100dvh"], "> .drawer-overlay": { position: "sticky", top: "calc(0.25rem * 0)", cursor: "pointer", "place-self": "stretch", "background-color": "oklch(0% 0 0 / 40%)" }, "> *": { "grid-column-start": "1", "grid-row-start": "1" }, "> *:not(.drawer-overlay)": { "will-change": "transform", transition: "translate 0.3s ease-out, width 0.2s ease-out", translate: "-100%", '[dir="rtl"] &': { translate: "100%" } } } }, ".drawer-toggle": { "@layer daisyui.l1.l2.l3": { position: "fixed", height: "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", appearance: "none", opacity: "0%", ":where(&:checked ~ .drawer-side)": { "scrollbar-color": "color-mix(in oklch, currentColor 35%, #0000) oklch(0 0 0 / calc(var(--page-has-backdrop, 0) * 0.4))" }, ":where(:root:has(&:checked))": { "--page-has-backdrop": "1", "--page-overflow": "hidden", "--page-scroll-bg": "var(--page-scroll-bg-on)", "--page-scroll-gutter": "stable", "--page-scroll-transition": "var(--page-scroll-transition-on)", animation: "set-page-has-scroll forwards", "animation-timeline": "scroll()" } }, "@layer daisyui.l1.l2": { ":where(&:checked ~ .drawer-side)": { "pointer-events": "auto", visibility: "visible", "overflow-y": "auto", opacity: "100%", "& > *:not(.drawer-overlay)": { translate: "0%" } }, "&:focus-visible ~ .drawer-content label.drawer-button": { outline: "2px solid", "outline-offset": "2px" } } }, ".drawer-end": { "@layer daisyui.l1.l2": { "grid-auto-columns": "auto max-content", "> .drawer-toggle": { "& ~ .drawer-content": { "grid-column-start": "1" }, "& ~ .drawer-side": { "grid-column-start": "2", "justify-items": "end" }, "& ~ .drawer-side > *:not(.drawer-overlay)": { translate: "100%", '[dir="rtl"] &': { translate: "-100%" } }, "&:checked ~ .drawer-side > *:not(.drawer-overlay)": { translate: "0%" } } } }, ".drawer-open": { "@layer daisyui.l1.l2.l3": { "& > .drawer-toggle:checked": { "& ~ .drawer-side": { "scrollbar-color": "revert-layer" }, ":root:has(&)": { "--page-overflow": "revert-layer", "--page-scroll-gutter": "revert-layer", "--page-scroll-bg": "revert-layer", "--page-scroll-transition": "revert-layer", "--page-has-backdrop": "revert-layer", animation: "revert-layer", "animation-timeline": "revert-layer" } } }, "@layer daisyui.l1.l2": { "& > .drawer-side": { "overflow-y": "auto" }, "> .drawer-toggle": { display: "none", "& ~ .drawer-side": { "pointer-events": "auto", visibility: "visible", position: "sticky", display: "block", width: "auto", "overscroll-behavior": "auto", opacity: "100%", "& > .drawer-overlay": { cursor: "default", "background-color": "transparent" }, "& > *:not(.drawer-overlay)": { translate: "0%", '[dir="rtl"] &': { translate: "0%" } } }, "&:checked ~ .drawer-side": { "pointer-events": "auto", visibility: "visible" } } } } }; +var object_default34 = { ".drawer": { "@layer daisyui.l1.l2.l3": { position: "relative", display: "grid", width: "100%", "grid-auto-columns": "max-content auto" } }, ".drawer-content": { "@layer daisyui.l1.l2.l3": { "grid-column-start": "2", "grid-row-start": "1", "min-width": "calc(0.25rem * 0)" } }, ":where(.drawer-side)": { "@layer daisyui.l1.l2.l3": { "overflow-x": "hidden", "overflow-y": "hidden" } }, ".drawer-side": { "@layer daisyui.l1.l2.l3": { "pointer-events": "none", visibility: "hidden", position: "fixed", "inset-inline-start": "calc(0.25rem * 0)", top: "calc(0.25rem * 0)", "z-index": 10, "grid-column-start": "1", "grid-row-start": "1", display: "grid", width: "100%", "grid-template-columns": "repeat(1, minmax(0, 1fr))", "grid-template-rows": "repeat(1, minmax(0, 1fr))", "align-items": "flex-start", "justify-items": "start", "overscroll-behavior": "contain", "background-color": "transparent", opacity: "0%", transition: "opacity 0.2s ease-out 0.1s allow-discrete, visibility 0.3s ease-out 0.1s allow-discrete", height: ["100vh", "100dvh"], "> .drawer-overlay": { position: "sticky", top: "calc(0.25rem * 0)", cursor: "pointer", "place-self": "stretch", "background-color": "oklch(0% 0 0 / 40%)" }, "> *": { "grid-column-start": "1", "grid-row-start": "1" }, "> :not(.drawer-overlay)": { "will-change": "transform", transition: "translate 0.3s ease-out, width 0.2s ease-out", translate: "-100%", '[dir="rtl"] &': { translate: "100%" } } } }, ".drawer-toggle": { "@layer daisyui.l1.l2.l3": { position: "fixed", height: "calc(0.25rem * 0)", width: "calc(0.25rem * 0)", appearance: "none", opacity: "0%", ":where(&:checked ~ .drawer-side)": { "scrollbar-color": "color-mix(in oklch, currentColor 35%, #0000) oklch(0 0 0 / calc(var(--page-has-backdrop, 0) * 0.4))" }, ":where(:root:has(&:checked))": { "--page-has-backdrop": "1", "--page-overflow": "hidden", "--page-scroll-bg": "var(--page-scroll-bg-on)", "--page-scroll-gutter": "stable", "--page-scroll-transition": "var(--page-scroll-transition-on)", animation: "set-page-has-scroll forwards", "animation-timeline": "scroll()" } }, "@layer daisyui.l1.l2": { ":where(&:checked ~ .drawer-side)": { "pointer-events": "auto", visibility: "visible", "overflow-y": "auto", opacity: "100%", "> :not(.drawer-overlay)": { translate: "0%" } }, "&:focus-visible ~ .drawer-content label.drawer-button": { outline: "2px solid", "outline-offset": "2px" } } }, ".drawer-end": { "@layer daisyui.l1.l2": { "grid-auto-columns": "auto max-content", "> .drawer-toggle": { "~ .drawer-content": { "grid-column-start": "1" }, "~ .drawer-side": { "grid-column-start": "2", "justify-items": "end" }, "~ .drawer-side > :not(.drawer-overlay)": { translate: "100%", '[dir="rtl"] &': { translate: "-100%" } }, "&:checked ~ .drawer-side > :not(.drawer-overlay)": { translate: "0%" } } } }, ".drawer-open": { "@layer daisyui.l1.l2.l3": { "> .drawer-toggle:checked": { "~ .drawer-side": { "scrollbar-color": "revert-layer" }, ":root:has(&)": { "--page-overflow": "revert-layer", "--page-scroll-gutter": "revert-layer", "--page-scroll-bg": "revert-layer", "--page-scroll-transition": "revert-layer", "--page-has-backdrop": "revert-layer", animation: "revert-layer", "animation-timeline": "revert-layer" } } }, "@layer daisyui.l1.l2": { "> .drawer-side": { "overflow-y": "auto" }, "> .drawer-toggle": { display: "none", "~ .drawer-side": { "pointer-events": "auto", visibility: "visible", position: "sticky", display: "block", width: "auto", "overscroll-behavior": "auto", opacity: "100%", "> .drawer-overlay": { cursor: "default", "background-color": "transparent" } }, "&:checked ~ .drawer-side": { "pointer-events": "auto", visibility: "visible" } } }, "@layer daisyui.l1": { "> .drawer-toggle ~ .drawer-side > :not(.drawer-overlay)": { translate: "0%", '[dir="rtl"] &': { translate: "0%" } } } } }; // packages/daisyui/components/drawer/index.js var drawer_default = ({ addComponents, prefix = "" }) => { @@ -660,7 +660,7 @@ var menu_default = ({ addComponents, prefix = "" }) => { // packages/daisyui/components/toggle/object.js var object_default39 = { ".toggle": { "@layer daisyui.l1.l2.l3": { border: "var(--border) solid currentColor", color: "var(--input-color)", position: "relative", display: "inline-grid", "flex-shrink": 0, cursor: "pointer", appearance: "none", "place-content": "center", "vertical-align": "middle", "webkit-user-select": "none", "user-select": "none", "grid-template-columns": "0fr 1fr 1fr", "--radius-selector-max": `calc( var(--radius-selector) + var(--radius-selector) + var(--radius-selector) - )`, "border-radius": "calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) )", padding: "var(--toggle-p)", "box-shadow": "0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset", transition: "color 0.3s, grid-template-columns 0.2s", "--input-color": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--toggle-p": "calc(var(--size) * 0.125)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2)", height: "var(--size)", "> *": { "z-index": 1, "grid-column": "span 1 / span 1", "grid-column-start": "2", "grid-row-start": "1", height: "100%", cursor: "pointer", appearance: "none", "background-color": "transparent", padding: "calc(0.25rem * 0.5)", transition: "opacity 0.2s, rotate 0.4s", border: "none", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:nth-child(2)": { color: "var(--color-base-100)", rotate: "0deg" }, "&:nth-child(3)": { color: "var(--color-base-100)", opacity: "0%", rotate: "-15deg" } }, "&:has(:checked)": { "> :nth-child(2)": { opacity: "0%", rotate: "15deg" }, "> :nth-child(3)": { opacity: "100%", rotate: "0deg" } }, "&:before": { position: "relative", "inset-inline-start": "calc(0.25rem * 0)", "grid-column-start": "2", "grid-row-start": "1", "aspect-ratio": "1 / 1", height: "100%", "border-radius": "var(--radius-selector)", "background-color": "currentcolor", translate: "0", "--tw-content": '""', content: "var(--tw-content)", transition: "background-color 0.1s, translate 0.2s, inset-inline-start 0.2s", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)" }, "@media (forced-colors: active)": { "&:before": { "outline-style": "var(--tw-outline-style)", "outline-width": "1px", "outline-offset": "calc(1px * -1)" } }, "@media print": { "&:before": { outline: "0.25rem solid", "outline-offset": "-1rem" } }, "&:focus-visible, &:has(:focus-visible)": { outline: "2px solid currentColor", "outline-offset": "2px" }, '&:checked, &[aria-checked="true"], &:has(> input:checked)': { "grid-template-columns": "1fr 1fr 0fr", "background-color": "var(--color-base-100)", "--input-color": "var(--color-base-content)", "&:before": { "background-color": "currentcolor" }, "@starting-style": { "&:before": { opacity: 0 } } }, "&:indeterminate": { "grid-template-columns": "0.5fr 1fr 0.5fr" }, "&:disabled": { cursor: "not-allowed", opacity: "30%", "&:before": { "background-color": "transparent", border: "var(--border) solid currentColor" } } } }, ".toggle-primary": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-primary)" } } }, ".toggle-secondary": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-secondary)" } } }, ".toggle-accent": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-accent)" } } }, ".toggle-neutral": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-neutral)" } } }, ".toggle-success": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-success)" } } }, ".toggle-warning": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-warning)" } } }, ".toggle-info": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-info)" } } }, ".toggle-error": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-error)" } } }, ".toggle-xs": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 4)" } } }, ".toggle-sm": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 5)" } } }, ".toggle-md": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 6)" } } }, ".toggle-lg": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 7)" } } }, ".toggle-xl": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 8)" } } } }; + )`, "border-radius": "calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) )", padding: "var(--toggle-p)", "box-shadow": "0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset", transition: "color 0.3s, grid-template-columns 0.2s", "--input-color": "color-mix(in oklab, var(--color-base-content) 50%, #0000)", "--toggle-p": "calc(var(--size) * 0.125)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", width: "calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2)", height: "var(--size)", "> *": { "z-index": 1, "grid-column": "span 1 / span 1", "grid-column-start": "2", "grid-row-start": "1", height: "100%", cursor: "pointer", appearance: "none", "background-color": "transparent", padding: "calc(0.25rem * 0.5)", transition: "opacity 0.2s, rotate 0.4s", border: "none", "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:nth-child(2)": { color: "var(--color-base-100)", rotate: "0deg" }, "&:nth-child(3)": { color: "var(--color-base-100)", opacity: "0%", rotate: "-15deg" } }, "&:has(:checked)": { "> :nth-child(2)": { opacity: "0%", rotate: "15deg" }, "> :nth-child(3)": { opacity: "100%", rotate: "0deg" } }, "&:before": { position: "relative", "inset-inline-start": "calc(0.25rem * 0)", "grid-column-start": "2", "grid-row-start": "1", "aspect-ratio": "1 / 1", height: "100%", width: "100%", "border-radius": "var(--radius-selector)", "background-color": "currentcolor", translate: "0", "--tw-content": '""', content: "var(--tw-content)", transition: "background-color 0.1s, translate 0.2s, inset-inline-start 0.2s", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)", "background-size": "auto, calc(var(--noise) * 100%)", "background-image": "none, var(--fx-noise)" }, "@media (forced-colors: active)": { "&:before": { "outline-style": "var(--tw-outline-style)", "outline-width": "1px", "outline-offset": "calc(1px * -1)" } }, "@media print": { "&:before": { outline: "0.25rem solid", "outline-offset": "-1rem" } }, "&:focus-visible, &:has(:focus-visible)": { outline: "2px solid currentColor", "outline-offset": "2px" }, '&:checked, &[aria-checked="true"], &:has(> input:checked)': { "grid-template-columns": "1fr 1fr 0fr", "background-color": "var(--color-base-100)", "--input-color": "var(--color-base-content)", "&:before": { "background-color": "currentcolor" }, "@starting-style": { "&:before": { opacity: 0 } } }, "&:indeterminate": { "grid-template-columns": "0.5fr 1fr 0.5fr" }, "&:disabled": { cursor: "not-allowed", opacity: "30%", "&:before": { "background-color": "transparent", border: "var(--border) solid currentColor" } } } }, ".toggle-primary": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-primary)" } } }, ".toggle-secondary": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-secondary)" } } }, ".toggle-accent": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-accent)" } } }, ".toggle-neutral": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-neutral)" } } }, ".toggle-success": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-success)" } } }, ".toggle-warning": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-warning)" } } }, ".toggle-info": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-info)" } } }, ".toggle-error": { "@layer daisyui.l1.l2": { '&:checked, &[aria-checked="true"]': { "--input-color": "var(--color-error)" } } }, ".toggle-xs": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 4)" } } }, ".toggle-sm": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 5)" } } }, ".toggle-md": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 6)" } } }, ".toggle-lg": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 7)" } } }, ".toggle-xl": { "@layer daisyui.l1.l2": { '&:is([type="checkbox"]), &:has([type="checkbox"])': { "--size": "calc(var(--size-selector, 0.25rem) * 8)" } } } }; // packages/daisyui/components/toggle/index.js var toggle_default = ({ addComponents, prefix = "" }) => { @@ -669,7 +669,7 @@ var toggle_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/badge/object.js -var object_default40 = { ".badge": { "@layer daisyui.l1.l2.l3": { display: "inline-flex", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 2)", "border-radius": "var(--radius-selector)", "vertical-align": "middle", color: "var(--badge-fg)", border: "var(--border) solid var(--badge-color, var(--color-base-200))", "font-size": "0.875rem", width: "fit-content", "padding-inline": "calc(0.25rem * 3 - var(--border))", "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, 0.25rem) * 6)", height: "var(--size)" } }, ".badge-outline": { "@layer daisyui.l1.l2": { color: "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor" } }, ".badge-dash": { "@layer daisyui.l1.l2": { color: "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor", "border-style": "dashed" } }, ".badge-soft": { "@layer daisyui.l1.l2": { color: "var(--badge-color, var(--color-base-content))", "background-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) )", "border-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) )", "background-image": "none" } }, ".badge-primary": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-primary)", "--badge-fg": "var(--color-primary-content)" } }, ".badge-secondary": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-secondary)", "--badge-fg": "var(--color-secondary-content)" } }, ".badge-accent": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-accent)", "--badge-fg": "var(--color-accent-content)" } }, ".badge-neutral": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-neutral)", "--badge-fg": "var(--color-neutral-content)" } }, ".badge-info": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-info)", "--badge-fg": "var(--color-info-content)" } }, ".badge-success": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-success)", "--badge-fg": "var(--color-success-content)" } }, ".badge-warning": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-warning)", "--badge-fg": "var(--color-warning-content)" } }, ".badge-error": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-error)", "--badge-fg": "var(--color-error-content)" } }, ".badge-ghost": { "@layer daisyui.l1.l2": { "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "var(--color-base-content)", "background-image": "none" } }, ".badge-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem", "padding-inline": "calc(0.25rem * 2 - var(--border))" } }, ".badge-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem", "padding-inline": "calc(0.25rem * 2.5 - var(--border))" } }, ".badge-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", "padding-inline": "calc(0.25rem * 3 - var(--border))" } }, ".badge-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem", "padding-inline": "calc(0.25rem * 3.5 - var(--border))" } }, ".badge-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem", "padding-inline": "calc(0.25rem * 4 - var(--border))" } } }; +var object_default40 = { ".badge": { "@layer daisyui.l1.l2.l3": { display: "inline-flex", "align-items": "center", "justify-content": "center", gap: "calc(0.25rem * 2)", "border-radius": "var(--radius-selector)", "vertical-align": "middle", color: "var(--badge-fg)", border: "var(--border) solid var(--badge-color, var(--color-base-200))", "font-size": "0.875rem", width: "fit-content", "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, 0.25rem) * 6)", height: "var(--size)", "padding-inline": "calc(var(--size) / 2 - var(--border))" } }, ".badge-outline": { "@layer daisyui.l1.l2": { color: "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor" } }, ".badge-dash": { "@layer daisyui.l1.l2": { color: "var(--badge-color)", "--badge-bg": "#0000", "background-image": "none", "border-color": "currentColor", "border-style": "dashed" } }, ".badge-soft": { "@layer daisyui.l1.l2": { color: "var(--badge-color, var(--color-base-content))", "background-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) )", "border-color": "color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) )", "background-image": "none" } }, ".badge-primary": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-primary)", "--badge-fg": "var(--color-primary-content)" } }, ".badge-secondary": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-secondary)", "--badge-fg": "var(--color-secondary-content)" } }, ".badge-accent": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-accent)", "--badge-fg": "var(--color-accent-content)" } }, ".badge-neutral": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-neutral)", "--badge-fg": "var(--color-neutral-content)" } }, ".badge-info": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-info)", "--badge-fg": "var(--color-info-content)" } }, ".badge-success": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-success)", "--badge-fg": "var(--color-success-content)" } }, ".badge-warning": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-warning)", "--badge-fg": "var(--color-warning-content)" } }, ".badge-error": { "@layer daisyui.l1.l2": { "--badge-color": "var(--color-error)", "--badge-fg": "var(--color-error-content)" } }, ".badge-ghost": { "@layer daisyui.l1.l2": { "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "var(--color-base-content)", "background-image": "none" } }, ".badge-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem" } }, ".badge-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem" } }, ".badge-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem" } }, ".badge-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem" } }, ".badge-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem" } } }; // packages/daisyui/components/badge/index.js var badge_default = ({ addComponents, prefix = "" }) => { @@ -697,7 +697,7 @@ var validator_default = ({ addComponents, prefix = "" }) => { // packages/daisyui/components/list/object.js var object_default43 = { ".list": { "@layer daisyui.l1.l2.l3": { display: "flex", "flex-direction": "column", "font-size": "0.875rem", ".list-row": { "--list-grid-cols": "minmax(0, auto) 1fr", position: "relative", display: "grid", "grid-auto-flow": "column", gap: "calc(0.25rem * 4)", "border-radius": "var(--radius-box)", padding: "calc(0.25rem * 4)", "word-break": "break-word", "grid-template-columns": "var(--list-grid-cols)" }, "& > :not(:last-child)": { "&.list-row, .list-row": { "&:after": { content: '""', "border-bottom": "var(--border) solid", "inset-inline": "var(--radius-box)", position: "absolute", bottom: "calc(0.25rem * 0)", "border-color": "color-mix(in oklab, var(--color-base-content) 5%, transparent)" } } } }, "@layer daisyui.l1.l2": { ".list-row": { "&:has(.list-col-grow:nth-child(1))": { "--list-grid-cols": "1fr" }, "&:has(.list-col-grow:nth-child(2))": { "--list-grid-cols": "minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(3))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(4))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr" }, "&:has(.list-col-grow:nth-child(5))": { "--list-grid-cols": "minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr" }, "&: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` }, "> :not(.list-col-wrap)": { "grid-row-start": "1" } } } }, ".list-col-wrap": { "@layer daisyui.l1.l2": { "grid-row-start": "2" } } }; + minmax(0, auto) 1fr` }, "> *": { "grid-row-start": "1" } } } }, ".list-col-wrap": { "@layer daisyui.l1": { "grid-row-start": "2" } } }; // packages/daisyui/components/list/index.js var list_default = ({ addComponents, prefix = "" }) => { @@ -733,7 +733,7 @@ var hero_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/breadcrumbs/object.js -var object_default47 = { ".breadcrumbs": { "@layer daisyui.l1.l2.l3": { "max-width": "100%", "overflow-x": "auto", "padding-block": "calc(0.25rem * 2)", "> menu, > ul, > ol": { display: "flex", "min-height": "min-content", "align-items": "center", "white-space": "nowrap", "> li": { display: "flex", "align-items": "center", "> *": { display: "flex", cursor: "pointer", "align-items": "center", gap: "calc(0.25rem * 2)", "&:hover": { "@media (hover: hover)": { "text-decoration-line": "underline" } }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { outline: "2px solid currentColor", "outline-offset": "2px" } }, "& + *:before": { content: '""', "margin-right": "calc(0.25rem * 3)", "margin-left": "calc(0.25rem * 2)", display: "block", height: "calc(0.25rem * 1.5)", width: "calc(0.25rem * 1.5)", opacity: "40%", rotate: "45deg", "border-top": "1px solid", "border-right": "1px solid", "background-color": "#0000" }, '[dir="rtl"] & + *:before': { rotate: "-135deg" } } } } } }; +var object_default47 = { ".breadcrumbs": { "@layer daisyui.l1.l2.l3": { "max-width": "100%", "overflow-x": "auto", "padding-block": "calc(0.25rem * 2)", "> menu, > ul, > ol": { display: "flex", "min-height": "min-content", "align-items": "center", "white-space": "nowrap", "> li": { display: "flex", "align-items": "center", "> *": { display: "flex", cursor: "pointer", "align-items": "center", gap: "calc(0.25rem * 2)", "&:hover": { "@media (hover: hover)": { "text-decoration-line": "underline" } }, "&:focus": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } }, "&:focus-visible": { outline: "2px solid currentColor", "outline-offset": "2px" } }, "& + *:before": { content: '""', "margin-inline-start": "calc(0.25rem * 2)", "margin-inline-end": "calc(0.25rem * 3)", display: "block", height: "calc(0.25rem * 1.5)", width: "calc(0.25rem * 1.5)", opacity: "40%", rotate: "45deg", "border-top": "1px solid", "border-right": "1px solid", "background-color": "#0000" }, '[dir="rtl"] & + *:before': { rotate: "-135deg" } } } } } }; // packages/daisyui/components/breadcrumbs/index.js var breadcrumbs_default = ({ addComponents, prefix = "" }) => { @@ -742,7 +742,7 @@ var breadcrumbs_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/collapse/object.js -var object_default48 = { ".collapse:not(td, tr, colgroup)": { visibility: "revert-layer" }, ".collapse": { "@layer daisyui.l1.l2.l3": { display: "grid", position: "relative", overflow: "hidden", "border-radius": "var(--radius-box, 1rem)", width: "100%", "grid-template-rows": "max-content 0fr", "grid-template-columns": "minmax(0, 1fr)", isolation: "isolate", "@media (prefers-reduced-motion: no-preference)": { transition: "grid-template-rows 0.2s" }, '> input:is([type="checkbox"], [type="radio"])': { "grid-column-start": "1", "grid-row-start": "1", appearance: "none", opacity: 0, "z-index": 1, width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "1lh", transition: "background-color 0.2s ease-out" }, '&:is( [open], [tabindex]:focus:not(.collapse-close), [tabindex]:focus-within:not(.collapse-close) ), &:not(.collapse-close):has(> input:is([type="checkbox"], [type="radio"]):checked)': { "grid-template-rows": "max-content 1fr" }, '&:is( [open], [tabindex]:focus:not(.collapse-close), [tabindex]:focus-within:not(.collapse-close) ) > .collapse-content, &: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)": { visibility: "visible" } }, '&:focus-visible, &:has(> input:is([type="checkbox"], [type="radio"]):focus-visible), &:has(summary:focus-visible)': { "outline-color": "var(--color-base-content)", "outline-style": "solid", "outline-width": "2px", "outline-offset": "2px" }, "&:not(.collapse-close)": { '> input[type="checkbox"], > input[type="radio"]:not(:checked), > .collapse-title': { cursor: "pointer" } }, "&[tabindex]:focus:not(.collapse-close, .collapse[open]), &[tabindex]:focus-within:not(.collapse-close, .collapse[open])": { "> .collapse-title": { cursor: "unset" } }, '&:is( [open], [tabindex]:focus:not(.collapse-close), [tabindex]:focus-within:not(.collapse-close) ) > :where(.collapse-content), &:not(.collapse-close) > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content)': { "padding-bottom": "1rem" } }, "@layer daisyui.l1.l2": { "&:is([open])": { "&.collapse-arrow": { "> .collapse-title:after": { "@media (prefers-reduced-motion: no-preference)": { transform: "translateY(-50%) rotate(225deg)" } } } }, "&.collapse-open": { "&.collapse-arrow": { "> .collapse-title:after": { "@media (prefers-reduced-motion: no-preference)": { transform: "translateY(-50%) rotate(225deg)" } } }, "&.collapse-plus": { "> .collapse-title:after": { "--tw-content": '"−"', content: "var(--tw-content)" } } }, "&[tabindex].collapse-arrow:focus:not(.collapse-close), &.collapse-arrow[tabindex]:focus-within:not(.collapse-close)": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } }, "&.collapse-arrow:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { transform: "translateY(-50%) rotate(225deg)" } }, "&[open]": { "&.collapse-plus": { "> .collapse-title:after": { "--tw-content": '"−"', content: "var(--tw-content)" } } }, "&[tabindex].collapse-plus:focus:not(.collapse-close)": { "> .collapse-title:after": { "--tw-content": '"−"', content: "var(--tw-content)" } }, "&.collapse-plus:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { "--tw-content": '"−"', content: "var(--tw-content)" } } } }, ".collapse-title, .collapse-content": { "@layer daisyui.l1.l2.l3": { "grid-column-start": "1", "grid-row-start": "1" } }, ".collapse-content": { "@layer daisyui.l1.l2.l3": { "content-visibility": "hidden", "grid-column-start": "1", "grid-row-start": "2", "min-height": "0", "padding-left": "1rem", "padding-right": "1rem", cursor: "unset", "@supports not (content-visibility: hidden)": { visibility: "hidden" }, "@media (prefers-reduced-motion: no-preference)": { transition: "content-visibility 0.2s allow-discrete, visibility 0.2s allow-discrete, padding 0.2s ease-out, background-color 0.2s ease-out" } } }, ".collapse:is(details)": { "@layer daisyui.l1.l2.l3": { width: "100%", "@media (prefers-reduced-motion: no-preference)": { "&::details-content": { transition: "content-visibility 0.2s allow-discrete, visibility 0.2s allow-discrete, padding 0.2s ease-out, background-color 0.2s ease-out, height 0.2s", height: "0", "interpolate-size": "allow-keywords" }, "&:where([open])::details-content": { height: "auto" } }, "& summary": { position: "relative", display: "block", "&::-webkit-details-marker": { display: "none" } }, "& > .collapse-content": { "content-visibility": "visible" } } }, ".collapse:is(details) summary": { "@layer daisyui.l1.l2.l3": { outline: "none" } }, ".collapse-arrow": { "@layer daisyui.l1.l2": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", transform: "translateY(-100%) rotate(45deg)", "@media (prefers-reduced-motion: no-preference)": { "transition-property": "all", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", "transition-duration": "0.2s" }, top: "50%", "inset-inline-end": "1.4rem", content: '""', "transform-origin": "75% 75%", "box-shadow": "2px 2px", "pointer-events": "none" } } }, ".collapse-plus": { "@layer daisyui.l1.l2": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", "@media (prefers-reduced-motion: no-preference)": { "transition-property": "all", "transition-duration": "300ms", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)" }, top: "0.9rem", "inset-inline-end": "1.4rem", "--tw-content": '"+"', content: "var(--tw-content)", "pointer-events": "none" } } }, ".collapse-title": { "@layer daisyui.l1.l2.l3": { position: "relative", width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "1lh", transition: "background-color 0.2s ease-out" } }, ".collapse-open": { "@layer daisyui.l1.l2": { "grid-template-rows": "max-content 1fr", "> .collapse-content": { "content-visibility": "visible", "min-height": "fit-content", "padding-bottom": "1rem", "@supports not (content-visibility: visible)": { visibility: "visible" } } } } }; +var object_default48 = { ".collapse:not(td, tr, colgroup)": { visibility: "revert-layer" }, ".collapse": { "@layer daisyui.l1.l2.l3": { display: "grid", position: "relative", overflow: "hidden", "border-radius": "var(--radius-box, 1rem)", width: "100%", "grid-template-rows": "max-content 0fr", "grid-template-columns": "minmax(0, 1fr)", isolation: "isolate", "@media (prefers-reduced-motion: no-preference)": { transition: "grid-template-rows 0.2s" }, '> input:is([type="checkbox"], [type="radio"])': { "grid-column-start": "1", "grid-row-start": "1", appearance: "none", opacity: 0, "z-index": 1, width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "1lh", transition: "background-color 0.2s ease-out" }, '&:is( [open], [tabindex]:focus:not(.collapse-close), [tabindex]:focus-within:not(.collapse-close) ), &:not(.collapse-close):has(> input:is([type="checkbox"], [type="radio"]):checked)': { "grid-template-rows": "max-content 1fr" }, '&:is( [open], [tabindex]:focus:not(.collapse-close), [tabindex]:focus-within:not(.collapse-close) ) > .collapse-content, &: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)": { visibility: "visible" } }, '&:focus-visible, &:has(> input:is([type="checkbox"], [type="radio"]):focus-visible), &:has(summary:focus-visible)': { "outline-color": "var(--color-base-content)", "outline-style": "solid", "outline-width": "2px", "outline-offset": "2px" }, "&:not(.collapse-close)": { '> input[type="checkbox"], > input[type="radio"]:not(:checked), > .collapse-title': { cursor: "pointer" } }, "&[tabindex]:focus:not(.collapse-close, .collapse[open]), &[tabindex]:focus-within:not(.collapse-close, .collapse[open])": { "> .collapse-title": { cursor: "unset" } }, '&:is( [open], [tabindex]:focus:not(.collapse-close), [tabindex]:focus-within:not(.collapse-close) ) > :where(.collapse-content), &:not(.collapse-close) > :where(input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-content)': { "padding-bottom": "1rem" } }, "@layer daisyui.l1.l2": { "&:is([open])": { "&.collapse-arrow": { "> .collapse-title:after": { "@media (prefers-reduced-motion: no-preference)": { transform: "translateY(-50%) rotate(225deg)" } } } }, "&.collapse-open": { "&.collapse-arrow": { "> .collapse-title:after": { "@media (prefers-reduced-motion: no-preference)": { transform: "translateY(-50%) rotate(225deg)" } } }, "&.collapse-plus": { "> .collapse-title:after": { "--tw-content": '"−"', content: "var(--tw-content)" } } }, "&[tabindex].collapse-arrow:focus:not(.collapse-close), &.collapse-arrow[tabindex]:focus-within:not(.collapse-close)": { "> .collapse-title:after": { transform: "translateY(-50%) rotate(225deg)" } }, "&.collapse-arrow:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { transform: "translateY(-50%) rotate(225deg)" } }, "&[open]": { "&.collapse-plus": { "> .collapse-title:after": { "--tw-content": '"−"', content: "var(--tw-content)" } } }, "&[tabindex].collapse-plus:focus:not(.collapse-close)": { "> .collapse-title:after": { "--tw-content": '"−"', content: "var(--tw-content)" } }, "&.collapse-plus:not(.collapse-close)": { '> input:is([type="checkbox"], [type="radio"]):checked ~ .collapse-title:after': { "--tw-content": '"−"', content: "var(--tw-content)" } } } }, ".collapse-title, .collapse-content": { "@layer daisyui.l1.l2.l3": { "grid-column-start": "1", "grid-row-start": "1" } }, ".collapse-content": { "@layer daisyui.l1.l2.l3": { "content-visibility": "hidden", "grid-column-start": "1", "grid-row-start": "2", "min-height": "0", "padding-left": "1rem", "padding-right": "1rem", cursor: "unset", "@supports not (content-visibility: hidden)": { visibility: "hidden" }, "@media (prefers-reduced-motion: no-preference)": { transition: "content-visibility 0.2s allow-discrete, visibility 0.2s allow-discrete, min-height 0.2s ease-out allow-discrete, padding 0.1s ease-out 20ms, background-color 0.2s ease-out" } } }, ".collapse:is(details)": { "@layer daisyui.l1.l2.l3": { width: "100%", "@media (prefers-reduced-motion: no-preference)": { "&::details-content": { transition: "content-visibility 0.2s allow-discrete, visibility 0.2s allow-discrete, min-height 0.2s ease-out allow-discrete, padding 0.1s ease-out 20ms, background-color 0.2s ease-out, height 0.2s", height: "0", "interpolate-size": "allow-keywords" }, "&:where([open])::details-content": { height: "auto" } }, "& summary": { position: "relative", display: "block", "&::-webkit-details-marker": { display: "none" } }, "& > .collapse-content": { "content-visibility": "visible" } } }, ".collapse:is(details) summary": { "@layer daisyui.l1.l2.l3": { outline: "none" } }, ".collapse-arrow": { "@layer daisyui.l1.l2": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", transform: "translateY(-100%) rotate(45deg)", "@media (prefers-reduced-motion: no-preference)": { "transition-property": "all", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", "transition-duration": "0.2s" }, top: "50%", "inset-inline-end": "1.4rem", content: '""', "transform-origin": "75% 75%", "box-shadow": "2px 2px", "pointer-events": "none" } } }, ".collapse-plus": { "@layer daisyui.l1.l2": { "> .collapse-title:after": { position: "absolute", display: "block", height: "0.5rem", width: "0.5rem", "@media (prefers-reduced-motion: no-preference)": { "transition-property": "all", "transition-duration": "300ms", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)" }, top: "0.9rem", "inset-inline-end": "1.4rem", "--tw-content": '"+"', content: "var(--tw-content)", "pointer-events": "none" } } }, ".collapse-title": { "@layer daisyui.l1.l2.l3": { position: "relative", width: "100%", padding: "1rem", "padding-inline-end": "3rem", "min-height": "1lh", transition: "background-color 0.2s ease-out" } }, ".collapse-open": { "@layer daisyui.l1.l2": { "grid-template-rows": "max-content 1fr", "> .collapse-content": { "content-visibility": "visible", "min-height": "fit-content", "padding-bottom": "1rem", "@supports not (content-visibility: visible)": { visibility: "visible" } } } } }; // packages/daisyui/components/collapse/index.js var collapse_default = ({ addComponents, prefix = "" }) => { @@ -836,7 +836,7 @@ var alert_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/fab/object.js -var object_default58 = { ".fab": { "@layer daisyui.l1.l2.l3": { "pointer-events": "none", position: "fixed", "inset-inline-end": "calc(0.25rem * 4)", bottom: "calc(0.25rem * 4)", "z-index": 999, display: "flex", "flex-direction": "column-reverse", "align-items": "flex-end", gap: "calc(0.25rem * 2)", "font-size": "var(--text-sm)", "line-height": "var(--tw-leading, var(--text-sm--line-height))", "white-space": "nowrap", "> *": { "pointer-events": "auto", display: "flex", "align-items": "center", gap: "calc(0.25rem * 2)", "&:hover, &:has(:focus-visible)": { "z-index": 1 } }, "> [tabindex]": { "&:first-child": { position: "relative", display: "grid", "transition-property": "opacity, visibility, rotate", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)" } }, ".fab-close": { position: "absolute", "inset-inline-end": "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)" }, ".fab-main-action": { position: "absolute", "inset-inline-end": "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)" }, "&:focus-within": { "&:has(.fab-close), &:has(.fab-main-action)": { "> [tabindex]": { rotate: "90deg", opacity: "0%" } }, "> [tabindex]:first-child": { "pointer-events": "none" }, "> :nth-child(n + 2)": { visibility: "visible", "--tw-scale-x": "100%", "--tw-scale-y": "100%", "--tw-scale-z": "100%", scale: "var(--tw-scale-x) var(--tw-scale-y)", opacity: "100%" } }, "> :nth-child(n + 2)": { visibility: "hidden", "--tw-scale-x": "80%", "--tw-scale-y": "80%", "--tw-scale-z": "80%", scale: "var(--tw-scale-x) var(--tw-scale-y)", opacity: "0%", "transition-property": "opacity, scale, visibility", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", "&.fab-main-action, &.fab-close": { "--tw-scale-x": "100%", "--tw-scale-y": "100%", "--tw-scale-z": "100%", scale: "var(--tw-scale-x) var(--tw-scale-y)" } }, "> :nth-child(3)": { "transition-delay": "30ms" }, "> :nth-child(4)": { "transition-delay": "60ms" }, "> :nth-child(5)": { "transition-delay": "90ms" }, "> :nth-child(6)": { "transition-delay": "120ms" } } }, ".fab-flower": { "@layer daisyui.l1.l2.l3": { display: "grid", "--position": "0rem", "> *:nth-child(-n + 2)": { "--position": "0rem" }, "> *": { "grid-area": "1/1", "--degree": "180deg", "--flip-degree": "calc(180deg - var(--degree))", transform: "translateX(calc(cos(var(--degree)) * var(--position))) translateY(calc(sin(var(--degree)) * calc(-1 * var(--position))))", '[dir="rtl"] &': { transform: "translateX(calc(cos(var(--flip-degree)) * var(--position))) translateY(calc(sin(var(--flip-degree)) * calc(-1 * var(--position))))" } }, "> :nth-child(n + 7)": { display: "none" }, "&:has(:nth-child(3))": { "--position": "140%", "> :nth-child(3)": { "--degree": "135deg" } }, "&:has(:nth-child(4))": { "--position": "140%", "> :nth-child(3)": { "--degree": "165deg" }, "> :nth-child(4)": { "--degree": "105deg" } }, "&:has(:nth-child(5))": { "--position": "180%", "> :nth-child(3)": { "--degree": "180deg" }, "> :nth-child(4)": { "--degree": "135deg" }, "> :nth-child(5)": { "--degree": "90deg" } }, "&:has(:nth-child(6))": { "--position": "220%", "> :nth-child(3)": { "--degree": "180deg" }, "> :nth-child(4)": { "--degree": "150deg" }, "> :nth-child(5)": { "--degree": "120deg" }, "> :nth-child(6)": { "--degree": "90deg" } } } } }; +var object_default58 = { ".fab": { "@layer daisyui.l1.l2.l3": { "pointer-events": "none", position: "fixed", "inset-inline-end": "calc(0.25rem * 4)", bottom: "calc(0.25rem * 4)", "z-index": 999, display: "flex", "flex-direction": "column-reverse", "align-items": "flex-end", gap: "calc(0.25rem * 2)", "font-size": "var(--text-sm)", "line-height": "var(--tw-leading, var(--text-sm--line-height))", "white-space": "nowrap", "> *": { "pointer-events": "auto", display: "flex", "align-items": "center", gap: "calc(0.25rem * 2)", "&:hover, &:has(:focus-visible)": { "z-index": 1 } }, "> [tabindex]": { "&:first-child": { position: "relative", display: "grid", "transition-property": "opacity, visibility, rotate", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)" } }, ".fab-close": { position: "absolute", "inset-inline-end": "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)" }, ".fab-main-action": { position: "absolute", "inset-inline-end": "calc(0.25rem * 0)", bottom: "calc(0.25rem * 0)" }, "&:focus-within": { "&:has(.fab-close), &:has(.fab-main-action)": { "> [tabindex]": { rotate: "90deg", opacity: "0%" } }, "> [tabindex]:first-child": { "pointer-events": "none" }, "> :nth-child(n + 2)": { visibility: "visible", "--tw-scale-x": "100%", "--tw-scale-y": "100%", "--tw-scale-z": "100%", scale: "var(--tw-scale-x) var(--tw-scale-y)", opacity: "100%" } }, "> :nth-child(n + 2)": { visibility: "hidden", "--tw-scale-x": "80%", "--tw-scale-y": "80%", "--tw-scale-z": "80%", scale: "var(--tw-scale-x) var(--tw-scale-y)", opacity: "0%", "transition-property": "opacity, scale, visibility", "transition-duration": "0.2s", "transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", "&.fab-main-action, &.fab-close": { "--tw-scale-x": "100%", "--tw-scale-y": "100%", "--tw-scale-z": "100%", scale: "var(--tw-scale-x) var(--tw-scale-y)" } }, "> :nth-child(3)": { "transition-delay": "30ms" }, "> :nth-child(4)": { "transition-delay": "60ms" }, "> :nth-child(5)": { "transition-delay": "90ms" }, "> :nth-child(6)": { "transition-delay": "120ms" } } }, ".fab-flower": { "@layer daisyui.l1.l2.l3": { display: "grid", "--position": "0rem", "> *:nth-child(-n + 2)": { "--position": "0rem" }, "> *": { "grid-area": "1/1", "--degree": "180deg", "--flip-degree": "calc(180deg - var(--degree))", transform: "translateX(calc(cos(var(--degree)) * var(--position))) translateY(calc(sin(var(--degree)) * -1 * var(--position)))", '[dir="rtl"] &': { transform: "translateX(calc(cos(var(--flip-degree)) * var(--position))) translateY(calc(sin(var(--flip-degree)) * -1 * var(--position)))" } }, "> :nth-child(n + 7)": { display: "none" }, "&:has(:nth-child(3))": { "--position": "140%", "> :nth-child(3)": { "--degree": "135deg" } }, "&:has(:nth-child(4))": { "--position": "140%", "> :nth-child(3)": { "--degree": "165deg" }, "> :nth-child(4)": { "--degree": "105deg" } }, "&:has(:nth-child(5))": { "--position": "180%", "> :nth-child(3)": { "--degree": "180deg" }, "> :nth-child(4)": { "--degree": "135deg" }, "> :nth-child(5)": { "--degree": "90deg" } }, "&:has(:nth-child(6))": { "--position": "220%", "> :nth-child(3)": { "--degree": "180deg" }, "> :nth-child(4)": { "--degree": "150deg" }, "> :nth-child(5)": { "--degree": "120deg" }, "> :nth-child(6)": { "--degree": "90deg" } } } } }; // packages/daisyui/components/fab/index.js var fab_default = ({ addComponents, prefix = "" }) => { @@ -845,7 +845,7 @@ var fab_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/kbd/object.js -var object_default59 = { ".kbd": { "box-shadow": "none", "@layer daisyui.l1.l2.l3": { display: "inline-flex", "align-items": "center", "justify-content": "center", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-200)", "vertical-align": "middle", "padding-left": "0.5em", "padding-right": "0.5em", border: "var(--border) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "border-bottom": "calc(var(--border) + 1px) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", height: "var(--size)", "min-width": "var(--size)" } }, ".kbd-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem" } }, ".kbd-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem" } }, ".kbd-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem" } }, ".kbd-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem" } }, ".kbd-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem" } } }; +var object_default59 = { ".kbd": { "box-shadow": "none", "@layer daisyui.l1.l2.l3": { display: "inline-flex", "align-items": "center", "justify-content": "center", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-200)", "vertical-align": "middle", "padding-inline": "0.5em", border: "var(--border) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "border-bottom": "calc(var(--border) + 1px) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)", "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem", height: "var(--size)", "min-width": "var(--size)" } }, ".kbd-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 4)", "font-size": "0.625rem" } }, ".kbd-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 5)", "font-size": "0.75rem" } }, ".kbd-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 6)", "font-size": "0.875rem" } }, ".kbd-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 7)", "font-size": "1rem" } }, ".kbd-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-selector, 0.25rem) * 8)", "font-size": "1.125rem" } } }; // packages/daisyui/components/kbd/index.js var kbd_default = ({ addComponents, prefix = "" }) => { @@ -854,7 +854,7 @@ var kbd_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/input/object.js -var object_default60 = { ".input": { "@layer daisyui.l1.l2.l3": { cursor: "text", border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 2)", "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 3)", "vertical-align": "middle", "white-space": "nowrap", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "max(var(--font-size, 0.875rem), 0.875rem)", "touch-action": "manipulation", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "border-color": "var(--input-color)", "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) * 0.1)) inset", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "&:where(input)": { display: "inline-flex" }, ":where(input)": { display: "inline-flex", height: "100%", width: "100%", appearance: "none", "background-color": "transparent", border: "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, ':where(input[type="url"]), :where(input[type="email"])': { direction: "ltr" }, ':where(input[type="date"])': { display: "inline-flex" }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate", "z-index": 1 }, "@media (pointer: coarse)": { "@supports (-webkit-touch-callout: none)": { "&:focus, &:focus-within": { "--font-size": "1rem" } } }, "&:has(> input[disabled]), &:is(:disabled, [disabled]), fieldset:disabled &": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none" }, "&:has(> input[disabled]) > input[disabled]": { cursor: "not-allowed" }, "&::-webkit-date-and-time-value": { "text-align": "inherit" }, '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } }, "&::-webkit-calendar-picker-indicator": { position: "absolute", "inset-inline-end": "0.75em" }, '&:has(> input[type="date"])': { ':where(input[type="date"])': { display: "inline-flex", "webkit-appearance": "none", appearance: "none" }, 'input[type="date"]::-webkit-calendar-picker-indicator': { position: "absolute", "inset-inline-end": "0.75em", width: "1em", height: "1em", cursor: "pointer" } } } }, ".input-ghost": { "@layer daisyui.l1.l2": { "background-color": "transparent", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } } }, ".input-neutral": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } } }, ".input-primary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } } }, ".input-secondary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } } }, ".input-accent": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } } }, ".input-info": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } } }, ".input-success": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } } }, ".input-warning": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } } }, ".input-error": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } } }, ".input-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "max(var(--font-size, 0.6875rem), 0.6875rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -1)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "max(var(--font-size, 0.75rem), 0.75rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -2)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "max(var(--font-size, 0.875rem), 0.875rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "max(var(--font-size, 1.125rem), 1.125rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "max(var(--font-size, 1.375rem), 1.375rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -4)", "margin-inline-end": "calc(0.25rem * -3)" } } } } }; +var object_default60 = { ".input": { "@layer daisyui.l1.l2.l3": { cursor: "text", border: "var(--border) solid #0000", position: "relative", display: "inline-flex", "flex-shrink": 1, appearance: "none", "align-items": "center", gap: "calc(0.25rem * 2)", "background-color": "var(--color-base-100)", "padding-inline": "calc(0.25rem * 3)", "vertical-align": "middle", "white-space": "nowrap", width: "clamp(3rem, 20rem, 100%)", height: "var(--size)", "font-size": "max(var(--font-size, 0.875rem), 0.875rem)", "touch-action": "manipulation", "border-start-start-radius": "var(--join-ss, var(--radius-field))", "border-start-end-radius": "var(--join-se, var(--radius-field))", "border-end-start-radius": "var(--join-es, var(--radius-field))", "border-end-end-radius": "var(--join-ee, var(--radius-field))", "border-color": "var(--input-color)", "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) * 0.1)) inset", "--size": "calc(var(--size-field, 0.25rem) * 10)", "--input-color": "color-mix(in oklab, var(--color-base-content) 20%, #0000)", "&:where(input)": { display: "inline-flex" }, ":where(input)": { display: "inline-flex", height: "100%", width: "100%", appearance: "none", "background-color": "transparent", border: "none", "&:focus, &:focus-within": { "--tw-outline-style": "none", "outline-style": "none", "@media (forced-colors: active)": { outline: "2px solid transparent", "outline-offset": "2px" } } }, ':where(input[type="url"]), :where(input[type="email"])': { direction: "ltr" }, ':where(input[type="date"])': { display: "inline-flex" }, "&:focus, &:focus-within": { "--input-color": "var(--color-base-content)", "box-shadow": "0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)", outline: "2px solid var(--input-color)", "outline-offset": "2px", isolation: "isolate" }, "@media (pointer: coarse)": { "@supports (-webkit-touch-callout: none)": { "&:focus, &:focus-within": { "--font-size": "1rem" } } }, "&:has(> input[disabled]), &:is(:disabled, [disabled]), fieldset:disabled &": { cursor: "not-allowed", "border-color": "var(--color-base-200)", "background-color": "var(--color-base-200)", color: "color-mix(in oklab, var(--color-base-content) 40%, transparent)", "&::placeholder": { color: "color-mix(in oklab, var(--color-base-content) 20%, transparent)" }, "box-shadow": "none" }, "&:has(> input[disabled]) > input[disabled]": { cursor: "not-allowed" }, "&::-webkit-date-and-time-value": { "text-align": "inherit" }, '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } }, "&::-webkit-calendar-picker-indicator": { position: "absolute", "inset-inline-end": "0.75em" }, '&:has(> input[type="date"])': { ':where(input[type="date"])': { display: "inline-flex", "webkit-appearance": "none", appearance: "none" }, 'input[type="date"]::-webkit-calendar-picker-indicator': { position: "absolute", "inset-inline-end": "0.75em", width: "1em", height: "1em", cursor: "pointer" } } } }, ".input-ghost": { "@layer daisyui.l1.l2": { "background-color": "transparent", "box-shadow": "none", "border-color": "#0000", "&:focus, &:focus-within": { "background-color": "var(--color-base-100)", color: "var(--color-base-content)", "border-color": "#0000", "box-shadow": "none" } } }, ".input-neutral": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-neutral)" } } }, ".input-primary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-primary)" } } }, ".input-secondary": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-secondary)" } } }, ".input-accent": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-accent)" } } }, ".input-info": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-info)" } } }, ".input-success": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-success)" } } }, ".input-warning": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-warning)" } } }, ".input-error": { "@layer daisyui.l1.l2": { "&, &:focus, &:focus-within": { "--input-color": "var(--color-error)" } } }, ".input-xs": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 6)", "font-size": "max(var(--font-size, 0.6875rem), 0.6875rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -1)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-sm": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 8)", "font-size": "max(var(--font-size, 0.75rem), 0.75rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -2)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-md": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 10)", "font-size": "max(var(--font-size, 0.875rem), 0.875rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-lg": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 12)", "font-size": "max(var(--font-size, 1.125rem), 1.125rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -3)", "margin-inline-end": "calc(0.25rem * -3)" } } } }, ".input-xl": { "@layer daisyui.l1.l2": { "--size": "calc(var(--size-field, 0.25rem) * 14)", "font-size": "max(var(--font-size, 1.375rem), 1.375rem)", '&[type="number"]': { "&::-webkit-inner-spin-button": { "margin-block": "calc(0.25rem * -4)", "margin-inline-end": "calc(0.25rem * -3)" } } } } }; // packages/daisyui/components/input/index.js var input_default = ({ addComponents, prefix = "" }) => { @@ -872,7 +872,7 @@ var timeline_default = ({ addComponents, prefix = "" }) => { }; // packages/daisyui/components/chat/object.js -var object_default62 = { ".chat": { "@layer daisyui.l1.l2.l3": { display: "grid", "column-gap": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1)", "--mask-chat": `url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e")` } }, ".chat-bubble": { "@layer daisyui.l1.l2.l3": { position: "relative", display: "block", width: "fit-content", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-300)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", color: "var(--color-base-content)", "grid-row-end": "3", "min-height": "2rem", "min-width": "2.5rem", "max-width": "90%", "&:before": { position: "absolute", bottom: "calc(0.25rem * 0)", height: "calc(0.25rem * 3)", width: "calc(0.25rem * 3)", "background-color": "inherit", content: '""', "mask-repeat": "no-repeat", "mask-image": "var(--mask-chat)", "mask-position": "0px -1px", "mask-size": "0.8125rem" } } }, ".chat-bubble-primary": { "@layer daisyui.l1.l2": { "background-color": "var(--color-primary)", color: "var(--color-primary-content)" } }, ".chat-bubble-secondary": { "@layer daisyui.l1.l2": { "background-color": "var(--color-secondary)", color: "var(--color-secondary-content)" } }, ".chat-bubble-accent": { "@layer daisyui.l1.l2": { "background-color": "var(--color-accent)", color: "var(--color-accent-content)" } }, ".chat-bubble-neutral": { "@layer daisyui.l1.l2": { "background-color": "var(--color-neutral)", color: "var(--color-neutral-content)" } }, ".chat-bubble-info": { "@layer daisyui.l1.l2": { "background-color": "var(--color-info)", color: "var(--color-info-content)" } }, ".chat-bubble-success": { "@layer daisyui.l1.l2": { "background-color": "var(--color-success)", color: "var(--color-success-content)" } }, ".chat-bubble-warning": { "@layer daisyui.l1.l2": { "background-color": "var(--color-warning)", color: "var(--color-warning-content)" } }, ".chat-bubble-error": { "@layer daisyui.l1.l2": { "background-color": "var(--color-error)", color: "var(--color-error-content)" } }, ".chat-image": { "@layer daisyui.l1.l2.l3": { "grid-row": "span 2 / span 2", "align-self": "flex-end" } }, ".chat-header": { "@layer daisyui.l1.l2.l3": { "grid-row-start": "1", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" } }, ".chat-footer": { "@layer daisyui.l1.l2.l3": { "grid-row-start": "3", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" } }, ".chat-start": { "@layer daisyui.l1.l2": { "place-items": "start", "grid-template-columns": "auto 1fr", ".chat-header": { "grid-column-start": "2" }, ".chat-footer": { "grid-column-start": "2" }, ".chat-image": { "grid-column-start": "1" }, ".chat-bubble": { "grid-column-start": "2", "border-end-start-radius": "0", "&:before": { transform: "rotateY(0deg)", "inset-inline-start": "-0.75rem" }, '[dir="rtl"] &:before': { transform: "rotateY(180deg)" } } } }, ".chat-end": { "@layer daisyui.l1.l2": { "place-items": "end", "grid-template-columns": "1fr auto", ".chat-header": { "grid-column-start": "1" }, ".chat-footer": { "grid-column-start": "1" }, ".chat-image": { "grid-column-start": "2" }, ".chat-bubble": { "grid-column-start": "1", "border-end-end-radius": "0", "&:before": { transform: "rotateY(180deg)", "inset-inline-start": "100%" }, '[dir="rtl"] &:before': { transform: "rotateY(0deg)" } } } } }; +var object_default62 = { ".chat": { "@layer daisyui.l1.l2.l3": { display: "grid", "grid-auto-rows": "min-content", "column-gap": "calc(0.25rem * 3)", "padding-block": "calc(0.25rem * 1)", "--mask-chat": `url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e")` } }, ".chat-bubble": { "@layer daisyui.l1.l2.l3": { position: "relative", display: "block", width: "fit-content", "border-radius": "var(--radius-field)", "background-color": "var(--color-base-300)", "padding-inline": "calc(0.25rem * 4)", "padding-block": "calc(0.25rem * 2)", color: "var(--color-base-content)", "grid-row-end": "3", "min-height": "2rem", "min-width": "2.5rem", "max-width": "90%", "&:before": { position: "absolute", bottom: "calc(0.25rem * 0)", height: "calc(0.25rem * 3)", width: "calc(0.25rem * 3)", "background-color": "inherit", content: '""', "mask-repeat": "no-repeat", "mask-image": "var(--mask-chat)", "mask-position": "0px -1px", "mask-size": "0.8125rem" } } }, ".chat-bubble-primary": { "@layer daisyui.l1.l2": { "background-color": "var(--color-primary)", color: "var(--color-primary-content)" } }, ".chat-bubble-secondary": { "@layer daisyui.l1.l2": { "background-color": "var(--color-secondary)", color: "var(--color-secondary-content)" } }, ".chat-bubble-accent": { "@layer daisyui.l1.l2": { "background-color": "var(--color-accent)", color: "var(--color-accent-content)" } }, ".chat-bubble-neutral": { "@layer daisyui.l1.l2": { "background-color": "var(--color-neutral)", color: "var(--color-neutral-content)" } }, ".chat-bubble-info": { "@layer daisyui.l1.l2": { "background-color": "var(--color-info)", color: "var(--color-info-content)" } }, ".chat-bubble-success": { "@layer daisyui.l1.l2": { "background-color": "var(--color-success)", color: "var(--color-success-content)" } }, ".chat-bubble-warning": { "@layer daisyui.l1.l2": { "background-color": "var(--color-warning)", color: "var(--color-warning-content)" } }, ".chat-bubble-error": { "@layer daisyui.l1.l2": { "background-color": "var(--color-error)", color: "var(--color-error-content)" } }, ".chat-image": { "@layer daisyui.l1.l2.l3": { "grid-row": "span 2 / span 2", "align-self": "flex-end" } }, ".chat-header": { "@layer daisyui.l1.l2.l3": { "grid-row-start": "1", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" } }, ".chat-footer": { "@layer daisyui.l1.l2.l3": { "grid-row-start": "3", display: "flex", gap: "calc(0.25rem * 1)", "font-size": "0.6875rem" } }, ".chat-start": { "@layer daisyui.l1.l2": { "place-items": "start", "grid-template-columns": "auto 1fr", ".chat-header": { "grid-column-start": "2" }, ".chat-footer": { "grid-column-start": "2" }, ".chat-image": { "grid-column-start": "1" }, ".chat-bubble": { "grid-column-start": "2", "border-end-start-radius": "0", "&:before": { transform: "rotateY(0deg)", "inset-inline-start": "-0.75rem" }, '[dir="rtl"] &:before': { transform: "rotateY(180deg)" } } } }, ".chat-end": { "@layer daisyui.l1.l2": { "place-items": "end", "grid-template-columns": "1fr auto", ".chat-header": { "grid-column-start": "1" }, ".chat-footer": { "grid-column-start": "1" }, ".chat-image": { "grid-column-start": "2" }, ".chat-bubble": { "grid-column-start": "1", "border-end-end-radius": "0", "&:before": { transform: "rotateY(180deg)", "inset-inline-start": "100%" }, '[dir="rtl"] &:before': { transform: "rotateY(0deg)" } } } } }; // packages/daisyui/components/chat/index.js var chat_default = ({ addComponents, prefix = "" }) => { @@ -883,7 +883,7 @@ var chat_default = ({ addComponents, prefix = "" }) => { // packages/daisyui/components/range/object.js var object_default63 = { ".range": { "@layer daisyui.l1.l2.l3": { appearance: "none", "webkit-appearance": "none", "--range-thumb": "var(--color-base-100)", "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)", "--range-progress": "currentColor", "--range-fill": "1", "--range-p": "0.25rem", "--range-bg": "color-mix(in oklab, currentColor 10%, #0000)", cursor: "pointer", overflow: "hidden", "background-color": "transparent", "vertical-align": "middle", width: "clamp(3rem, 20rem, 100%)", "--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)))", border: "none", height: "var(--range-thumb-size)", '[dir="rtl"] &': { "--range-dir": "-1" }, "&:focus": { outline: "none" }, "&:focus-visible": { outline: "2px solid", "outline-offset": "2px" }, "&::-webkit-slider-runnable-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "@media (forced-colors: active)": [{ "&::-webkit-slider-runnable-track": { border: "1px solid" } }, { "&::-moz-range-track": { border: "1px solid" } }], "&::-webkit-slider-thumb": { position: "relative", "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", top: "50%", color: "var(--range-progress)", transform: "translateY(-50%)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.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) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))" }, "&::-moz-range-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "&::-moz-range-thumb": { position: "relative", "box-sizing": "border-box", "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", "background-color": "currentColor", height: "var(--range-thumb-size)", width: "var(--range-thumb-size)", border: "var(--range-p) solid", top: "50%", color: "var(--range-progress)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.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) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))" }, "&:disabled": { cursor: "not-allowed", opacity: "30%" } } }, ".range-primary": { "@layer daisyui.l1.l2": { color: "var(--color-primary)", "--range-thumb": "var(--color-primary-content)" } }, ".range-secondary": { "@layer daisyui.l1.l2": { color: "var(--color-secondary)", "--range-thumb": "var(--color-secondary-content)" } }, ".range-accent": { "@layer daisyui.l1.l2": { color: "var(--color-accent)", "--range-thumb": "var(--color-accent-content)" } }, ".range-neutral": { "@layer daisyui.l1.l2": { color: "var(--color-neutral)", "--range-thumb": "var(--color-neutral-content)" } }, ".range-success": { "@layer daisyui.l1.l2": { color: "var(--color-success)", "--range-thumb": "var(--color-success-content)" } }, ".range-warning": { "@layer daisyui.l1.l2": { color: "var(--color-warning)", "--range-thumb": "var(--color-warning-content)" } }, ".range-info": { "@layer daisyui.l1.l2": { color: "var(--color-info)", "--range-thumb": "var(--color-info-content)" } }, ".range-error": { "@layer daisyui.l1.l2": { color: "var(--color-error)", "--range-thumb": "var(--color-error-content)" } }, ".range-xs": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 4)" } }, ".range-sm": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 5)" } }, ".range-md": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)" } }, ".range-lg": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 7)" } }, ".range-xl": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 8)" } } }; + )`, "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", border: "none", height: "var(--range-thumb-size)", '[dir="rtl"] &': { "--range-dir": "-1" }, "&:focus": { outline: "none" }, "&:focus-visible": { outline: "2px solid", "outline-offset": "2px" }, "&::-webkit-slider-runnable-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "@media (forced-colors: active)": [{ "&::-webkit-slider-runnable-track": { border: "1px solid" } }, { "&::-moz-range-track": { border: "1px solid" } }], "&::-webkit-slider-thumb": { position: "relative", "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", top: "50%", color: "var(--range-progress)", transform: "translateY(-50%)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.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))" }, "&::-moz-range-track": { width: "100%", "background-color": "var(--range-bg)", "border-radius": "var(--radius-selector)", height: "calc(var(--range-thumb-size) * 0.5)" }, "&::-moz-range-thumb": { position: "relative", "box-sizing": "border-box", "border-radius": "calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))", "background-color": "currentColor", height: "var(--range-thumb-size)", width: "var(--range-thumb-size)", border: "var(--range-p) solid", top: "50%", color: "var(--range-progress)", "box-shadow": "0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.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))" }, "&:disabled": { cursor: "not-allowed", opacity: "30%" } } }, ".range-primary": { "@layer daisyui.l1.l2": { color: "var(--color-primary)", "--range-thumb": "var(--color-primary-content)" } }, ".range-secondary": { "@layer daisyui.l1.l2": { color: "var(--color-secondary)", "--range-thumb": "var(--color-secondary-content)" } }, ".range-accent": { "@layer daisyui.l1.l2": { color: "var(--color-accent)", "--range-thumb": "var(--color-accent-content)" } }, ".range-neutral": { "@layer daisyui.l1.l2": { color: "var(--color-neutral)", "--range-thumb": "var(--color-neutral-content)" } }, ".range-success": { "@layer daisyui.l1.l2": { color: "var(--color-success)", "--range-thumb": "var(--color-success-content)" } }, ".range-warning": { "@layer daisyui.l1.l2": { color: "var(--color-warning)", "--range-thumb": "var(--color-warning-content)" } }, ".range-info": { "@layer daisyui.l1.l2": { color: "var(--color-info)", "--range-thumb": "var(--color-info-content)" } }, ".range-error": { "@layer daisyui.l1.l2": { color: "var(--color-error)", "--range-thumb": "var(--color-error-content)" } }, ".range-xs": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 4)" } }, ".range-sm": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 5)" } }, ".range-md": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 6)" } }, ".range-lg": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 7)" } }, ".range-xl": { "@layer daisyui.l1.l2": { "--range-thumb-size": "calc(var(--size-selector, 0.25rem) * 8)" } } }; // packages/daisyui/components/range/index.js var range_default = ({ addComponents, prefix = "" }) => { @@ -919,7 +919,7 @@ var glass_default = ({ addUtilities, prefix = "" }) => { }; // packages/daisyui/utilities/join/object.js -var object_default67 = { ".join": { display: "inline-flex", "align-items": "stretch", "--join-ss": "0", "--join-se": "0", "--join-es": "0", "--join-ee": "0", ":where(.join-item)": { "border-start-start-radius": "var(--join-ss, 0)", "border-start-end-radius": "var(--join-se, 0)", "border-end-start-radius": "var(--join-es, 0)", "border-end-end-radius": "var(--join-ee, 0)", "*": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:first-child)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:where(:last-child)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ":where(.join-item)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:only-child)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } } }, ".join-item": { "&:where(*:not(:first-child, :disabled, [disabled], .btn-disabled))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" }, "&:where(*:is(:disabled, [disabled], .btn-disabled))": { "border-width": "var(--border, 1px) 0 var(--border, 1px) var(--border, 1px)" } }, ".join-vertical": { "flex-direction": "column", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "0", "margin-block-start": "calc(var(--border, 1px) * -1)" } } }, ".join-horizontal": { "flex-direction": "row", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" } } } }; +var object_default67 = { ".join": { display: "inline-flex", "align-items": "stretch", "--join-ss": "0", "--join-se": "0", "--join-es": "0", "--join-ee": "0", ":where(.join-item)": { "border-start-start-radius": "var(--join-ss, 0)", "border-start-end-radius": "var(--join-se, 0)", "border-end-start-radius": "var(--join-es, 0)", "border-end-end-radius": "var(--join-ee, 0)", "*": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:first-child)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:where(:last-child)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ":where(.join-item)": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:where(:only-child)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ":where(.join-item)": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> :where(:focus, :has(:focus))": { "z-index": 1 }, "@media (hover: hover)": { "> :where(.btn:hover, :has(.btn:hover))": { isolation: "isolate" } } }, ".join-item": { "&:where(*:not(:first-child, :disabled, [disabled], .btn-disabled))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" }, "&:where(*:is(:disabled, [disabled], .btn-disabled))": { "border-width": "var(--border, 1px) 0 var(--border, 1px) var(--border, 1px)" } }, ".join-vertical": { "flex-direction": "column", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "0", "margin-block-start": "calc(var(--border, 1px) * -1)" } } }, ".join-horizontal": { "flex-direction": "row", "> .join-item:first-child": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" }, ":first-child:not(:last-child)": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "0", "--join-es": "var(--radius-field)", "--join-ee": "0" } }, "> .join-item:last-child": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" }, ":last-child:not(:first-child)": { ".join-item": { "--join-ss": "0", "--join-se": "var(--radius-field)", "--join-es": "0", "--join-ee": "var(--radius-field)" } }, "> .join-item:only-child": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" }, ":only-child": { ".join-item": { "--join-ss": "var(--radius-field)", "--join-se": "var(--radius-field)", "--join-es": "var(--radius-field)", "--join-ee": "var(--radius-field)" } }, ".join-item": { "&:where(*:not(:first-child))": { "margin-inline-start": "calc(var(--border, 1px) * -1)", "margin-block-start": "0" } } } }; // packages/daisyui/utilities/join/index.js var join_default = ({ addUtilities, prefix = "" }) => { @@ -951,7 +951,7 @@ var components = { textarea: textarea_default, rating: rating_default, calendar: var utilities = { glass: glass_default, join: join_default, typography: typography_default, radius: radius_default }; // packages/daisyui/index.js -var version = "5.5.5"; +var version = "5.5.19"; var daisyui_default = plugin.withOptions((options) => { return ({ addBase, addComponents, addUtilities, addVariant }) => { const { diff --git a/assets/css/output.css b/assets/css/output.css index d84d77b..fefcd35 100644 --- a/assets/css/output.css +++ b/assets/css/output.css @@ -1,2105 +1,2 @@ -/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ -@layer properties; -@layer theme, base, components, utilities; -@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-gray-800: oklch(27.8% 0.033 256.848); - --color-black: #000; - --color-white: #fff; - --spacing: 0.25rem; - --container-sm: 24rem; - --container-md: 28rem; - --text-xs: 0.75rem; - --text-xs--line-height: calc(1 / 0.75); - --text-sm: 0.875rem; - --text-sm--line-height: calc(1.25 / 0.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-3xl: 1.875rem; - --text-3xl--line-height: calc(2.25 / 1.875); - --font-weight-bold: 700; - --radius-lg: 0.5rem; - --default-transition-duration: 150ms; - --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - --default-font-family: var(--font-sans); - --default-mono-font-family: var(--font-mono); - } -} -@layer base { - *, ::after, ::before, ::backdrop, ::file-selector-button { - box-sizing: border-box; - margin: 0; - padding: 0; - border: 0 solid; - } - html, :host { - line-height: 1.5; - -webkit-text-size-adjust: 100%; - tab-size: 4; - 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; - 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 { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; - } - sub { - bottom: -0.25em; - } - sup { - top: -0.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 { - display: block; - vertical-align: middle; - } - img, video { - max-width: 100%; - height: auto; - } - button, input, select, optgroup, textarea, ::file-selector-button { - font: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - letter-spacing: inherit; - color: inherit; - border-radius: 0; - background-color: transparent; - opacity: 1; - } - :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)) { - 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, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-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']), ::file-selector-button { - appearance: button; - } - ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { - height: auto; - } - [hidden]:where(:not([hidden='until-found'])) { - display: none !important; - } -} -@layer utilities { - .tab { - @layer daisyui.l1.l2.l3 { - position: relative; - display: inline-flex; - cursor: pointer; - appearance: none; - flex-wrap: wrap; - align-items: center; - justify-content: center; - text-align: center; - webkit-user-select: none; - user-select: none; - &:hover { - @media (hover: hover) { - color: var(--color-base-content); - } - } - --tab-p: 0.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(0.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(calc(69% - var(--border)) + 0.25px), - var(--tab-border-color) calc(calc(69% - var(--border)) + var(--border)), - var(--tab-bg) calc(calc(69% - var(--border)) + var(--border) + 0.25px); - border-color: #0000; - order: var(--tab-order); - height: var(--tab-height); - font-size: 0.875rem; - padding-inline-start: var(--tab-p); - padding-inline-end: var(--tab-p); - &:is(input[type="radio"]) { - min-width: fit-content; - &:after { - --tw-content: attr(aria-label); - content: var(--tw-content); - } - } - &:is(label) { - position: relative; - input { - position: absolute; - inset: calc(0.25rem * 0); - cursor: pointer; - appearance: none; - opacity: 0%; - } - } - &:checked, &:is(label:has(:checked)), &:is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]) { - & + .tab-content { - display: block; - } - } - &: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)) { - color: color-mix(in oklab, var(--color-base-content) 50%, transparent); - } - } - &:not(input):empty { - flex-grow: 1; - cursor: default; - } - &:focus { - --tw-outline-style: none; - outline-style: none; - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: 2px; - } - } - &:focus-visible, &:is(label:has(:checked:focus-visible)) { - outline: 2px solid currentColor; - outline-offset: -5px; - } - &[disabled] { - pointer-events: none; - opacity: 40%; - } - } - } - .btn { - :where(&) { - @layer daisyui.l1.l2.l3 { - width: unset; - } - } - @layer daisyui.l1.l2.l3 { - display: inline-flex; - flex-shrink: 0; - cursor: pointer; - flex-wrap: nowrap; - align-items: center; - justify-content: center; - gap: calc(0.25rem * 1.5); - text-align: center; - vertical-align: middle; - outline-offset: 2px; - 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, 0.875rem); - font-weight: 600; - outline-color: var(--btn-color, var(--color-base-content)); - transition-property: color, background-color, border-color, box-shadow; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: 0.2s; - border-start-start-radius: var(--join-ss, var(--radius-field)); - border-start-end-radius: var(--join-se, var(--radius-field)); - border-end-start-radius: var(--join-es, var(--radius-field)); - border-end-end-radius: var(--join-ee, var(--radius-field)); - 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 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15)); - touch-action: manipulation; - box-shadow: 0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow); - --size: calc(var(--size-field, 0.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); - @supports (color: color-mix(in lab, red, red)) { - --btn-border: color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%)); - } - --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-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-noise: var(--fx-noise); - @media (hover: hover) { - &:hover { - --btn-bg: var(--btn-color, var(--color-base-200)); - @supports (color: color-mix(in lab, red, red)) { - --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%); - } - } - } - &:focus-visible, &:has(:focus-visible) { - outline-width: 2px; - outline-style: solid; - isolation: isolate; - } - &:active:not(.btn-active) { - translate: 0 0.5px; - --btn-bg: var(--btn-color, var(--color-base-200)); - @supports (color: color-mix(in lab, red, red)) { - --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%); - } - --btn-border: var(--btn-color, var(--color-base-200)); - @supports (color: color-mix(in lab, red, red)) { - --btn-border: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%); - } - --btn-shadow: 0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0); - } - &:is(input[type="checkbox"], input[type="radio"]) { - appearance: none; - &[aria-label]::after { - --tw-content: attr(aria-label); - content: var(--tw-content); - } - } - &:where(input:checked:not(.filter .btn)) { - --btn-color: var(--color-primary); - --btn-fg: var(--color-primary-content); - isolation: isolate; - } - } - &:disabled { - @layer daisyui.l1.l2 { - &:not(.btn-link, .btn-ghost) { - background-color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); - } - box-shadow: none; - } - pointer-events: none; - --btn-border: #0000; - --btn-noise: none; - --btn-fg: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000); - } - } - } - &[disabled] { - @layer daisyui.l1.l2 { - &:not(.btn-link, .btn-ghost) { - background-color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); - } - box-shadow: none; - } - pointer-events: none; - --btn-border: #0000; - --btn-noise: none; - --btn-fg: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000); - } - } - } - } - .countdown { - &.countdown { - line-height: 1em; - } - @layer daisyui.l1.l2.l3 { - display: inline-flex; - & > * { - visibility: hidden; - position: relative; - display: inline-block; - overflow-y: clip; - transition: width 0.4s ease-out 0.2s; - height: 1em; - --value-v: calc(mod(max(0, var(--value)), 1000)); - --value-hundreds: calc(round(to-zero, var(--value-v) / 100, 1)); - --value-tens: calc(round(to-zero, mod(var(--value-v), 100) / 10, 1)); - --value-ones: calc(mod(var(--value-v), 100)); - --show-hundreds: clamp(clamp(0, var(--digits, 1) - 2, 1), var(--value-hundreds), 1); - --show-tens: clamp( - clamp(0, var(--digits, 1) - 1, 1), - var(--value-tens) + var(--show-hundreds), - 1 - ); - --first-digits: calc(round(to-zero, var(--value-v) / 10, 1)); - width: calc(1ch + var(--show-tens) * 1ch + var(--show-hundreds) * 1ch); - direction: ltr; - &:before, &:after { - visibility: visible; - position: absolute; - overflow-x: clip; - --tw-content: "00\A 01\A 02\A 03\A 04\A 05\A 06\A 07\A 08\A 09\A 10\A 11\A 12\A 13\A 14\A 15\A 16\A 17\A 18\A 19\A 20\A 21\A 22\A 23\A 24\A 25\A 26\A 27\A 28\A 29\A 30\A 31\A 32\A 33\A 34\A 35\A 36\A 37\A 38\A 39\A 40\A 41\A 42\A 43\A 44\A 45\A 46\A 47\A 48\A 49\A 50\A 51\A 52\A 53\A 54\A 55\A 56\A 57\A 58\A 59\A 60\A 61\A 62\A 63\A 64\A 65\A 66\A 67\A 68\A 69\A 70\A 71\A 72\A 73\A 74\A 75\A 76\A 77\A 78\A 79\A 80\A 81\A 82\A 83\A 84\A 85\A 86\A 87\A 88\A 89\A 90\A 91\A 92\A 93\A 94\A 95\A 96\A 97\A 98\A 99\A"; - content: var(--tw-content); - font-variant-numeric: tabular-nums; - white-space: pre; - text-align: end; - direction: rtl; - transition: all 1s cubic-bezier(1, 0, 0, 1), width 0.2s ease-out 0.2s, opacity 0.2s ease-out 0.2s; - } - &:before { - width: calc(1ch + var(--show-hundreds) * 1ch); - top: calc(var(--first-digits) * -1em); - inset-inline-end: 0; - opacity: var(--show-tens); - } - &:after { - width: 1ch; - top: calc(var(--value-ones) * -1em); - inset-inline-start: 0; - } - } - } - } - .input { - @layer daisyui.l1.l2.l3 { - cursor: text; - border: var(--border) solid #0000; - position: relative; - display: inline-flex; - flex-shrink: 1; - appearance: none; - align-items: center; - gap: calc(0.25rem * 2); - background-color: var(--color-base-100); - padding-inline: calc(0.25rem * 3); - vertical-align: middle; - white-space: nowrap; - width: clamp(3rem, 20rem, 100%); - height: var(--size); - font-size: max(var(--font-size, 0.875rem), 0.875rem); - touch-action: manipulation; - border-start-start-radius: var(--join-ss, var(--radius-field)); - border-start-end-radius: var(--join-se, var(--radius-field)); - border-end-start-radius: var(--join-es, var(--radius-field)); - border-end-end-radius: var(--join-ee, var(--radius-field)); - border-color: var(--input-color); - box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; - @supports (color: color-mix(in lab, red, red)) { - 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) * 0.1)) inset; - } - --size: calc(var(--size-field, 0.25rem) * 10); - --input-color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000); - } - &:where(input) { - display: inline-flex; - } - :where(input) { - display: inline-flex; - height: 100%; - width: 100%; - appearance: none; - background-color: transparent; - border: none; - &:focus, &:focus-within { - --tw-outline-style: none; - outline-style: none; - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: 2px; - } - } - } - :where(input[type="url"]), :where(input[type="email"]) { - direction: ltr; - } - :where(input[type="date"]) { - display: inline-flex; - } - &:focus, &:focus-within { - --input-color: var(--color-base-content); - box-shadow: 0 1px var(--input-color); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000); - } - outline: 2px solid var(--input-color); - outline-offset: 2px; - isolation: isolate; - z-index: 1; - } - @media (pointer: coarse) { - @supports (-webkit-touch-callout: none) { - &:focus, &:focus-within { - --font-size: 1rem; - } - } - } - &:has(> input[disabled]), &:is(:disabled, [disabled]), fieldset: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)) { - color: color-mix(in oklab, var(--color-base-content) 40%, transparent); - } - &::placeholder { - color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - color: color-mix(in oklab, var(--color-base-content) 20%, transparent); - } - } - box-shadow: none; - } - &:has(> input[disabled]) > input[disabled] { - cursor: not-allowed; - } - &::-webkit-date-and-time-value { - text-align: inherit; - } - &[type="number"] { - &::-webkit-inner-spin-button { - margin-block: calc(0.25rem * -3); - margin-inline-end: calc(0.25rem * -3); - } - } - &::-webkit-calendar-picker-indicator { - position: absolute; - inset-inline-end: 0.75em; - } - &:has(> input[type="date"]) { - :where(input[type="date"]) { - display: inline-flex; - webkit-appearance: none; - appearance: none; - } - input[type="date"]::-webkit-calendar-picker-indicator { - position: absolute; - inset-inline-end: 0.75em; - width: 1em; - height: 1em; - cursor: pointer; - } - } - } - } - .steps { - @layer daisyui.l1.l2.l3 { - display: inline-grid; - grid-auto-flow: column; - overflow: hidden; - overflow-x: auto; - counter-reset: step; - grid-auto-columns: 1fr; - .step { - display: grid; - grid-template-columns: repeat(1, minmax(0, 1fr)); - grid-template-columns: auto; - grid-template-rows: repeat(2, minmax(0, 1fr)); - grid-template-rows: 40px 1fr; - place-items: center; - text-align: center; - min-width: 4rem; - --step-bg: var(--color-base-300); - --step-fg: var(--color-base-content); - &:before { - top: calc(0.25rem * 0); - grid-column-start: 1; - grid-row-start: 1; - height: calc(0.25rem * 2); - width: 100%; - border: 1px solid; - color: var(--step-bg); - background-color: var(--step-bg); - content: ""; - margin-inline-start: -100%; - } - > .step-icon, &: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); - position: relative; - grid-column-start: 1; - grid-row-start: 1; - display: grid; - height: calc(0.25rem * 8); - width: calc(0.25rem * 8); - place-items: center; - place-self: center; - border-radius: calc(infinity * 1px); - } - &:first-child:before { - --tw-content: none; - content: var(--tw-content); - } - &[data-content]:after { - --tw-content: attr(data-content); - content: var(--tw-content); - } - } - } - @layer daisyui.l1.l2 { - .step-neutral { - + .step-neutral:before, &:after, > .step-icon { - --step-bg: var(--color-neutral); - --step-fg: var(--color-neutral-content); - } - } - .step-primary { - + .step-primary:before, &:after, > .step-icon { - --step-bg: var(--color-primary); - --step-fg: var(--color-primary-content); - } - } - .step-secondary { - + .step-secondary:before, &:after, > .step-icon { - --step-bg: var(--color-secondary); - --step-fg: var(--color-secondary-content); - } - } - .step-accent { - + .step-accent:before, &:after, > .step-icon { - --step-bg: var(--color-accent); - --step-fg: var(--color-accent-content); - } - } - .step-info { - + .step-info:before, &:after, > .step-icon { - --step-bg: var(--color-info); - --step-fg: var(--color-info-content); - } - } - .step-success { - + .step-success:before, &:after, > .step-icon { - --step-bg: var(--color-success); - --step-fg: var(--color-success-content); - } - } - .step-warning { - + .step-warning:before, &:after, > .step-icon { - --step-bg: var(--color-warning); - --step-fg: var(--color-warning-content); - } - } - .step-error { - + .step-error:before, &:after, > .step-icon { - --step-bg: var(--color-error); - --step-fg: var(--color-error-content); - } - } - } - } - .range { - @layer daisyui.l1.l2.l3 { - appearance: none; - webkit-appearance: none; - --range-thumb: var(--color-base-100); - --range-thumb-size: calc(var(--size-selector, 0.25rem) * 6); - --range-progress: currentColor; - --range-fill: 1; - --range-p: 0.25rem; - --range-bg: currentColor; - @supports (color: color-mix(in lab, red, red)) { - --range-bg: color-mix(in oklab, currentColor 10%, #0000); - } - cursor: pointer; - overflow: hidden; - background-color: transparent; - vertical-align: middle; - width: clamp(3rem, 20rem, 100%); - --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))); - border: none; - height: var(--range-thumb-size); - [dir="rtl"] & { - --range-dir: -1; - } - &:focus { - outline: none; - } - &:focus-visible { - outline: 2px solid; - outline-offset: 2px; - } - &::-webkit-slider-runnable-track { - width: 100%; - background-color: var(--range-bg); - border-radius: var(--radius-selector); - height: calc(var(--range-thumb-size) * 0.5); - } - @media (forced-colors: active) { - &::-webkit-slider-runnable-track { - border: 1px solid; - } - } - @media (forced-colors: active) { - &::-moz-range-track { - border: 1px solid; - } - } - &::-webkit-slider-thumb { - position: relative; - 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; - top: 50%; - color: var(--range-progress); - transform: translateY(-50%); - box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px currentColor, 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill)); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.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) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill)); - } - } - &::-moz-range-track { - width: 100%; - background-color: var(--range-bg); - border-radius: var(--radius-selector); - height: calc(var(--range-thumb-size) * 0.5); - } - &::-moz-range-thumb { - position: relative; - box-sizing: border-box; - border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max))); - background-color: currentColor; - height: var(--range-thumb-size); - width: var(--range-thumb-size); - border: var(--range-p) solid; - top: 50%; - color: var(--range-progress); - box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px currentColor, 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill)); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.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) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill)); - } - } - &:disabled { - cursor: not-allowed; - opacity: 30%; - } - } - } - .select { - @layer daisyui.l1.l2.l3 { - border: var(--border) solid #0000; - position: relative; - display: inline-flex; - flex-shrink: 1; - appearance: none; - align-items: center; - gap: calc(0.25rem * 1.5); - background-color: var(--color-base-100); - padding-inline-start: calc(0.25rem * 3); - padding-inline-end: calc(0.25rem * 7); - vertical-align: middle; - width: clamp(3rem, 20rem, 100%); - height: var(--size); - font-size: 0.875rem; - touch-action: manipulation; - border-start-start-radius: var(--join-ss, var(--radius-field)); - border-start-end-radius: var(--join-se, var(--radius-field)); - border-end-start-radius: var(--join-es, var(--radius-field)); - border-end-end-radius: var(--join-ee, var(--radius-field)); - 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-size: 4px 4px, 4px 4px; - background-repeat: no-repeat; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - box-shadow: 0 1px var(--input-color) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset; - @supports (color: color-mix(in lab, red, red)) { - 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) * 0.1)) inset; - } - border-color: var(--input-color); - --input-color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000); - } - --size: calc(var(--size-field, 0.25rem) * 10); - [dir="rtl"] & { - background-position: calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%); - &::picker(select), select::picker(select) { - translate: 0.5rem 0; - } - } - &[multiple] { - height: auto; - overflow: auto; - padding-block: calc(0.25rem * 3); - padding-inline-end: calc(0.25rem * 3); - background-image: none; - } - select { - margin-inline-start: calc(0.25rem * -3); - margin-inline-end: calc(0.25rem * -7); - width: calc(100% + 2.75rem); - appearance: none; - padding-inline-start: calc(0.25rem * 3); - padding-inline-end: calc(0.25rem * 7); - height: calc(100% - calc(var(--border) * 2)); - align-items: center; - background: inherit; - border-radius: inherit; - border-style: none; - &:focus, &:focus-within { - --tw-outline-style: none; - outline-style: none; - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: 2px; - } - } - &:not(:last-child) { - margin-inline-end: calc(0.25rem * -5.5); - background-image: none; - } - } - &:focus, &:focus-within { - --input-color: var(--color-base-content); - box-shadow: 0 1px var(--input-color); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000); - } - outline: 2px solid var(--input-color); - outline-offset: 2px; - isolation: isolate; - z-index: 1; - } - &:has(> select[disabled]), &:is(:disabled, [disabled]), fieldset: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)) { - color: color-mix(in oklab, var(--color-base-content) 40%, transparent); - } - &::placeholder { - color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - color: color-mix(in oklab, var(--color-base-content) 20%, transparent); - } - } - } - &:has(> select[disabled]) > select[disabled] { - cursor: not-allowed; - } - &, & select { - @supports (appearance: base-select) { - appearance: base-select; - } - @supports (appearance: base-select) { - &::picker(select) { - appearance: base-select; - } - } - &::picker(select) { - color: inherit; - max-height: min(24rem, 70dvh); - margin-inline: 0.5rem; - translate: -0.5rem 0; - border: var(--border) solid var(--color-base-200); - margin-block: calc(0.25rem * 2); - border-radius: var(--radius-box); - padding: calc(0.25rem * 2); - background-color: inherit; - box-shadow: 0 2px calc(var(--depth) * 3px) -2px oklch(0% 0 0/0.2); - box-shadow: 0 20px 25px -5px rgb(0 0 0 / calc(var(--depth) * 0.1)), 0 8px 10px -6px rgb(0 0 0 / calc(var(--depth) * 0.1)); - } - &::picker-icon { - display: none; - } - optgroup { - padding-top: 0.5em; - option { - &:nth-child(1) { - margin-top: 0.5em; - } - } - } - option { - border-radius: var(--radius-field); - padding-inline: calc(0.25rem * 3); - padding-block: calc(0.25rem * 1.5); - transition-property: color, background-color; - transition-duration: 0.2s; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - white-space: normal; - &:not(:disabled) { - &:hover, &:focus-visible { - cursor: pointer; - background-color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent); - } - --tw-outline-style: none; - outline-style: none; - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: 2px; - } - } - &:active { - background-color: var(--color-neutral); - color: var(--color-neutral-content); - box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--color-neutral); - } - } - } - } - } - } - .progress { - @layer daisyui.l1.l2.l3 { - position: relative; - height: calc(0.25rem * 2); - width: 100%; - appearance: none; - overflow: hidden; - border-radius: var(--radius-box); - background-color: currentcolor; - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, currentcolor 20%, transparent); - } - color: var(--color-base-content); - &:indeterminate { - background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% ); - background-size: 200%; - background-position-x: 15%; - @media (prefers-reduced-motion: no-preference) { - animation: progress 5s ease-in-out infinite; - } - @supports (-moz-appearance: none) { - &::-moz-progress-bar { - background-color: transparent; - @media (prefers-reduced-motion: no-preference) { - animation: progress 5s ease-in-out infinite; - background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% ); - background-size: 200%; - background-position-x: 15%; - } - } - } - } - @supports (-moz-appearance: none) { - &::-moz-progress-bar { - border-radius: var(--radius-box); - background-color: currentcolor; - } - } - @supports (-webkit-appearance: none) { - &::-webkit-progress-bar { - border-radius: var(--radius-box); - background-color: transparent; - } - &::-webkit-progress-value { - border-radius: var(--radius-box); - background-color: currentColor; - } - } - } - } - .btn-active { - @layer daisyui.l1.l2 { - --btn-bg: var(--btn-color, var(--color-base-200)); - @supports (color: color-mix(in lab, red, red)) { - --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%); - } - --btn-shadow: 0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0); - isolation: isolate; - } - } - .container { - width: 100%; - @media (width >= 40rem) { - max-width: 40rem; - } - @media (width >= 48rem) { - max-width: 48rem; - } - @media (width >= 64rem) { - max-width: 64rem; - } - @media (width >= 80rem) { - max-width: 80rem; - } - @media (width >= 96rem) { - max-width: 96rem; - } - } - .mx-auto { - margin-inline: auto; - } - .my-2 { - margin-block: calc(var(--spacing) * 2); - } - .my-4 { - margin-block: calc(var(--spacing) * 4); - } - .label { - @layer daisyui.l1.l2.l3 { - display: inline-flex; - align-items: center; - gap: calc(0.25rem * 1.5); - white-space: nowrap; - color: currentcolor; - @supports (color: color-mix(in lab, red, red)) { - color: color-mix(in oklab, currentcolor 60%, transparent); - } - &:has(input) { - cursor: pointer; - } - &:is(.input > *, .select > *) { - display: flex; - height: calc(100% - 0.5rem); - align-items: center; - padding-inline: calc(0.25rem * 3); - white-space: nowrap; - font-size: inherit; - &:first-child { - margin-inline-start: calc(0.25rem * -3); - margin-inline-end: calc(0.25rem * 3); - border-inline-end: var(--border) solid currentColor; - @supports (color: color-mix(in lab, red, red)) { - border-inline-end: var(--border) solid color-mix(in oklab, currentColor 10%, #0000); - } - } - &:last-child { - margin-inline-start: calc(0.25rem * 3); - margin-inline-end: calc(0.25rem * -3); - border-inline-start: var(--border) solid currentColor; - @supports (color: color-mix(in lab, red, red)) { - border-inline-start: var(--border) solid color-mix(in oklab, currentColor 10%, #0000); - } - } - } - } - } - .tabs-box { - @layer daisyui.l1.l2 { - background-color: var(--color-base-200); - padding: calc(0.25rem * 1); - --tabs-box-radius: calc(3 * var(--radius-field)); - border-radius: calc( min(calc(var(--tab-height) / 2), var(--radius-field)) + min(0.25rem, var(--tabs-box-radius)) ); - box-shadow: 0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0.5px oklch(0% 0 0 / calc(var(--depth) * 0.05)) inset; - > .tab { - border-radius: var(--radius-field); - border-style: none; - &:focus-visible, &:is(label:has(:checked:focus-visible)) { - outline-offset: 2px; - } - } - > :is(.tab-active, [aria-selected="true"], [aria-current="true"], [aria-current="page"]):not( .tab-disabled, [disabled] ), > :is(input:checked), > :is(label:has(:checked)) { - background-color: var(--tab-bg, var(--color-base-100)); - box-shadow: 0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px var(--color-neutral), 0 1px 6px -4px var(--color-neutral); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000); - } - @media (forced-colors: active) { - border: 1px solid; - } - } - > .tab-content { - margin-top: calc(0.25rem * 1); - height: calc(100% - var(--tab-height) + var(--border) - 0.5rem); - border-radius: calc( min(calc(var(--tab-height) / 2), var(--radius-field)) + min(0.25rem, var(--tabs-box-radius)) - var(--border) ); - } - } - } - .mt-2 { - margin-top: calc(var(--spacing) * 2); - } - .mt-4 { - margin-top: calc(var(--spacing) * 4); - } - .mt-6 { - margin-top: calc(var(--spacing) * 6); - } - .mt-8 { - margin-top: calc(var(--spacing) * 8); - } - .mb-2 { - margin-bottom: calc(var(--spacing) * 2); - } - .mb-4 { - margin-bottom: calc(var(--spacing) * 4); - } - .mb-6 { - margin-bottom: calc(var(--spacing) * 6); - } - .ml-4 { - margin-left: calc(var(--spacing) * 4); - } - .status { - @layer daisyui.l1.l2.l3 { - display: inline-block; - aspect-ratio: 1 / 1; - width: calc(0.25rem * 2); - height: calc(0.25rem * 2); - border-radius: var(--radius-selector); - background-color: var(--color-base-content); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-base-content) 20%, transparent); - } - background-position: center; - background-repeat: no-repeat; - vertical-align: middle; - color: color-mix(in srgb, #000 30%, transparent); - @supports (color: color-mix(in lab, red, red)) { - color: color-mix(in oklab, var(--color-black) 30%, transparent); - } - background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), #0000 ); - box-shadow: 0 2px 3px -1px currentColor; - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000); - } - } - } - .tabs { - @layer daisyui.l1.l2.l3 { - display: flex; - flex-wrap: wrap; - --tabs-height: auto; - --tabs-direction: row; - --tab-height: calc(var(--size-field, 0.25rem) * 10); - height: var(--tabs-height); - flex-direction: var(--tabs-direction); - } - } - .alert { - border-width: var(--border); - border-color: var(--alert-border-color, var(--color-base-200)); - @layer daisyui.l1.l2.l3 { - border-style: solid; - --alert-border-color: var(--color-base-200); - display: grid; - align-items: center; - gap: calc(0.25rem * 4); - border-radius: var(--radius-box); - padding-inline: calc(0.25rem * 4); - padding-block: calc(0.25rem * 3); - color: var(--color-base-content); - background-color: var(--alert-color, var(--color-base-200)); - justify-content: start; - justify-items: start; - grid-auto-flow: column; - grid-template-columns: auto; - text-align: start; - font-size: 0.875rem; - line-height: 1.25rem; - 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) * 0.08)) inset, 0 1px #000, 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08)); - @supports (color: color-mix(in lab, red, red)) { - box-shadow: 0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.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) * 0.08)); - } - &:has(:nth-child(2)) { - grid-template-columns: auto minmax(auto, 1fr); - } - } - } - .fieldset { - @layer daisyui.l1.l2.l3 { - display: grid; - gap: calc(0.25rem * 1.5); - padding-block: calc(0.25rem * 1); - font-size: 0.75rem; - grid-template-columns: 1fr; - grid-auto-rows: max-content; - } - } - .join { - display: inline-flex; - align-items: stretch; - --join-ss: 0; - --join-se: 0; - --join-es: 0; - --join-ee: 0; - :where(.join-item) { - border-start-start-radius: var(--join-ss, 0); - border-start-end-radius: var(--join-se, 0); - border-end-start-radius: var(--join-es, 0); - border-end-end-radius: var(--join-ee, 0); - * { - --join-ss: var(--radius-field); - --join-se: var(--radius-field); - --join-es: var(--radius-field); - --join-ee: var(--radius-field); - } - } - > .join-item:where(:first-child) { - --join-ss: var(--radius-field); - --join-se: 0; - --join-es: var(--radius-field); - --join-ee: 0; - } - :first-child:not(:last-child) { - :where(.join-item) { - --join-ss: var(--radius-field); - --join-se: 0; - --join-es: var(--radius-field); - --join-ee: 0; - } - } - > .join-item:where(:last-child) { - --join-ss: 0; - --join-se: var(--radius-field); - --join-es: 0; - --join-ee: var(--radius-field); - } - :last-child:not(:first-child) { - :where(.join-item) { - --join-ss: 0; - --join-se: var(--radius-field); - --join-es: 0; - --join-ee: var(--radius-field); - } - } - > .join-item:where(:only-child) { - --join-ss: var(--radius-field); - --join-se: var(--radius-field); - --join-es: var(--radius-field); - --join-ee: var(--radius-field); - } - :only-child { - :where(.join-item) { - --join-ss: var(--radius-field); - --join-se: var(--radius-field); - --join-es: var(--radius-field); - --join-ee: var(--radius-field); - } - } - } - .chat { - @layer daisyui.l1.l2.l3 { - display: grid; - column-gap: calc(0.25rem * 3); - padding-block: calc(0.25rem * 1); - --mask-chat: url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e"); - } - } - .flex { - display: flex; - } - .grid { - display: grid; - } - .btn-square { - @layer daisyui.l1.l2 { - padding-inline: calc(0.25rem * 0); - width: var(--size); - height: var(--size); - } - } - .w-full { - width: 100%; - } - .max-w-md { - max-width: var(--container-md); - } - .max-w-sm { - max-width: var(--container-sm); - } - .flex-1 { - flex: 1; - } - .link { - @layer daisyui.l1.l2.l3 { - cursor: pointer; - text-decoration-line: underline; - &:focus { - --tw-outline-style: none; - outline-style: none; - @media (forced-colors: active) { - outline: 2px solid transparent; - outline-offset: 2px; - } - } - &:focus-visible { - outline: 2px solid currentColor; - outline-offset: 2px; - } - } - } - .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-1 { - gap: calc(var(--spacing) * 1); - } - .gap-2 { - gap: calc(var(--spacing) * 2); - } - .gap-4 { - gap: calc(var(--spacing) * 4); - } - .gap-8 { - gap: calc(var(--spacing) * 8); - } - .rounded-lg { - border-radius: var(--radius-lg); - } - .border-none { - --tw-border-style: none; - border-style: none; - } - .bg-base-200 { - background-color: var(--color-base-200); - } - .bg-white { - background-color: var(--color-white); - } - .p-2 { - padding: calc(var(--spacing) * 2); - } - .p-3 { - padding: calc(var(--spacing) * 3); - } - .p-4 { - padding: calc(var(--spacing) * 4); - } - .px-2 { - padding-inline: calc(var(--spacing) * 2); - } - .py-1 { - padding-block: calc(var(--spacing) * 1); - } - .text-center { - text-align: center; - } - .text-left { - text-align: left; - } - .font-mono { - font-family: var(--font-mono); - } - .text-3xl { - font-size: var(--text-3xl); - line-height: var(--tw-leading, var(--text-3xl--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); - } - .break-all { - word-break: break-all; - } - .alert-error { - @layer daisyui.l1.l2 { - color: var(--color-error-content); - --alert-border-color: var(--color-error); - --alert-color: var(--color-error); - } - } - .alert-info { - @layer daisyui.l1.l2 { - color: var(--color-info-content); - --alert-border-color: var(--color-info); - --alert-color: var(--color-info); - } - } - .alert-success { - @layer daisyui.l1.l2 { - color: var(--color-success-content); - --alert-border-color: var(--color-success); - --alert-color: var(--color-success); - } - } - .alert-warning { - @layer daisyui.l1.l2 { - color: var(--color-warning-content); - --alert-border-color: var(--color-warning); - --alert-color: var(--color-warning); - } - } - .text-base-content { - color: var(--color-base-content); - } - .text-gray-800 { - color: var(--color-gray-800); - } - .text-success { - color: var(--color-success); - } - .no-underline { - text-decoration-line: none; - } - .opacity-40 { - opacity: 40%; - } - .opacity-60 { - opacity: 60%; - } - .opacity-70 { - opacity: 70%; - } - .btn-ghost { - @layer daisyui.l1 { - &:not(.btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn)) { - --btn-shadow: ""; - --btn-bg: #0000; - --btn-border: #0000; - --btn-noise: none; - &:not(:disabled, [disabled], .btn-disabled) { - outline-color: currentcolor; - --btn-fg: var(--btn-color, currentColor); - } - } - @media (hover: none) { - &:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover { - outline-color: currentcolor; - --btn-shadow: ""; - --btn-bg: #0000; - --btn-fg: var(--btn-color, currentColor); - --btn-border: #0000; - --btn-noise: none; - } - } - } - } - .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)); - } - .btn-sm { - @layer daisyui.l1.l2 { - --fontsize: 0.75rem; - --btn-p: 0.75rem; - --size: calc(var(--size-field, 0.25rem) * 8); - } - } - .btn-primary { - @layer daisyui.l1.l2.l3 { - --btn-color: var(--color-primary); - --btn-fg: var(--color-primary-content); - } - } - .btn-secondary { - @layer daisyui.l1.l2.l3 { - --btn-color: var(--color-secondary); - --btn-fg: var(--color-secondary-content); - } - } - .hover\:bg-base-300 { - &:hover { - @media (hover: hover) { - background-color: var(--color-base-300); - } - } - } - .hover\:btn-error { - &:hover { - @media (hover: hover) { - @layer daisyui.l1.l2.l3 { - --btn-color: var(--color-error); - --btn-fg: var(--color-error-content); - } - } - } - } -} -.board { - display: flex; - gap: 8px; - background: #334; - padding: 16px; - border-radius: 12px; -} -.column { - display: flex; - flex-direction: column; - gap: 8px; - padding: 4px; - border-radius: 8px; -} -.column.clickable { - cursor: pointer; -} -.column.clickable:hover { - background: rgba(255,255,255,0.08); -} -.cell { - width: 48px; - height: 48px; - border-radius: 50%; - background: #556; - transition: background 0.2s; -} -.cell.red { - background: #4a2a3a; -} -.cell.yellow { - background: #2a4545; -} -.cell.red.active-turn { - animation: glow-red 1.5s ease-in-out infinite alternate; -} -.cell.yellow.active-turn { - animation: glow-yellow 1.5s ease-in-out infinite alternate; -} -.cell.winning { - animation: pulse 0.5s ease-in-out infinite alternate; -} -@keyframes glow-red { - from { - box-shadow: 0 0 4px rgba(74, 42, 58, 0.3); - } - to { - box-shadow: 0 0 12px rgba(74, 42, 58, 0.7); - } -} -@keyframes glow-yellow { - from { - box-shadow: 0 0 4px rgba(42, 69, 69, 0.3); - } - to { - box-shadow: 0 0 12px rgba(42, 69, 69, 0.7); - } -} -@keyframes pulse { - from { - transform: scale(1); - box-shadow: 0 0 4px rgba(0,0,0,0.15); - } - to { - transform: scale(1.03); - box-shadow: 0 0 8px rgba(0,0,0,0.25); - } -} -.player-chip { - width: 20px; - height: 20px; - border-radius: 50%; - background: #445; -} -.player-chip.red { - background: #4a2a3a; -} -.player-chip.yellow { - background: #2a4545; -} -.snake-board { - display: inline-grid; - gap: 0; - background: #556; - border-radius: 8px; - overflow: hidden; - border: 3px solid #445; -} -.snake-row { - display: contents; -} -.snake-cell { - background: #667; - border: 1px solid rgba(0,0,0,0.08); -} -.snake-cell.snake-head { - border-radius: 4px; - animation: head-pop 50ms ease-out; -} -@keyframes head-pop { - 0% { - transform: scale(0.85); - } - 100% { - transform: scale(1); - } -} -.snake-cell.snake-food { - background: #334; - border-radius: 50%; - box-shadow: 0 0 3px rgba(0,0,0,0.2); - animation: food-pulse 1.2s ease-in-out infinite alternate; -} -@keyframes food-pulse { - from { - box-shadow: 0 0 3px rgba(0,0,0,0.1); - transform: scale(0.85); - } - to { - box-shadow: 0 0 6px rgba(0,0,0,0.2); - transform: scale(1); - } -} -.snake-cell.snake-dead { - opacity: 0.35; - filter: grayscale(0.5); - transition: opacity 400ms ease-out; -} -.snake-wrapper:focus { - outline: none; -} -.snake-game-area { - display: flex; - gap: 16px; - align-items: flex-start; - justify-content: center; -} -@media (max-width: 768px) { - .snake-game-area { - flex-direction: column; - align-items: center; - } - .snake-game-area .snake-chat { - width: 100%; - max-width: 480px; - } - .snake-chat-history { - height: 150px; - } -} -.snake-chat { - width: 100%; - max-width: 480px; -} -.snake-game-area .snake-chat { - width: 260px; - max-width: none; - flex-shrink: 0; -} -.snake-chat-history { - height: 300px; - overflow-y: auto; - background: #334; - border-radius: 8px 8px 0 0; - padding: 8px; - display: flex; - flex-direction: column; - gap: 2px; -} -.snake-chat-msg { - font-size: 0.85rem; - line-height: 1.3; -} -.snake-chat-input { - display: flex; - gap: 0; - background: #445; - border-radius: 0 0 8px 8px; - overflow: hidden; -} -.snake-chat-input input { - flex: 1; - padding: 6px 10px; - background: transparent; - border: none; - color: inherit; - outline: none; - font-size: 0.85rem; -} -.snake-chat-input button { - padding: 6px 14px; - background: #556; - border: none; - color: inherit; - cursor: pointer; - font-size: 0.85rem; -} -.snake-chat-input button:hover { - background: #667; -} -.c4-game-area { - display: flex; - gap: 16px; - align-items: flex-start; - justify-content: center; -} -@media (max-width: 768px) { - .c4-game-area { - flex-direction: column; - align-items: center; - } - .c4-game-area .c4-chat { - width: 100%; - max-width: 480px; - } - .c4-chat-history { - height: 150px; - } - .board { - gap: 4px; - padding: 8px; - } - .column { - gap: 4px; - padding: 2px; - } - .cell { - width: 36px; - height: 36px; - } -} -.c4-chat { - width: 100%; - max-width: 480px; -} -.c4-game-area .c4-chat { - width: 260px; - max-width: none; - flex-shrink: 0; -} -.c4-chat-history { - height: 300px; - overflow-y: auto; - background: #334; - border-radius: 8px 8px 0 0; - padding: 8px; - display: flex; - flex-direction: column; - gap: 2px; -} -.c4-chat-msg { - font-size: 0.85rem; - line-height: 1.3; -} -.c4-chat-input { - display: flex; - gap: 0; - background: #445; - border-radius: 0 0 8px 8px; - overflow: hidden; -} -.c4-chat-input input { - flex: 1; - padding: 6px 10px; - background: transparent; - border: none; - color: inherit; - outline: none; - font-size: 0.85rem; -} -.c4-chat-input button { - padding: 6px 14px; - background: #556; - border: none; - color: inherit; - cursor: pointer; - font-size: 0.85rem; -} -.c4-chat-input button:hover { - background: #667; -} -@layer base { - :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% 0.006 285.885); - --color-primary: oklch(45% 0.24 277.023); - --color-primary-content: oklch(93% 0.034 272.788); - --color-secondary: oklch(65% 0.241 354.308); - --color-secondary-content: oklch(94% 0.028 342.258); - --color-accent: oklch(77% 0.152 181.912); - --color-accent-content: oklch(38% 0.063 188.416); - --color-neutral: oklch(14% 0.005 285.823); - --color-neutral-content: oklch(92% 0.004 286.32); - --color-info: oklch(74% 0.16 232.661); - --color-info-content: oklch(29% 0.066 243.157); - --color-success: oklch(76% 0.177 163.223); - --color-success-content: oklch(37% 0.077 168.94); - --color-warning: oklch(82% 0.189 84.429); - --color-warning-content: oklch(41% 0.112 45.904); - --color-error: oklch(71% 0.194 13.428); - --color-error-content: oklch(27% 0.105 12.094); - --radius-selector: 0.5rem; - --radius-field: 0.25rem; - --radius-box: 0.5rem; - --size-selector: 0.25rem; - --size-field: 0.25rem; - --border: 1px; - --depth: 1; - --noise: 0; - } -} -@layer base { - @media (prefers-color-scheme: dark) { - :root:not([data-theme]) { - color-scheme: dark; - --color-base-100: oklch(25.33% 0.016 252.42); - --color-base-200: oklch(23.26% 0.014 253.1); - --color-base-300: oklch(21.15% 0.012 254.09); - --color-base-content: oklch(97.807% 0.029 256.847); - --color-primary: oklch(58% 0.233 277.117); - --color-primary-content: oklch(96% 0.018 272.314); - --color-secondary: oklch(65% 0.241 354.308); - --color-secondary-content: oklch(94% 0.028 342.258); - --color-accent: oklch(77% 0.152 181.912); - --color-accent-content: oklch(38% 0.063 188.416); - --color-neutral: oklch(14% 0.005 285.823); - --color-neutral-content: oklch(92% 0.004 286.32); - --color-info: oklch(74% 0.16 232.661); - --color-info-content: oklch(29% 0.066 243.157); - --color-success: oklch(76% 0.177 163.223); - --color-success-content: oklch(37% 0.077 168.94); - --color-warning: oklch(82% 0.189 84.429); - --color-warning-content: oklch(41% 0.112 45.904); - --color-error: oklch(71% 0.194 13.428); - --color-error-content: oklch(27% 0.105 12.094); - --radius-selector: 0.5rem; - --radius-field: 0.25rem; - --radius-box: 0.5rem; - --size-selector: 0.25rem; - --size-field: 0.25rem; - --border: 1px; - --depth: 1; - --noise: 0; - } - } -} -@layer base { - :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% 0.006 285.885); - --color-primary: oklch(45% 0.24 277.023); - --color-primary-content: oklch(93% 0.034 272.788); - --color-secondary: oklch(65% 0.241 354.308); - --color-secondary-content: oklch(94% 0.028 342.258); - --color-accent: oklch(77% 0.152 181.912); - --color-accent-content: oklch(38% 0.063 188.416); - --color-neutral: oklch(14% 0.005 285.823); - --color-neutral-content: oklch(92% 0.004 286.32); - --color-info: oklch(74% 0.16 232.661); - --color-info-content: oklch(29% 0.066 243.157); - --color-success: oklch(76% 0.177 163.223); - --color-success-content: oklch(37% 0.077 168.94); - --color-warning: oklch(82% 0.189 84.429); - --color-warning-content: oklch(41% 0.112 45.904); - --color-error: oklch(71% 0.194 13.428); - --color-error-content: oklch(27% 0.105 12.094); - --radius-selector: 0.5rem; - --radius-field: 0.25rem; - --radius-box: 0.5rem; - --size-selector: 0.25rem; - --size-field: 0.25rem; - --border: 1px; - --depth: 1; - --noise: 0; - } -} -@layer base { - :root:has(input.theme-controller[value=dark]:checked),[data-theme=dark] { - color-scheme: dark; - --color-base-100: oklch(25.33% 0.016 252.42); - --color-base-200: oklch(23.26% 0.014 253.1); - --color-base-300: oklch(21.15% 0.012 254.09); - --color-base-content: oklch(97.807% 0.029 256.847); - --color-primary: oklch(58% 0.233 277.117); - --color-primary-content: oklch(96% 0.018 272.314); - --color-secondary: oklch(65% 0.241 354.308); - --color-secondary-content: oklch(94% 0.028 342.258); - --color-accent: oklch(77% 0.152 181.912); - --color-accent-content: oklch(38% 0.063 188.416); - --color-neutral: oklch(14% 0.005 285.823); - --color-neutral-content: oklch(92% 0.004 286.32); - --color-info: oklch(74% 0.16 232.661); - --color-info-content: oklch(29% 0.066 243.157); - --color-success: oklch(76% 0.177 163.223); - --color-success-content: oklch(37% 0.077 168.94); - --color-warning: oklch(82% 0.189 84.429); - --color-warning-content: oklch(41% 0.112 45.904); - --color-error: oklch(71% 0.194 13.428); - --color-error-content: oklch(27% 0.105 12.094); - --radius-selector: 0.5rem; - --radius-field: 0.25rem; - --radius-box: 0.5rem; - --size-selector: 0.25rem; - --size-field: 0.25rem; - --border: 1px; - --depth: 1; - --noise: 0; - } -} -@layer base { - :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"); - } -} -@layer base { - :root { - scrollbar-color: currentColor #0000; - @supports (color: color-mix(in lab, red, red)) { - scrollbar-color: color-mix(in oklch, currentColor 35%, #0000) #0000; - } - } -} -@layer base { - @property --radialprogress { - syntax: ""; - inherits: true; - initial-value: 0%; - } -} -@layer base { - :root:not(span) { - overflow: var(--page-overflow); - } -} -@layer base { - :root { - --page-scroll-bg-on: linear-gradient(var(--root-bg), var(--root-bg)) - var(--root-bg); - @supports (color: color-mix(in lab, red, red)) { - --page-scroll-bg-on: linear-gradient(var(--root-bg), var(--root-bg)) - color-mix(in srgb, var(--root-bg), oklch(0% 0 0) calc(var(--page-has-backdrop, 0) * 40%)); - } - --page-scroll-transition-on: background-color 0.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); - } - :root:root { - background: var(--page-scroll-bg, var(--root-bg, var(--color-base-100))); - } - @keyframes set-page-has-scroll { - 0%, to { - --page-has-scroll: 1; - } - } -} -@layer base { - :root, [data-theme] { - background-color: var(--root-bg, var(--color-base-100)); - color: var(--color-base-content); - } - :where(:root, [data-theme]) { - --root-bg: var(--color-base-100); - } -} -@keyframes rating { - 0%, 40% { - scale: 1.1; - filter: brightness(1.05) contrast(1.05); - } -} -@keyframes dropdown { - 0% { - opacity: 0; - } -} -@keyframes radio { - 0% { - padding: 5px; - } - 50% { - padding: 3px; - } -} -@keyframes toast { - 0% { - scale: 0.9; - opacity: 0; - } - 100% { - scale: 1; - opacity: 1; - } -} -@keyframes rotator { - 89.9999%, 100% { - --first-item-position: 0 0%; - } - 90%, 99.9999% { - --first-item-position: 0 calc(var(--items) * 100%); - } - 100% { - translate: 0 -100%; - } -} -@keyframes skeleton { - 0% { - background-position: 150%; - } - 100% { - background-position: -50%; - } -} -@keyframes menu { - 0% { - opacity: 0; - } -} -@keyframes progress { - 50% { - background-position-x: -115%; - } -} -@layer base { - :where(:root),:root:has(input.theme-controller[value=stealth]:checked),[data-theme="stealth"] { - color-scheme: light; - --color-base-100: oklch(78% 0.008 260); - --color-base-200: oklch(72% 0.008 260); - --color-base-300: oklch(64% 0.008 260); - --color-base-content: oklch(22% 0.01 260); - --color-primary: oklch(38% 0.02 260); - --color-primary-content: oklch(90% 0.006 260); - --color-secondary: oklch(52% 0.015 260); - --color-secondary-content: oklch(22% 0.01 260); - --color-accent: oklch(48% 0.02 280); - --color-accent-content: oklch(90% 0.006 260); - --color-neutral: oklch(35% 0.015 260); - --color-neutral-content: oklch(88% 0.006 260); - --color-success: oklch(52% 0.02 160); - --color-success-content: oklch(22% 0.01 160); - --color-warning: oklch(58% 0.02 80); - --color-warning-content: oklch(28% 0.01 80); - --color-error: oklch(45% 0.03 20); - --color-error-content: oklch(90% 0.006 20); - --color-info: oklch(48% 0.02 250); - --color-info-content: oklch(22% 0.01 250); - --radius-selector: 0.5rem; - --radius-field: 0.5rem; - --radius-box: 0.75rem; - --border: 1px; - --depth: 0; - --noise: 0; - } -} -@property --tw-font-weight { - syntax: "*"; - inherits: false; -} -@keyframes pulse { - 50% { - opacity: 0.5; - } -} -@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-font-weight: initial; - } - } -} +/*! 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-font-weight:initial;--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}}}@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-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--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-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--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{--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)}:where(:root),:root:has(input.theme-controller[value=stealth]:checked),[data-theme=stealth]{color-scheme:light;--color-base-100:oklch(78% .008 260);--color-base-200:oklch(72% .008 260);--color-base-300:oklch(64% .008 260);--color-base-content:oklch(22% .01 260);--color-primary:oklch(38% .02 260);--color-primary-content:oklch(90% .006 260);--color-secondary:oklch(52% .015 260);--color-secondary-content:oklch(22% .01 260);--color-accent:oklch(48% .02 280);--color-accent-content:oklch(90% .006 260);--color-neutral:oklch(35% .015 260);--color-neutral-content:oklch(88% .006 260);--color-success:oklch(52% .02 160);--color-success-content:oklch(22% .01 160);--color-warning:oklch(58% .02 80);--color-warning-content:oklch(28% .01 80);--color-error:oklch(45% .03 20);--color-error-content:oklch(90% .006 20);--color-info:oklch(48% .02 250);--color-info-content:oklch(22% .01 250);--radius-selector:.5rem;--radius-field:.5rem;--radius-box:.75rem;--border:1px;--depth:0;--noise:0}}@layer components;@layer utilities{@layer daisyui.l1.l2.l3{.diff{webkit-user-select:none;-webkit-user-select:none;user-select:none;direction:ltr;grid-template-rows:1fr 1.8rem 1fr;grid-template-columns:auto 1fr;width:100%;display:grid;position:relative;overflow:hidden;container-type:inline-size}.diff:focus-visible,.diff:has(.diff-item-1:focus-visible),.diff:focus-visible{outline-style:var(--tw-outline-style);outline-offset:1px;outline-width:2px;outline-color:var(--color-base-content)}.diff:focus-visible .diff-resizer{min-width:95cqi;max-width:95cqi}.diff:has(.diff-item-1:focus-visible){outline-style:var(--tw-outline-style);outline-offset:1px;outline-width:2px}.diff:has(.diff-item-1:focus-visible) .diff-resizer{min-width:5cqi;max-width:5cqi}@supports (-webkit-overflow-scrolling:touch) and (overflow:-webkit-paged-x){.diff:focus .diff-resizer{min-width:5cqi;max-width:5cqi}.diff:has(.diff-item-1:focus) .diff-resizer{min-width:95cqi;max-width:95cqi}}.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}: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}.countdown{display:inline-flex}.countdown>*{visibility:hidden;--value-v:calc(mod(max(0, var(--value)), 1000));--value-hundreds:calc(round(to-zero, var(--value-v) / 100, 1));--value-tens:calc(round(to-zero, mod(var(--value-v), 100) / 10, 1));--value-ones:calc(mod(var(--value-v), 100));--show-hundreds:clamp(clamp(0, var(--digits,1) - 2, 1), var(--value-hundreds), 1);--show-tens:clamp(clamp(0, var(--digits,1) - 1, 1), var(--value-tens) + var(--show-hundreds), 1);--first-digits:calc(round(to-zero, var(--value-v) / 10, 1));height:1em;width:calc(1ch + var(--show-tens) * 1ch + var(--show-hundreds) * 1ch);direction:ltr;transition:width .4s ease-out .2s;display:inline-block;position:relative;overflow-y:clip}.countdown>:before,.countdown>:after{visibility:visible;--tw-content:"00\a 01\a 02\a 03\a 04\a 05\a 06\a 07\a 08\a 09\a 10\a 11\a 12\a 13\a 14\a 15\a 16\a 17\a 18\a 19\a 20\a 21\a 22\a 23\a 24\a 25\a 26\a 27\a 28\a 29\a 30\a 31\a 32\a 33\a 34\a 35\a 36\a 37\a 38\a 39\a 40\a 41\a 42\a 43\a 44\a 45\a 46\a 47\a 48\a 49\a 50\a 51\a 52\a 53\a 54\a 55\a 56\a 57\a 58\a 59\a 60\a 61\a 62\a 63\a 64\a 65\a 66\a 67\a 68\a 69\a 70\a 71\a 72\a 73\a 74\a 75\a 76\a 77\a 78\a 79\a 80\a 81\a 82\a 83\a 84\a 85\a 86\a 87\a 88\a 89\a 90\a 91\a 92\a 93\a 94\a 95\a 96\a 97\a 98\a 99\a ";content:var(--tw-content);font-variant-numeric:tabular-nums;white-space:pre;text-align:end;direction:rtl;transition:all 1s cubic-bezier(1,0,0,1),width .2s ease-out .2s,opacity .2s ease-out .2s;position:absolute;overflow-x:clip}.countdown>:before{width:calc(1ch + var(--show-hundreds) * 1ch);top:calc(var(--first-digits) * -1em);opacity:var(--show-tens);inset-inline-end:0}.countdown>:after{width:1ch;top:calc(var(--value-ones) * -1em);inset-inline-start:0}.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}.indicator{width:max-content;display:inline-flex;position:relative}.indicator :where(.indicator-item){z-index:1;white-space:nowrap;top:var(--indicator-t,0);bottom:var(--indicator-b,auto);left:var(--indicator-s,auto);right:var(--indicator-e,0);translate:var(--indicator-x,50%) var(--indicator-y,-50%);position:absolute}.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)}.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}}.stack{grid-template-rows:3px 4px 1fr 4px 3px;grid-template-columns:3px 4px 1fr 4px 3px;display:inline-grid}.stack>*{width:100%;height:100%}.stack>:nth-child(n+2){opacity:.7;width:100%}.stack>:nth-child(2){z-index:2;opacity:.9}.stack>:first-child{z-index:3;width:100%}.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)}}.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)}}.tabs{--tabs-height:auto;--tabs-direction:row;--tab-height:calc(var(--size-field,.25rem) * 10);height:var(--tabs-height);flex-wrap:wrap;flex-direction:var(--tabs-direction);display:flex}.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}.chat{--mask-chat:url("data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e");grid-auto-rows:min-content;column-gap:.75rem;padding-block:.25rem;display:grid}.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)}.btn-secondary{--btn-color:var(--color-secondary);--btn-fg:var(--color-secondary-content)}}.prose :where(a.btn:not(.btn-link)):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:none}@layer daisyui.l1.l2{.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)}}.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)}.checkbox:disabled,.radio:disabled{cursor:not-allowed;opacity:.2}.btn-active{--btn-bg:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn-active{--btn-bg:color-mix(in oklab, var(--btn-color,var(--color-base-200)), #000 7%)}}.btn-active{--btn-shadow:0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0);isolation:isolate}:is(.stack,.stack.stack-bottom)>*{grid-area:3/3/6/4}:is(.stack,.stack.stack-bottom)>:nth-child(2){grid-area:2/2/5/5}:is(.stack,.stack.stack-bottom)>:first-child{grid-area:1/1/4/6}.stack.stack-top>*{grid-area:1/3/4/4}.stack.stack-top>:nth-child(2){grid-area:2/2/5/5}.stack.stack-top>:first-child{grid-area:3/1/6/6}.stack.stack-start>*{grid-area:3/1/4/4}.stack.stack-start>:nth-child(2){grid-area:2/2/5/5}.stack.stack-start>:first-child{grid-area:1/3/6/6}.stack.stack-end>*{grid-area:3/3/4/6}.stack.stack-end>:nth-child(2){grid-area:2/2/5/5}.stack.stack-end>:first-child{grid-area:1/1/6/4}.tabs-box{background-color:var(--color-base-200);--tabs-box-radius:calc(3 * var(--radius-field));border-radius:calc(min(var(--tab-height) / 2, var(--radius-field)) + min(.25rem, var(--tabs-box-radius)));box-shadow:0 -.5px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 .5px oklch(0% 0 0 / calc(var(--depth) * .05)) inset;padding:.25rem}.tabs-box>.tab{border-radius:var(--radius-field);border-style:none}.tabs-box>.tab:focus-visible,.tabs-box>.tab:is(label:has(:checked:focus-visible)){outline-offset:2px}.tabs-box>.tab:focus-visible{z-index:1}.tabs-box>:is(.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]):not(.tab-disabled,[disabled]),.tabs-box>:is(input:checked),.tabs-box>:is(label:has(:checked)){background-color:var(--tab-bg,var(--color-base-100));box-shadow:0 1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px 1px -1px var(--color-neutral), 0 1px 6px -4px var(--color-neutral)}@supports (color:color-mix(in lab, red, red)){.tabs-box>:is(.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]):not(.tab-disabled,[disabled]),.tabs-box>:is(input:checked),.tabs-box>:is(label:has(:checked)){box-shadow:0 1px oklch(100% 0 0 / calc(var(--depth) * .1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000)}}@media (forced-colors:active){.tabs-box>:is(.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]):not(.tab-disabled,[disabled]),.tabs-box>:is(input:checked),.tabs-box>:is(label:has(:checked)){border:1px solid}}.tabs-box>.tab-content{height:calc(100% - var(--tab-height) + var(--border) - .5rem);border-radius:calc(min(var(--tab-height) / 2, var(--radius-field)) + min(.25rem, var(--tabs-box-radius)) - var(--border));margin-top:.25rem}.btn-square{width:var(--size);height:var(--size);padding-inline:0}.alert-error{color:var(--color-error-content);--alert-border-color:var(--color-error);--alert-color:var(--color-error)}.alert-info{color:var(--color-info-content);--alert-border-color:var(--color-info);--alert-color:var(--color-info)}.alert-success{color:var(--color-success-content);--alert-border-color:var(--color-success);--alert-color:var(--color-success)}.alert-warning{color:var(--color-warning-content);--alert-border-color:var(--color-warning);--alert-color:var(--color-warning)}.btn-sm{--fontsize:.75rem;--btn-p:.75rem;--size:calc(var(--size-field,.25rem) * 8)}}.countdown.countdown{line-height:1em}.visible{visibility:visible}.relative{position:relative}.static{position:static}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.join{--join-ss:0;--join-se:0;--join-es:0;--join-ee:0;align-items:stretch;display:inline-flex}.join :where(.join-item){border-start-start-radius:var(--join-ss,0);border-start-end-radius:var(--join-se,0);border-end-end-radius:var(--join-ee,0);border-end-start-radius:var(--join-es,0)}.join :where(.join-item) *{--join-ss:var(--radius-field);--join-se:var(--radius-field);--join-es:var(--radius-field);--join-ee:var(--radius-field)}.join>.join-item:where(:first-child),.join :first-child:not(:last-child) :where(.join-item){--join-ss:var(--radius-field);--join-se:0;--join-es:var(--radius-field);--join-ee:0}.join>.join-item:where(:last-child),.join :last-child:not(:first-child) :where(.join-item){--join-ss:0;--join-se:var(--radius-field);--join-es:0;--join-ee:var(--radius-field)}.join>.join-item:where(:only-child),.join :only-child :where(.join-item){--join-ss:var(--radius-field);--join-se:var(--radius-field);--join-es:var(--radius-field);--join-ee:var(--radius-field)}.join>:where(:focus,:has(:focus)){z-index:1}@media (hover:hover){.join>:where(.btn:hover,:has(.btn:hover)){isolation:isolate}}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing) * 2)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-4{margin-left:calc(var(--spacing) * 4)}.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}.h-screen{height:100vh}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.flex-1{flex:1}.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-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-8{gap:calc(var(--spacing) * 8)}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.border-none{--tw-border-style:none;border-style:none}.bg-base-200{background-color:var(--color-base-200)}.bg-white{background-color:var(--color-white)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.py-1{padding-block:calc(var(--spacing) * 1)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--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)}.break-all{word-break:break-all}.text-base-content{color:var(--color-base-content)}.text-gray-800{color:var(--color-gray-800)}.text-success{color:var(--color-success)}.no-underline{text-decoration-line:none}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-70{opacity:.7}@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-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))}@media (hover:hover){.hover\:bg-base-300:hover{background-color:var(--color-base-300)}@layer daisyui.l1.l2.l3{.hover\:btn-error:hover{--btn-color:var(--color-error);--btn-fg:var(--color-error-content)}}}}.board{background:#334;border-radius:12px;gap:8px;padding:16px;display:flex}.column{border-radius:8px;flex-direction:column;gap:8px;padding:4px;display:flex}.column.clickable{cursor:pointer}.column.clickable:hover{background:#ffffff14}.cell{background:#556;border-radius:50%;width:48px;height:48px;transition:background .2s}.cell.red{background:#4a2a3a}.cell.yellow{background:#2a4545}.cell.red.active-turn{animation:1.5s ease-in-out infinite alternate glow-red}.cell.yellow.active-turn{animation:1.5s ease-in-out infinite alternate glow-yellow}.cell.winning{animation:.5s ease-in-out infinite alternate pulse}@keyframes glow-red{0%{box-shadow:0 0 4px #4a2a3a4d}to{box-shadow:0 0 12px #4a2a3ab3}}@keyframes glow-yellow{0%{box-shadow:0 0 4px #2a45454d}to{box-shadow:0 0 12px #2a4545b3}}@keyframes pulse{50%{opacity:.5}}.player-chip{background:#445;border-radius:50%;width:20px;height:20px}.player-chip.red{background:#4a2a3a}.player-chip.yellow{background:#2a4545}.snake-board{background:#556;border:3px solid #445;border-radius:8px;gap:0;display:inline-grid;overflow:hidden}.snake-row{display:contents}.snake-cell{background:#667;border:1px solid #00000014}.snake-cell.snake-head{border-radius:4px;animation:50ms ease-out head-pop}@keyframes head-pop{0%{transform:scale(.85)}to{transform:scale(1)}}.snake-cell.snake-food{background:#334;border-radius:50%;animation:1.2s ease-in-out infinite alternate food-pulse;box-shadow:0 0 3px #0003}@keyframes food-pulse{0%{transform:scale(.85);box-shadow:0 0 3px #0000001a}to{transform:scale(1);box-shadow:0 0 6px #0003}}.snake-cell.snake-dead{opacity:.35;filter:grayscale(.5);transition:opacity .4s ease-out}.snake-wrapper:focus{outline:none}.snake-game-area{justify-content:center;align-items:flex-start;gap:16px;display:flex}@media (max-width:768px){.snake-game-area{flex-direction:column;align-items:center}.snake-game-area .snake-chat{width:100%;max-width:480px}.snake-chat-history{height:150px}}.snake-chat{width:100%;max-width:480px}.snake-game-area .snake-chat{flex-shrink:0;width:260px;max-width:none}.snake-chat-history{background:#334;border-radius:8px 8px 0 0;flex-direction:column;gap:2px;height:300px;padding:8px;display:flex;overflow-y:auto}.snake-chat-msg{font-size:.85rem;line-height:1.3}.snake-chat-input{background:#445;border-radius:0 0 8px 8px;gap:0;display:flex;overflow:hidden}.snake-chat-input input{color:inherit;background:0 0;border:none;outline:none;flex:1;padding:6px 10px;font-size:.85rem}.snake-chat-input button{color:inherit;cursor:pointer;background:#556;border:none;padding:6px 14px;font-size:.85rem}.snake-chat-input button:hover{background:#667}.c4-game-area{justify-content:center;align-items:flex-start;gap:16px;display:flex}@media (max-width:768px){.c4-game-area{flex-direction:column;align-items:center}.c4-game-area .c4-chat{width:100%;max-width:480px}.c4-chat-history{height:150px}.board{gap:4px;padding:8px}.column{gap:4px;padding:2px}.cell{width:36px;height:36px}}.c4-chat{width:100%;max-width:480px}.c4-game-area .c4-chat{flex-shrink:0;width:260px;max-width:none}.c4-chat-history{background:#334;border-radius:8px 8px 0 0;flex-direction:column;gap:2px;height:300px;padding:8px;display:flex;overflow-y:auto}.c4-chat-msg{font-size:.85rem;line-height:1.3}.c4-chat-input{background:#445;border-radius:0 0 8px 8px;gap:0;display:flex;overflow:hidden}.c4-chat-input input{color:inherit;background:0 0;border:none;outline:none;flex:1;padding:6px 10px;font-size:.85rem}.c4-chat-input button{color:inherit;cursor:pointer;background:#556;border:none;padding:6px 14px;font-size:.85rem}.c4-chat-input button:hover{background:#667}@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-font-weight{syntax:"*";inherits:false}@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} \ No newline at end of file diff --git a/assets/js/datastar.js.map b/assets/js/datastar.js.map new file mode 100644 index 0000000..09386be --- /dev/null +++ b/assets/js/datastar.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../library/src/engine/consts.ts", "../library/src/utils/polyfills.ts", "../library/src/utils/paths.ts", "../library/src/engine/signals.ts", "../library/src/utils/dom.ts", "../library/src/utils/text.ts", "../library/src/engine/engine.ts", "../library/src/plugins/actions/peek.ts", "../library/src/plugins/actions/setAll.ts", "../library/src/plugins/actions/toggleAll.ts", "../library/src/plugins/actions/fetch.ts", "../library/src/plugins/attributes/attr.ts", "../library/src/plugins/attributes/bind.ts", "../library/src/plugins/attributes/class.ts", "../library/src/plugins/attributes/computed.ts", "../library/src/plugins/attributes/effect.ts", "../library/src/plugins/attributes/indicator.ts", "../library/src/utils/tags.ts", "../library/src/utils/timing.ts", "../library/src/utils/view-transitions.ts", "../library/src/plugins/attributes/init.ts", "../library/src/plugins/attributes/jsonSignals.ts", "../library/src/plugins/attributes/on.ts", "../library/src/utils/math.ts", "../library/src/plugins/attributes/onIntersect.ts", "../library/src/plugins/attributes/onInterval.ts", "../library/src/plugins/attributes/onSignalPatch.ts", "../library/src/plugins/attributes/ref.ts", "../library/src/plugins/attributes/show.ts", "../library/src/plugins/attributes/signals.ts", "../library/src/plugins/attributes/style.ts", "../library/src/plugins/attributes/text.ts", "../library/src/plugins/watchers/patchElements.ts", "../library/src/plugins/watchers/patchSignals.ts"], + "sourcesContent": ["const lol = /\uD83D\uDD95JS_DS\uD83D\uDE80/.source\nexport const DSP = lol.slice(0, 5)\nexport const DSS = lol.slice(4)\nexport const DATASTAR_FETCH_EVENT = 'datastar-fetch'\nexport const DATASTAR_SIGNAL_PATCH_EVENT = 'datastar-signal-patch'\n", "export const hasOwn: (obj: object, prop: PropertyKey) => boolean =\n // @ts-expect-error\n Object.hasOwn ?? Object.prototype.hasOwnProperty.call\n", "import type { Paths } from '@engine/types'\nimport { hasOwn } from '@utils/polyfills'\n\nexport const isPojo = (obj: any): obj is Record =>\n obj !== null &&\n typeof obj === 'object' &&\n (Object.getPrototypeOf(obj) === Object.prototype ||\n Object.getPrototypeOf(obj) === null)\n\nexport const isEmpty = (obj: Record): boolean => {\n for (const prop in obj) {\n if (hasOwn(obj, prop)) {\n return false\n }\n }\n return true\n}\n\nexport const updateLeaves = (\n obj: Record,\n fn: (oldValue: any) => any,\n) => {\n for (const key in obj) {\n const val = obj[key]\n if (isPojo(val) || Array.isArray(val)) {\n updateLeaves(val, fn)\n } else {\n obj[key] = fn(val)\n }\n }\n}\n\nexport const pathToObj = (paths: Paths): Record => {\n const result: Record = {}\n for (const [path, value] of paths) {\n const keys = path.split('.')\n const lastKey = keys.pop()!\n const obj = keys.reduce((acc, key) => (acc[key] ??= {}), result)\n obj[lastKey] = value\n }\n return result\n}\n", "import { DATASTAR_SIGNAL_PATCH_EVENT } from '@engine/consts'\nimport type {\n Computed,\n Effect,\n JSONPatch,\n MergePatchArgs,\n Paths,\n Signal,\n SignalFilterOptions,\n} from '@engine/types'\nimport { isPojo, pathToObj } from '@utils/paths'\nimport { hasOwn } from '@utils/polyfills'\n\ninterface ReactiveNode {\n deps_?: Link\n depsTail_?: Link\n subs_?: Link\n subsTail_?: Link\n flags_: ReactiveFlags\n}\n\ninterface Link {\n version_: number\n dep_: ReactiveNode\n sub_: ReactiveNode\n prevSub_?: Link\n nextSub_?: Link\n prevDep_?: Link\n nextDep_?: Link\n}\n\ninterface Stack {\n value_: T\n prev_?: Stack\n}\n\nenum ReactiveFlags {\n None = 0,\n Mutable = 1 << 0,\n Watching = 1 << 1,\n RecursedCheck = 1 << 2,\n Recursed = 1 << 3,\n Dirty = 1 << 4,\n Pending = 1 << 5,\n}\n\nenum EffectFlags {\n Queued = 1 << 6,\n}\n\ninterface AlienEffect extends ReactiveNode {\n fn_(): void\n}\n\ninterface AlienComputed extends ReactiveNode {\n value_?: T\n getter(previousValue?: T): T\n}\n\ninterface AlienSignal extends ReactiveNode {\n previousValue: T\n value_: T\n}\n\nconst currentPatch: Paths = []\nconst queuedEffects: (AlienEffect | undefined)[] = []\nlet batchDepth = 0\nlet notifyIndex = 0\nlet queuedEffectsLength = 0\nlet prevSub: ReactiveNode | undefined\nlet activeSub: ReactiveNode | undefined\nlet version = 0\n\nexport const beginBatch = (): void => {\n batchDepth++\n}\n\nexport const endBatch = (): void => {\n if (!--batchDepth) {\n flush()\n dispatch()\n }\n}\n\nexport const startPeeking = (sub?: ReactiveNode): void => {\n prevSub = activeSub\n activeSub = sub\n}\n\nexport const stopPeeking = (): void => {\n activeSub = prevSub\n prevSub = undefined\n}\n\nexport const signal = (initialValue?: T): Signal => {\n return signalOper.bind(0, {\n previousValue: initialValue,\n value_: initialValue,\n flags_: 1 satisfies ReactiveFlags.Mutable,\n }) as Signal\n}\n\nconst computedSymbol = Symbol('computed')\nexport const computed = (getter: (previousValue?: T) => T): Computed => {\n const c = computedOper.bind(0, {\n flags_: 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty,\n getter,\n }) as Computed\n // @ts-expect-error\n c[computedSymbol] = 1\n return c\n}\n\nexport const effect = (fn: () => void): Effect => {\n const e: AlienEffect = {\n fn_: fn,\n flags_: 2 satisfies ReactiveFlags.Watching,\n }\n if (activeSub) {\n link(e, activeSub)\n }\n startPeeking(e)\n beginBatch()\n try {\n e.fn_()\n } finally {\n endBatch()\n stopPeeking()\n }\n return effectOper.bind(0, e)\n}\n\nconst flush = () => {\n while (notifyIndex < queuedEffectsLength) {\n const effect = queuedEffects[notifyIndex]!\n queuedEffects[notifyIndex++] = undefined\n run(effect, (effect.flags_ &= ~EffectFlags.Queued))\n }\n notifyIndex = 0\n queuedEffectsLength = 0\n}\n\nconst update = (signal: AlienSignal | AlienComputed): boolean => {\n if ('getter' in signal) {\n return updateComputed(signal)\n }\n return updateSignal(signal, signal.value_)\n}\n\nconst updateComputed = (c: AlienComputed): boolean => {\n startPeeking(c)\n startTracking(c)\n try {\n const oldValue = c.value_\n return oldValue !== (c.value_ = c.getter(oldValue))\n } finally {\n stopPeeking()\n endTracking(c)\n }\n}\n\nconst updateSignal = (s: AlienSignal, value: T): boolean => {\n s.flags_ = 1 satisfies ReactiveFlags.Mutable\n return s.previousValue !== (s.previousValue = value)\n}\n\nconst notify = (e: AlienEffect): void => {\n const flags = e.flags_\n if (!(flags & EffectFlags.Queued)) {\n e.flags_ = flags | EffectFlags.Queued\n const subs = e.subs_\n if (subs) {\n notify(subs.sub_ as AlienEffect)\n } else {\n queuedEffects[queuedEffectsLength++] = e\n }\n }\n}\n\nconst run = (e: AlienEffect, flags: ReactiveFlags): void => {\n if (\n flags & (16 satisfies ReactiveFlags.Dirty) ||\n (flags & (32 satisfies ReactiveFlags.Pending) && checkDirty(e.deps_!, e))\n ) {\n startPeeking(e)\n startTracking(e)\n beginBatch()\n try {\n e.fn_()\n } finally {\n endBatch()\n stopPeeking()\n endTracking(e)\n }\n return\n }\n if (flags & (32 satisfies ReactiveFlags.Pending)) {\n e.flags_ = flags & ~(32 satisfies ReactiveFlags.Pending)\n }\n let link = e.deps_\n while (link) {\n const dep = link.dep_\n const depFlags = dep.flags_\n if (depFlags & EffectFlags.Queued) {\n run(dep as AlienEffect, (dep.flags_ = depFlags & ~EffectFlags.Queued))\n }\n link = link.nextDep_\n }\n}\n\nconst signalOper = (s: AlienSignal, ...value: [T]): T | boolean => {\n if (value.length) {\n if (s.value_ !== (s.value_ = value[0])) {\n s.flags_ = 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty\n const subs = s.subs_\n if (subs) {\n propagate(subs)\n if (!batchDepth) {\n flush()\n }\n }\n return true\n }\n return false\n }\n const currentValue = s.value_\n if (s.flags_ & (16 satisfies ReactiveFlags.Dirty)) {\n if (updateSignal(s, currentValue)) {\n const subs_ = s.subs_\n if (subs_) {\n shallowPropagate(subs_)\n }\n }\n }\n if (activeSub) {\n link(s, activeSub)\n }\n return currentValue\n}\n\nconst computedOper = (c: AlienComputed): T => {\n const flags = c.flags_\n if (\n flags & (16 satisfies ReactiveFlags.Dirty) ||\n (flags & (32 satisfies ReactiveFlags.Pending) && checkDirty(c.deps_!, c))\n ) {\n if (updateComputed(c)) {\n const subs = c.subs_\n if (subs) {\n shallowPropagate(subs)\n }\n }\n } else if (flags & (32 satisfies ReactiveFlags.Pending)) {\n c.flags_ = flags & ~(32 satisfies ReactiveFlags.Pending)\n }\n if (activeSub) {\n link(c, activeSub)\n }\n return c.value_!\n}\n\nconst effectOper = (e: AlienEffect): void => {\n let dep = e.deps_\n while (dep) {\n dep = unlink(dep, e)\n }\n const sub = e.subs_\n if (sub) {\n unlink(sub)\n }\n e.flags_ = 0 satisfies ReactiveFlags.None\n}\n\nconst link = (dep: ReactiveNode, sub: ReactiveNode): void => {\n const prevDep = sub.depsTail_\n if (prevDep && prevDep.dep_ === dep) {\n return\n }\n const nextDep = prevDep ? prevDep.nextDep_ : sub.deps_\n if (nextDep && nextDep.dep_ === dep) {\n nextDep.version_ = version\n sub.depsTail_ = nextDep\n return\n }\n const prevSub = dep.subsTail_\n if (prevSub && prevSub.version_ === version && prevSub.sub_ === sub) {\n return\n }\n const newLink =\n (sub.depsTail_ =\n dep.subsTail_ =\n {\n version_: version,\n dep_: dep,\n sub_: sub,\n prevDep_: prevDep,\n nextDep_: nextDep,\n prevSub_: prevSub,\n })\n if (nextDep) {\n nextDep.prevDep_ = newLink\n }\n if (prevDep) {\n prevDep.nextDep_ = newLink\n } else {\n sub.deps_ = newLink\n }\n if (prevSub) {\n prevSub.nextSub_ = newLink\n } else {\n dep.subs_ = newLink\n }\n}\n\nconst unlink = (link: Link, sub = link.sub_): Link | undefined => {\n const dep_ = link.dep_\n const prevDep_ = link.prevDep_\n const nextDep_ = link.nextDep_\n const nextSub_ = link.nextSub_\n const prevSub_ = link.prevSub_\n if (nextDep_) {\n nextDep_.prevDep_ = prevDep_\n } else {\n sub.depsTail_ = prevDep_\n }\n if (prevDep_) {\n prevDep_.nextDep_ = nextDep_\n } else {\n sub.deps_ = nextDep_\n }\n if (nextSub_) {\n nextSub_.prevSub_ = prevSub_\n } else {\n dep_.subsTail_ = prevSub_\n }\n if (prevSub_) {\n prevSub_.nextSub_ = nextSub_\n } else if (!(dep_.subs_ = nextSub_)) {\n if ('getter' in dep_) {\n let toRemove = dep_.deps_\n if (toRemove) {\n dep_.flags_ = 17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty\n do {\n toRemove = unlink(toRemove, dep_)\n } while (toRemove)\n }\n } else if (!('previousValue' in dep_)) {\n effectOper(dep_ as AlienEffect)\n }\n }\n return nextDep_\n}\n\nconst propagate = (link: Link): void => {\n let next = link.nextSub_\n let stack: Stack | undefined\n\n top: while (true) {\n const sub = link.sub_\n\n let flags = sub.flags_\n\n if (\n !(\n flags &\n (60 as\n | ReactiveFlags.RecursedCheck\n | ReactiveFlags.Recursed\n | ReactiveFlags.Dirty\n | ReactiveFlags.Pending)\n )\n ) {\n sub.flags_ = flags | (32 satisfies ReactiveFlags.Pending)\n } else if (\n !(flags & (12 as ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))\n ) {\n flags = 0 satisfies ReactiveFlags.None\n } else if (!(flags & (4 satisfies ReactiveFlags.RecursedCheck))) {\n sub.flags_ =\n (flags & ~(8 satisfies ReactiveFlags.Recursed)) |\n (32 satisfies ReactiveFlags.Pending)\n } else if (\n !(flags & (48 as ReactiveFlags.Dirty | ReactiveFlags.Pending)) &&\n isValidLink(link, sub)\n ) {\n sub.flags_ =\n flags | (40 as ReactiveFlags.Recursed | ReactiveFlags.Pending)\n flags &= 1 satisfies ReactiveFlags.Mutable\n } else {\n flags = 0 satisfies ReactiveFlags.None\n }\n\n if (flags & (2 satisfies ReactiveFlags.Watching)) {\n notify(sub as AlienEffect)\n }\n\n if (flags & (1 satisfies ReactiveFlags.Mutable)) {\n const subSubs = sub.subs_\n if (subSubs) {\n const nextSub = (link = subSubs).nextSub_\n if (nextSub) {\n stack = { value_: next, prev_: stack }\n next = nextSub\n }\n continue\n }\n }\n\n if ((link = next!)) {\n next = link.nextSub_\n continue\n }\n\n while (stack) {\n link = stack.value_!\n stack = stack.prev_\n if (link) {\n next = link.nextSub_\n continue top\n }\n }\n\n break\n }\n}\n\nconst startTracking = (sub: ReactiveNode): void => {\n version++\n sub.depsTail_ = undefined\n sub.flags_ =\n (sub.flags_ &\n ~(56 as\n | ReactiveFlags.Recursed\n | ReactiveFlags.Dirty\n | ReactiveFlags.Pending)) |\n (4 satisfies ReactiveFlags.RecursedCheck)\n}\n\nconst endTracking = (sub: ReactiveNode): void => {\n const depsTail_ = sub.depsTail_\n let toRemove = depsTail_ ? depsTail_.nextDep_ : sub.deps_\n while (toRemove) {\n toRemove = unlink(toRemove, sub)\n }\n sub.flags_ &= ~(4 satisfies ReactiveFlags.RecursedCheck)\n}\n\nconst checkDirty = (link: Link, sub: ReactiveNode): boolean => {\n let stack: Stack | undefined\n let checkDepth = 0\n let dirty = false\n\n top: while (true) {\n const dep = link.dep_\n const flags = dep.flags_\n\n if (sub.flags_ & (16 satisfies ReactiveFlags.Dirty)) {\n dirty = true\n } else if (\n (flags & (17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===\n (17 as ReactiveFlags.Mutable | ReactiveFlags.Dirty)\n ) {\n if (update(dep as AlienSignal | AlienComputed)) {\n const subs = dep.subs_!\n if (subs.nextSub_) {\n shallowPropagate(subs)\n }\n dirty = true\n }\n } else if (\n (flags & (33 as ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===\n (33 as ReactiveFlags.Mutable | ReactiveFlags.Pending)\n ) {\n if (link.nextSub_ || link.prevSub_) {\n stack = { value_: link, prev_: stack }\n }\n link = dep.deps_!\n sub = dep\n ++checkDepth\n continue\n }\n\n if (!dirty) {\n const nextDep = link.nextDep_\n if (nextDep) {\n link = nextDep\n continue\n }\n }\n\n while (checkDepth--) {\n const firstSub = sub.subs_!\n const hasMultipleSubs = firstSub.nextSub_\n if (hasMultipleSubs) {\n link = stack!.value_\n stack = stack!.prev_\n } else {\n link = firstSub\n }\n if (dirty) {\n if (update(sub as AlienSignal | AlienComputed)) {\n if (hasMultipleSubs) {\n shallowPropagate(firstSub)\n }\n sub = link.sub_\n continue\n }\n dirty = false\n } else {\n sub.flags_ &= ~(32 satisfies ReactiveFlags.Pending)\n }\n sub = link.sub_\n if (link.nextDep_) {\n link = link.nextDep_\n continue top\n }\n }\n\n return dirty\n }\n}\n\nconst shallowPropagate = (link: Link): void => {\n do {\n const sub = link.sub_\n const flags = sub.flags_\n if (\n (flags & (48 as ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===\n (32 satisfies ReactiveFlags.Pending)\n ) {\n sub.flags_ = flags | (16 satisfies ReactiveFlags.Dirty)\n if (flags & (2 satisfies ReactiveFlags.Watching)) {\n notify(sub as AlienEffect)\n }\n }\n } while ((link = link.nextSub_!))\n}\n\nconst isValidLink = (checkLink: Link, sub: ReactiveNode): boolean => {\n let link = sub.depsTail_\n while (link) {\n if (link === checkLink) {\n return true\n }\n link = link.prevDep_\n }\n return false\n}\n\nexport const getPath = (path: string): T | undefined => {\n let result = root\n const split = path.split('.')\n for (const path of split) {\n if (result == null || !hasOwn(result, path)) {\n return\n }\n result = result[path]\n }\n return result as T\n}\n\nconst deep = (value: any, prefix = ''): any => {\n const isArr = Array.isArray(value)\n if (isArr || isPojo(value)) {\n const deepObj = (isArr ? [] : {}) as Record>\n for (const key in value) {\n deepObj[key] = signal(\n deep((value as Record>)[key], `${prefix + key}.`),\n )\n }\n const keys = signal(0)\n return new Proxy(deepObj, {\n get(_, prop: string) {\n // JSON.stringify calls `.toJSON()` directly instead of checking if it exists on the object\n // so we have to check if `toJSON` is being called and prevent a signal from automatically\n // being made so JSON.stringify can fallback to the default stringify\n if (!(prop === 'toJSON' && !hasOwn(deepObj, prop))) {\n // special case for when prop is an array function because every array function needs to\n // be reactive to when the keys change\n if (isArr && prop in Array.prototype) {\n keys()\n return deepObj[prop]\n }\n // if prop is a symbol just return the symbol because we don't want to make up that theres\n // an iterator symbol on an object or not\n if (typeof prop === 'symbol') {\n return deepObj[prop]\n }\n // if obj doesnt have prop OR prop is null or undefined then create a signal and default\n // to an empty string\n if (!hasOwn(deepObj, prop) || deepObj[prop]() == null) {\n deepObj[prop] = signal('')\n dispatch(prefix + prop, '')\n keys(keys() + 1)\n }\n return deepObj[prop]()\n }\n },\n set(_, prop: string, newValue) {\n const path = prefix + prop\n // special case for when setting length so we can make a diff patch\n if (isArr && prop === 'length') {\n const diff = (deepObj[prop] as unknown as number) - newValue\n deepObj[prop] = newValue\n // manually make a diff patch for now, shouldnt have to do this when object diffing is\n // implemented. see https://github.com/starfederation/datastar-dev/issues/274\n if (diff > 0) {\n const patch: Record = {}\n for (let i = newValue; i < deepObj[prop]; i++) {\n patch[i] = null\n }\n dispatch(prefix.slice(0, -1), patch)\n keys(keys() + 1)\n }\n } else if (hasOwn(deepObj, prop)) {\n if (newValue == null) {\n delete deepObj[prop]\n // if newValue is a computed set the computed directly instead of wrapping in signal\n } else if (hasOwn(newValue, computedSymbol)) {\n deepObj[prop] = newValue\n dispatch(path, '')\n // if prop changed after setting it then dispatch\n } else {\n const currentValue = deepObj[prop]()\n const pathStr = `${path}.`\n if (isPojo(currentValue) && isPojo(newValue)) {\n for (const key in currentValue) {\n if (!hasOwn(newValue, key)) {\n delete currentValue[key]\n dispatch(pathStr + key, null)\n }\n }\n for (const key in newValue) {\n const nextVal = newValue[key]\n if (currentValue[key] !== nextVal) {\n currentValue[key] = nextVal\n }\n }\n } else if (deepObj[prop](deep(newValue, pathStr))) {\n dispatch(path, newValue)\n }\n }\n // if newValue is null or undefined then noop\n } else if (newValue != null) {\n // if newValue is a computed set the computed directly instead of wrapping in signal\n if (hasOwn(newValue, computedSymbol)) {\n deepObj[prop] = newValue\n dispatch(path, '')\n } else {\n deepObj[prop] = signal(deep(newValue, `${path}.`))\n dispatch(path, newValue)\n }\n keys(keys() + 1)\n }\n\n return true\n },\n deleteProperty(_, prop: string) {\n delete deepObj[prop]\n keys(keys() + 1)\n return true\n },\n ownKeys() {\n keys()\n return Reflect.ownKeys(deepObj)\n },\n has(_, prop) {\n keys()\n return prop in deepObj\n },\n })\n }\n return value\n}\n\nconst dispatch = (path?: string, value?: any) => {\n if (path !== undefined && value !== undefined) {\n currentPatch.push([path, value])\n }\n if (!batchDepth && currentPatch.length) {\n const detail = pathToObj(currentPatch)\n currentPatch.length = 0\n document.dispatchEvent(\n new CustomEvent(DATASTAR_SIGNAL_PATCH_EVENT, {\n detail,\n }),\n )\n }\n}\n\nexport const mergePatch = (\n patch: JSONPatch,\n { ifMissing }: MergePatchArgs = {},\n): void => {\n beginBatch()\n for (const key in patch) {\n if (patch[key] == null) {\n if (!ifMissing) {\n delete root[key]\n }\n } else {\n mergeInner(patch[key], key, root, '', ifMissing)\n }\n }\n endBatch()\n}\n\nexport const mergePaths = (paths: Paths, options?: MergePatchArgs): void =>\n mergePatch(pathToObj(paths), options)\n\nconst mergeInner = (\n patch: any,\n target: string,\n targetParent: Record,\n prefix: string,\n ifMissing: boolean | undefined,\n): void => {\n if (isPojo(patch)) {\n if (\n !(\n hasOwn(targetParent, target) &&\n (isPojo(targetParent[target]) || Array.isArray(targetParent[target]))\n )\n ) {\n targetParent[target] = {}\n }\n\n for (const key in patch) {\n if (patch[key] == null) {\n if (!ifMissing) {\n delete targetParent[target][key]\n }\n } else {\n mergeInner(\n patch[key],\n key,\n targetParent[target],\n `${prefix + target}.`,\n ifMissing,\n )\n }\n }\n } else if (!(ifMissing && hasOwn(targetParent, target))) {\n targetParent[target] = patch\n }\n}\n\nconst toRegExp = (val: string | RegExp): RegExp =>\n typeof val === 'string' ? RegExp(val.replace(/^\\/|\\/$/g, '')) : val\n\n/**\n * Filters the root store based on an include and exclude RegExp\n *\n * @returns The filtered object\n */\nexport const filtered = (\n { include = /.*/, exclude = /(?!)/ }: SignalFilterOptions = {},\n obj: JSONPatch = root,\n): Record => {\n const includeRe = toRegExp(include)\n const excludeRe = toRegExp(exclude)\n const paths: Paths = []\n const stack: [any, string][] = [[obj, '']]\n\n while (stack.length) {\n const [node, prefix] = stack.pop()!\n\n for (const key in node) {\n const path = prefix + key\n if (isPojo(node[key])) {\n stack.push([node[key], `${path}.`])\n } else if (includeRe.test(path) && !excludeRe.test(path)) {\n paths.push([path, getPath(path)])\n }\n }\n }\n\n return pathToObj(paths)\n}\n\nexport const root: Record = deep({})\n", "import type { HTMLOrSVG } from '@engine/types'\n\nexport const isHTMLOrSVG = (el: Node): el is HTMLOrSVG =>\n el instanceof HTMLElement ||\n el instanceof SVGElement ||\n el instanceof MathMLElement\n", "import type { Modifiers } from '@engine/types'\n\nexport const kebab = (str: string): string =>\n str\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/([a-z])([0-9]+)/gi, '$1-$2')\n .replace(/([0-9]+)([a-z])/gi, '$1-$2')\n .replace(/[\\s_]+/g, '-') // Replace whitespace and underscores with hyphens\n .toLowerCase()\n\nexport const camel = (str: string): string =>\n kebab(str).replace(/-./g, (x) => x[1].toUpperCase())\n\nexport const snake = (str: string): string => kebab(str).replace(/-/g, '_')\n\nexport const pascal = (str: string): string =>\n camel(str).replace(/(^.|(?<=\\.).)/g, (x) => x[0].toUpperCase())\n\nexport const title = (str: string): string =>\n str.replace(/\\b\\w/g, (char) => char.toUpperCase())\n\nexport const jsStrToObject = (raw: string) => {\n try {\n return JSON.parse(raw)\n } catch {\n // If JSON parsing fails, try to evaluate as a JavaScript object\n // This is less safe and should be used with caution\n return Function(`return (${raw})`)()\n }\n}\n\nconst caseFns: Record string> = {\n camel: (str) => str.replace(/-[a-z]/g, (x) => x[1].toUpperCase()),\n snake: (str) => str.replace(/-/g, '_'),\n pascal: (str) => str[0].toUpperCase() + caseFns.camel(str.slice(1)),\n}\n\nexport const modifyCasing = (\n str: string,\n mods: Modifiers,\n defaultCase = 'camel',\n): string => {\n for (const c of mods.get('case') || [defaultCase]) {\n str = caseFns[c]?.(str) || str\n }\n return str\n}\n\nexport const aliasify = (name: string) =>\n ALIAS ? `data-${ALIAS}-${name}` : `data-${name}`\n", "import { DATASTAR_FETCH_EVENT, DSP, DSS } from '@engine/consts'\nimport { root } from '@engine/signals'\nimport type {\n ActionContext,\n ActionPlugin,\n AttributeContext,\n AttributePlugin,\n DatastarFetchEvent,\n HTMLOrSVG,\n Modifiers,\n Requirement,\n WatcherPlugin,\n} from '@engine/types'\nimport { isHTMLOrSVG } from '@utils/dom'\nimport { aliasify, snake } from '@utils/text'\n\nconst url = 'https://data-star.dev/errors'\n\nconst error = (\n ctx: Record,\n reason: string,\n metadata: Record = {},\n) => {\n Object.assign(metadata, ctx)\n const e = new Error()\n const r = snake(reason)\n const q = new URLSearchParams({\n metadata: JSON.stringify(metadata),\n }).toString()\n const c = JSON.stringify(metadata, null, 2)\n e.message = `${reason}\\nMore info: ${url}/${r}?${q}\\nContext: ${c}`\n return e\n}\n\nconst actionPlugins: Map = new Map()\nconst attributePlugins: Map = new Map()\nconst watcherPlugins: Map = new Map()\n\nexport const actions: Record<\n string,\n (ctx: ActionContext, ...args: any[]) => any\n> = new Proxy(\n {},\n {\n get: (_, prop: string) => actionPlugins.get(prop)?.apply,\n has: (_, prop: string) => actionPlugins.has(prop),\n ownKeys: () => Reflect.ownKeys(actionPlugins),\n set: () => false,\n deleteProperty: () => false,\n },\n)\n\n// Map of cleanups keyed by element, attribute name, and cleanup name\nconst removals = new Map void>>>()\n\nconst queuedAttributes: AttributePlugin[] = []\nconst queuedAttributeNames = new Set()\nconst observedRoots = new WeakSet()\nexport const attribute = (\n plugin: AttributePlugin,\n): void => {\n queuedAttributes.push(plugin as unknown as AttributePlugin)\n\n if (queuedAttributes.length === 1) {\n setTimeout(() => {\n for (const attribute of queuedAttributes) {\n queuedAttributeNames.add(attribute.name)\n attributePlugins.set(attribute.name, attribute)\n }\n queuedAttributes.length = 0\n apply()\n queuedAttributeNames.clear()\n })\n }\n}\n\nexport const action = (plugin: ActionPlugin): void => {\n actionPlugins.set(plugin.name, plugin)\n}\n\ndocument.addEventListener(DATASTAR_FETCH_EVENT, ((\n evt: CustomEvent,\n) => {\n const plugin = watcherPlugins.get(evt.detail.type)\n if (plugin) {\n plugin.apply(\n {\n error: error.bind(0, {\n plugin: { type: 'watcher', name: plugin.name },\n element: {\n id: (evt.target as Element).id,\n tag: (evt.target as Element).tagName,\n },\n }),\n },\n evt.detail.argsRaw,\n )\n }\n}) as EventListener)\n\nexport const watcher = (plugin: WatcherPlugin): void => {\n watcherPlugins.set(plugin.name, plugin)\n}\n\nconst cleanupEls = (els: Iterable): void => {\n for (const el of els) {\n const elCleanups = removals.get(el)\n if (elCleanups && removals.delete(el)) {\n for (const attrCleanups of elCleanups.values()) {\n for (const cleanup of attrCleanups.values()) {\n cleanup()\n }\n }\n }\n }\n}\n\nconst aliasedIgnore = aliasify('ignore')\nconst aliasedIgnoreAttr = `[${aliasedIgnore}]`\nconst shouldIgnore = (el: HTMLOrSVG) =>\n el.hasAttribute(`${aliasedIgnore}__self`) || !!el.closest(aliasedIgnoreAttr)\n\nconst applyEls = (els: Iterable, onlyNew?: boolean): void => {\n for (const el of els) {\n if (!shouldIgnore(el)) {\n for (const key in el.dataset) {\n applyAttributePlugin(\n el,\n key.replace(/[A-Z]/g, '-$&').toLowerCase(),\n el.dataset[key]!,\n onlyNew,\n )\n }\n }\n }\n}\n\nconst observe = (mutations: MutationRecord[]) => {\n for (const {\n target,\n type,\n attributeName,\n addedNodes,\n removedNodes,\n } of mutations) {\n if (type === 'childList') {\n for (const node of removedNodes) {\n if (isHTMLOrSVG(node)) {\n cleanupEls([node])\n cleanupEls(node.querySelectorAll('*'))\n }\n }\n\n for (const node of addedNodes) {\n if (isHTMLOrSVG(node)) {\n applyEls([node])\n applyEls(node.querySelectorAll('*'))\n }\n }\n } else if (\n type === 'attributes' &&\n attributeName!.startsWith('data-') &&\n isHTMLOrSVG(target) &&\n !shouldIgnore(target)\n ) {\n // skip over 'data-'\n const key = attributeName!.slice(5)\n const value = target.getAttribute(attributeName!)\n if (value === null) {\n const elCleanups = removals.get(target)\n if (elCleanups) {\n const attrCleanups = elCleanups.get(key)\n if (attrCleanups) {\n for (const cleanup of attrCleanups.values()) {\n cleanup()\n }\n elCleanups.delete(key)\n }\n }\n } else {\n applyAttributePlugin(target, key, value)\n }\n }\n }\n}\n\n// TODO: mutation observer per root so applying to web component doesnt overwrite main observer\nconst mutationObserver = new MutationObserver(observe)\n\nexport const parseAttributeKey = (\n rawKey: string,\n): {\n pluginName: string\n key: string | undefined\n mods: Modifiers\n} => {\n const [namePart, ...rawModifiers] = rawKey.split('__')\n const [pluginName, key] = namePart.split(/:(.+)/)\n const mods: Modifiers = new Map()\n\n for (const rawMod of rawModifiers) {\n const [label, ...mod] = rawMod.split('.')\n mods.set(label, new Set(mod))\n }\n\n return { pluginName, key, mods }\n}\n\nexport const isDocumentObserverActive = () =>\n observedRoots.has(document.documentElement)\n\nexport const apply = (\n root: HTMLOrSVG | ShadowRoot = document.documentElement,\n observeRoot = true,\n): void => {\n if (isHTMLOrSVG(root)) {\n applyEls([root], true)\n }\n applyEls(root.querySelectorAll('*'), true)\n\n if (observeRoot) {\n mutationObserver.observe(root, {\n subtree: true,\n childList: true,\n attributes: true,\n })\n observedRoots.add(root)\n }\n}\n\nconst applyAttributePlugin = (\n el: HTMLOrSVG,\n attrKey: string,\n value: string,\n onlyNew?: boolean,\n): void => {\n if (!ALIAS || attrKey.startsWith(`${ALIAS}-`)) {\n const rawKey = ALIAS ? attrKey.slice(ALIAS.length + 1) : attrKey\n const { pluginName, key, mods } = parseAttributeKey(rawKey)\n const plugin = attributePlugins.get(pluginName)\n if ((!onlyNew || queuedAttributeNames.has(pluginName)) && plugin) {\n const ctx = {\n el,\n rawKey,\n mods,\n error: error.bind(0, {\n plugin: { type: 'attribute', name: plugin.name },\n element: { id: el.id, tag: el.tagName },\n expression: { rawKey, key, value },\n }),\n key,\n value,\n loadedPluginNames: {\n actions: new Set(actionPlugins.keys()),\n attributes: new Set(attributePlugins.keys()),\n },\n rx: undefined,\n } as AttributeContext\n\n const keyReq =\n (plugin.requirement &&\n (typeof plugin.requirement === 'string'\n ? plugin.requirement\n : plugin.requirement.key)) ||\n 'allowed'\n const valueReq =\n (plugin.requirement &&\n (typeof plugin.requirement === 'string'\n ? plugin.requirement\n : plugin.requirement.value)) ||\n 'allowed'\n\n const keyProvided = key !== undefined && key !== null && key !== ''\n const valueProvided =\n value !== undefined && value !== null && value !== ''\n\n if (keyProvided) {\n if (keyReq === 'denied') {\n throw ctx.error('KeyNotAllowed')\n }\n } else if (keyReq === 'must') {\n throw ctx.error('KeyRequired')\n }\n\n if (valueProvided) {\n if (valueReq === 'denied') {\n throw ctx.error('ValueNotAllowed')\n }\n } else if (valueReq === 'must') {\n throw ctx.error('ValueRequired')\n }\n\n if (keyReq === 'exclusive' || valueReq === 'exclusive') {\n if (keyProvided && valueProvided) {\n throw ctx.error('KeyAndValueProvided')\n }\n if (!keyProvided && !valueProvided) {\n throw ctx.error('KeyOrValueRequired')\n }\n }\n\n const cleanups = new Map void>()\n if (valueProvided) {\n let cachedRx: GenRxFn\n ctx.rx = (...args: any[]) => {\n if (!cachedRx) {\n cachedRx = genRx(value, {\n returnsValue: plugin.returnsValue,\n argNames: plugin.argNames,\n cleanups,\n })\n }\n return cachedRx(el, ...args)\n }\n }\n\n const cleanup = plugin.apply(ctx)\n if (cleanup) {\n cleanups.set('attribute', cleanup)\n }\n\n let elCleanups = removals.get(el)\n if (elCleanups) {\n const attrCleanups = elCleanups.get(rawKey)\n if (attrCleanups) {\n for (const oldCleanup of attrCleanups.values()) {\n oldCleanup()\n }\n }\n } else {\n elCleanups = new Map()\n removals.set(el, elCleanups)\n }\n elCleanups.set(rawKey, cleanups)\n }\n }\n}\n\ntype GenRxOptions = {\n returnsValue?: boolean\n argNames?: string[]\n cleanups?: Map void>\n}\n\ntype GenRxFn = (el: HTMLOrSVG, ...args: any[]) => T\n\nconst genRx = (\n value: string,\n {\n returnsValue = false,\n argNames = [],\n cleanups = new Map(),\n }: GenRxOptions = {},\n): GenRxFn => {\n let expr = ''\n if (returnsValue) {\n // This regex allows Datastar expressions to support nested\n // regex and strings that contain ; without breaking.\n //\n // Each of these regex defines a block type we want to match\n // (importantly we ignore the content within these blocks):\n //\n // regex \\/(\\\\\\/|[^\\/])*\\/\n // double quotes \"(\\\\\"|[^\\\"])*\"\n // single quotes '(\\\\'|[^'])*'\n // ticks `(\\\\`|[^`])*`\n // iife \\(\\s*((function)\\s*\\(\\s*\\)|(\\(\\s*\\))\\s*=>)\\s*(?:\\{[\\s\\S]*?\\}|[^;)\\{]*)\\s*\\)\\s*\\(\\s*\\)\n //\n // The iife support is (intentionally) limited. It only supports\n // function and arrow syntax with no arguments, and no nested IIFEs.\n //\n // We also want to match the non delimiter part of statements\n // note we only support ; statement delimiters:\n //\n // [^;]\n //\n const statementRe =\n /(\\/(\\\\\\/|[^/])*\\/|\"(\\\\\"|[^\"])*\"|'(\\\\'|[^'])*'|`(\\\\`|[^`])*`|\\(\\s*((function)\\s*\\(\\s*\\)|(\\(\\s*\\))\\s*=>)\\s*(?:\\{[\\s\\S]*?\\}|[^;){]*)\\s*\\)\\s*\\(\\s*\\)|[^;])+/gm\n const statements = value.trim().match(statementRe)\n if (statements) {\n const lastIdx = statements.length - 1\n const last = statements[lastIdx].trim()\n if (!last.startsWith('return')) {\n statements[lastIdx] = `return (${last});`\n }\n expr = statements.join(';\\n')\n }\n } else {\n expr = value.trim()\n }\n\n // Ignore any escaped values\n const escaped = new Map()\n const escapeRe = RegExp(`(?:${DSP})(.*?)(?:${DSS})`, 'gm')\n let counter = 0\n for (const match of expr.matchAll(escapeRe)) {\n const k = match[1]\n const v = `__escaped${counter++}`\n escaped.set(v, k)\n expr = expr.replace(DSP + k + DSS, v)\n }\n\n // Replace signal references with bracket notation\n // Examples:\n // $count -> $['count']\n // $count-- -> $['count']--\n // $foo.bar -> $['foo']['bar']\n // $foo-bar -> $['foo-bar']\n // $foo.bar-baz -> $['foo']['bar-baz']\n // $foo-$bar -> $['foo']-$['bar']\n // $arr[$index] -> $['arr'][$['index']]\n // $['foo'] -> $['foo']\n // $foo[obj.bar] -> $['foo'][obj.bar]\n // $foo['bar.baz'] -> $['foo']['bar.baz']\n // $123 -> $['123']\n // $foo.0.name -> $['foo']['0']['name']\n\n expr = expr\n // $['x'] -> $x (normalize existing bracket notation)\n .replace(/\\$\\['([a-zA-Z_$\\d][\\w$]*)'\\]/g, '$$$1')\n // $x -> $['x'] (including dots and hyphens)\n .replace(/\\$([a-zA-Z_\\d]\\w*(?:[.-]\\w+)*)/g, (_, signalName) =>\n signalName\n .split('.')\n .reduce((acc: string, part: string) => `${acc}['${part}']`, '$'),\n )\n\n expr = expr.replaceAll(/@([A-Za-z_$][\\w$]*)\\(/g, '__action(\"$1\",evt,')\n\n // Replace any escaped values\n for (const [k, v] of escaped) {\n expr = expr.replace(k, v)\n }\n\n try {\n const fn = Function('el', '$', '__action', 'evt', ...argNames, expr)\n return (el: HTMLOrSVG, ...args: any[]) => {\n const action = (name: string, evt: Event | undefined, ...args: any[]) => {\n const err = error.bind(0, {\n plugin: { type: 'action', name },\n element: { id: el.id, tag: el.tagName },\n expression: {\n fnContent: expr,\n value,\n },\n })\n const fn = actions[name]\n if (fn) {\n return fn(\n {\n el,\n evt,\n error: err,\n cleanups,\n },\n ...args,\n )\n }\n throw err('UndefinedAction')\n }\n try {\n return fn(el, root, action, undefined, ...args)\n } catch (e: any) {\n console.error(e)\n throw error(\n {\n element: { id: el.id, tag: el.tagName },\n expression: {\n fnContent: expr,\n value,\n },\n error: e.message,\n },\n 'ExecuteExpression',\n )\n }\n }\n } catch (e: any) {\n console.error(e)\n throw error(\n {\n expression: {\n fnContent: expr,\n value,\n },\n error: e.message,\n },\n 'GenerateExpression',\n )\n }\n}\n", "// Icon: ion:eye\n// Slug: Access signals without subscribing to changes.\n// Description: Allows accessing signals without subscribing to their changes in expressions.\n\nimport { action } from '@engine'\nimport { startPeeking, stopPeeking } from '@engine/signals'\n\naction({\n name: 'peek',\n apply(_, fn: () => any) {\n startPeeking()\n try {\n return fn()\n } finally {\n stopPeeking()\n }\n },\n})\n", "// Icon: ion:checkmark-round\n// Slug: Sets the value of all matching signals.\n// Description: Sets the value of all matching signals (or all signals if no filter is used) to the expression provided in the first argument.\n\nimport { action } from '@engine'\nimport {\n filtered,\n mergePatch,\n startPeeking,\n stopPeeking,\n} from '@engine/signals'\nimport type { SignalFilterOptions } from '@engine/types'\nimport { updateLeaves } from '@utils/paths'\n\naction({\n name: 'setAll',\n apply(_, value: any, filter: SignalFilterOptions) {\n // peek because in an effect you would be subscribing to signals and then setting them which\n // would cause an infinite loop and why would you want to infinite loop on purpose\n startPeeking()\n const masked = filtered(filter)\n updateLeaves(masked, () => value)\n mergePatch(masked)\n stopPeeking()\n },\n})\n", "// Icon: material-symbols:toggle-off\n// Slug: Toggles the value of all matching signals.\n// Description: Toggles the boolean value of all matching signals (or all signals if no filter is used).\n\nimport { action } from '@engine'\nimport {\n filtered,\n mergePatch,\n startPeeking,\n stopPeeking,\n} from '@engine/signals'\nimport type { SignalFilterOptions } from '@engine/types'\nimport { updateLeaves } from '@utils/paths'\n\naction({\n name: 'toggleAll',\n apply(_, filter: SignalFilterOptions) {\n // peek because in an effect you would be subscribing to signals and then setting them which\n // would cause an infinite loop and why would you want to infinite loop on purpose\n startPeeking()\n const masked = filtered(filter)\n updateLeaves(masked, (oldValue: any) => !oldValue)\n mergePatch(masked)\n stopPeeking()\n },\n})\n", "// Icon: ion:eye\n// Slug: Access signals without subscribing to changes.\n// Description: Allows accessing signals without subscribing to their changes in expressions.\n\nimport { action } from '@engine'\nimport { DATASTAR_FETCH_EVENT } from '@engine/consts'\nimport { filtered, startPeeking, stopPeeking } from '@engine/signals'\nimport type {\n DatastarFetchEvent,\n HTMLOrSVG,\n SignalFilterOptions,\n} from '@engine/types'\nimport { kebab } from '@utils/text'\n\nconst createHttpMethod = (\n name: string,\n method: string,\n openWhenHiddenDefault: boolean = true,\n): void =>\n action({\n name,\n apply: async (\n { el, evt, error, cleanups },\n url: string,\n {\n selector,\n headers: userHeaders,\n contentType = 'json',\n filterSignals: { include = /.*/, exclude = /(^|\\.)_/ } = {},\n openWhenHidden = openWhenHiddenDefault,\n payload,\n requestCancellation = 'auto',\n retry = 'auto',\n retryInterval = 1_000,\n retryScaler = 2,\n retryMaxWaitMs = 30_000,\n retryMaxCount = 10,\n }: FetchArgs = {},\n ) => {\n const controller =\n requestCancellation instanceof AbortController\n ? requestCancellation\n : new AbortController()\n if (requestCancellation === 'auto') {\n cleanups.get(`@${name}`)?.()\n cleanups.set(`@${name}`, async () => {\n controller.abort()\n // wait one tick for FINISHED to fire\n await Promise.resolve()\n })\n }\n\n let cleanupFn = null\n\n try {\n if (!url?.length) {\n throw error('FetchNoUrlProvided', { action })\n }\n\n const initialHeaders: Record = {\n Accept: 'text/event-stream, text/html, application/json',\n 'Datastar-Request': true,\n }\n if (contentType === 'json') {\n initialHeaders['Content-Type'] = 'application/json'\n }\n const headers = Object.assign({}, initialHeaders, userHeaders)\n\n // We ignore the content-type header if using form data\n // if missing the boundary will be set automatically\n\n const req: FetchEventSourceInit = {\n method,\n headers,\n openWhenHidden,\n retry,\n retryInterval,\n retryScaler,\n retryMaxWaitMs,\n retryMaxCount,\n signal: controller.signal,\n onopen: async (response: Response) => {\n if (response.status >= 400)\n dispatchFetch(ERROR, el, { status: response.status.toString() })\n },\n onmessage: (evt) => {\n if (!evt.event.startsWith('datastar')) return\n const type = evt.event\n const argsRawLines: Record = {}\n\n for (const line of evt.data.split('\\n')) {\n const i = line.indexOf(' ')\n const k = line.slice(0, i)\n const v = line.slice(i + 1)\n ;(argsRawLines[k] ||= []).push(v)\n }\n\n const argsRaw = Object.fromEntries(\n Object.entries(argsRawLines).map(([k, v]) => [k, v.join('\\n')]),\n )\n\n dispatchFetch(type, el, argsRaw)\n },\n onerror: (error) => {\n if (isWrongContent(error)) {\n // don't retry if the content-type is wrong\n throw error('FetchExpectedTextEventStream', { url })\n }\n // do nothing and it will retry\n if (error) {\n console.error(error.message)\n dispatchFetch(RETRYING, el, { message: error.message })\n }\n },\n }\n\n const urlInstance = new URL(url, document.baseURI)\n const queryParams = new URLSearchParams(urlInstance.search)\n\n if (contentType === 'json') {\n startPeeking()\n payload =\n payload !== undefined ? payload : filtered({ include, exclude })\n stopPeeking()\n const body = JSON.stringify(payload)\n if (method === 'GET') {\n queryParams.set('datastar', body)\n } else {\n req.body = body\n }\n } else if (contentType === 'form') {\n const formEl = (\n selector ? document.querySelector(selector) : el.closest('form')\n ) as HTMLFormElement\n if (!formEl) {\n throw error('FetchFormNotFound', { action, selector })\n }\n\n // Validate the form\n if (!formEl.noValidate && !formEl.checkValidity()) {\n formEl.reportValidity()\n return\n }\n\n // Collect the form data\n const formData = new FormData(formEl)\n let submitter = el as HTMLElement | null\n\n if (el === formEl && evt instanceof SubmitEvent) {\n // Get the submitter from the event\n submitter = evt.submitter\n } else {\n // Prevent the form being submitted\n const preventDefault = (evt: Event) => evt.preventDefault()\n formEl.addEventListener('submit', preventDefault)\n cleanupFn = () => {\n formEl.removeEventListener('submit', preventDefault)\n }\n }\n\n // Append the value of the form submitter if it is a button with a name\n if (submitter instanceof HTMLButtonElement) {\n const name = submitter.getAttribute('name')\n if (name) formData.append(name, submitter.value)\n }\n\n const multipart =\n formEl.getAttribute('enctype') === 'multipart/form-data'\n // Leave the `Content-Type` header empty for multipart encoding so the browser can set it automatically with the correct boundary\n if (!multipart) {\n headers['Content-Type'] = 'application/x-www-form-urlencoded'\n }\n\n const formParams = new URLSearchParams(formData as any)\n if (method === 'GET') {\n for (const [key, value] of formParams) {\n queryParams.append(key, value)\n }\n } else if (multipart) {\n req.body = formData\n } else {\n req.body = formParams\n }\n } else {\n throw error('FetchInvalidContentType', { action, contentType })\n }\n\n dispatchFetch(STARTED, el, {})\n urlInstance.search = queryParams.toString()\n\n try {\n await fetchEventSource(urlInstance.toString(), el, req)\n } catch (e: any) {\n if (!isWrongContent(e)) {\n throw error('FetchFailed', { method, url, error: e.message })\n }\n // exit gracefully and do nothing if the content-type is wrong\n // this can happen if the client is sending a request\n // where no response is expected, and they haven\u2019t\n // set the content-type to text/event-stream\n }\n } finally {\n dispatchFetch(FINISHED, el, {})\n cleanupFn?.()\n cleanups.delete(`@${name}`)\n }\n },\n })\n\ncreateHttpMethod('get', 'GET', false)\ncreateHttpMethod('patch', 'PATCH')\ncreateHttpMethod('post', 'POST')\ncreateHttpMethod('put', 'PUT')\ncreateHttpMethod('delete', 'DELETE')\n\nexport const STARTED = 'started'\nexport const FINISHED = 'finished'\nexport const ERROR = 'error'\nexport const RETRYING = 'retrying'\nexport const RETRIES_FAILED = 'retries-failed'\n\nconst dispatchFetch = (\n type: string,\n el: HTMLOrSVG,\n argsRaw: Record,\n) =>\n document.dispatchEvent(\n new CustomEvent(DATASTAR_FETCH_EVENT, {\n detail: { type, el, argsRaw },\n }),\n )\n\nconst isWrongContent = (err: any) => `${err}`.includes('text/event-stream')\n\ntype ResponseOverrides =\n | {\n selector?: string\n mode?: string\n namespace?: string\n useViewTransition?: boolean\n }\n | {\n onlyIfMissing?: boolean\n }\n\nexport type FetchArgs = {\n selector?: string\n headers?: Record\n contentType?: 'json' | 'form'\n filterSignals?: SignalFilterOptions\n openWhenHidden?: boolean\n payload?: any\n requestCancellation?: 'auto' | 'disabled' | AbortController\n responseOverrides?: ResponseOverrides\n retry?: 'auto' | 'error' | 'always' | 'never'\n retryInterval?: number\n retryScaler?: number\n retryMaxWaitMs?: number\n retryMaxCount?: number\n}\n\n// Below originally from https://github.com/Azure/fetch-event-source/blob/main/LICENSE\n\n/**\n * Represents a message sent in an event stream\n * https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format\n */\n\ninterface EventSourceMessage {\n id: string\n event: string\n data: string\n retry?: number\n}\n\n/**\n * Converts a ReadableStream into a callback pattern.\n * @param stream The input ReadableStream.\n * @param onChunk A function that will be called on each new byte chunk in the stream.\n * @returns {Promise} A promise that will be resolved when the stream closes.\n */\nconst getBytes = async (\n stream: ReadableStream,\n onChunk: (arr: Uint8Array) => void,\n): Promise => {\n const reader = stream.getReader()\n let result = await reader.read()\n while (!result.done) {\n onChunk(result.value)\n result = await reader.read()\n }\n}\n\nconst getLines = (onLine: (line: Uint8Array, fieldLength: number) => void) => {\n let buffer: Uint8Array | undefined\n let position: number // current read position\n let fieldLength: number // length of the `field` portion of the line\n let discardTrailingNewline = false\n\n // return a function that can process each incoming byte chunk:\n return (arr: Uint8Array) => {\n if (!buffer) {\n buffer = arr\n position = 0\n fieldLength = -1\n } else {\n // we're still parsing the old line. Append the new bytes into buffer:\n buffer = concat(buffer, arr)\n }\n\n const bufLength = buffer.length\n let lineStart = 0 // index where the current line starts\n while (position < bufLength) {\n if (discardTrailingNewline) {\n if (buffer[position] === 10) lineStart = ++position // skip to next char\n discardTrailingNewline = false\n }\n\n // start looking forward till the end of line:\n let lineEnd = -1 // index of the \\r or \\n char\n for (; position < bufLength && lineEnd === -1; ++position) {\n switch (buffer[position]) {\n case 58: // :\n if (fieldLength === -1) {\n // first colon in line\n fieldLength = position - lineStart\n }\n break\n // @ts-expect-error:7029 \\r case below should fallthrough to \\n:\n // biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional fallthrough for CR to LF\n case 13: // \\r\n discardTrailingNewline = true\n case 10: // \\n\n lineEnd = position\n break\n }\n }\n\n if (lineEnd === -1) break // Wait for the next arr and then continue parsing\n\n // we've reached the line end, send it out:\n onLine(buffer.subarray(lineStart, lineEnd), fieldLength)\n lineStart = position // we're now on the next line\n fieldLength = -1\n }\n\n if (lineStart === bufLength)\n buffer = undefined // we've finished reading it\n else if (lineStart) {\n // Create a new view into buffer beginning at lineStart so we don't\n // need to copy over the previous lines when we get the new arr:\n buffer = buffer.subarray(lineStart)\n position -= lineStart\n }\n }\n}\n\nconst getMessages = (\n onId: (id: string) => void,\n onRetry: (retry: number) => void,\n onMessage?: (msg: EventSourceMessage) => void,\n): ((line: Uint8Array, fieldLength: number) => void) => {\n let message = newMessage()\n const decoder = new TextDecoder()\n\n // return a function that can process each incoming line buffer:\n return (line, fieldLength) => {\n if (!line.length) {\n // empty line denotes end of message. Trigger the callback and start a new message:\n onMessage?.(message)\n message = newMessage()\n } else if (fieldLength > 0) {\n // exclude comments and lines with no values\n // line is of format \":\" or \": \"\n // https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation\n const field = decoder.decode(line.subarray(0, fieldLength))\n const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1)\n const value = decoder.decode(line.subarray(valueOffset))\n\n switch (field) {\n case 'data':\n message.data = message.data ? `${message.data}\\n${value}` : value\n break\n case 'event':\n message.event = value\n break\n case 'id':\n onId((message.id = value))\n break\n case 'retry': {\n const retry = +value\n if (!Number.isNaN(retry)) {\n // per spec, ignore non-integers\n onRetry((message.retry = retry))\n }\n break\n }\n }\n }\n }\n}\n\nconst concat = (a: Uint8Array, b: Uint8Array) => {\n const res = new Uint8Array(a.length + b.length)\n res.set(a)\n res.set(b, a.length)\n return res\n}\n\nconst newMessage = (): EventSourceMessage => ({\n // data, event, and id must be initialized to empty strings:\n // https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation\n // retry should be initialized to undefined so we return a consistent shape\n // to the js engine all the time: https://mathiasbynens.be/notes/shapes-ics#takeaways\n data: '',\n event: '',\n id: '',\n retry: undefined,\n})\n\ntype FetchEventSourceInit = RequestInit & {\n headers?: Record\n onopen?: (response: Response) => Promise\n onmessage?: (ev: EventSourceMessage) => void\n onclose?: () => void\n onerror?: (err: any) => number | null | undefined | void\n openWhenHidden?: boolean\n fetch?: typeof fetch\n retry?: 'auto' | 'error' | 'always' | 'never'\n retryInterval?: number\n retryScaler?: number\n retryMaxWaitMs?: number\n retryMaxCount?: number\n responseOverrides?: ResponseOverrides\n}\n\nconst fetchEventSource = (\n input: RequestInfo,\n el: HTMLOrSVG,\n {\n signal: inputSignal,\n headers: inputHeaders,\n onopen: inputOnOpen,\n onmessage,\n onclose,\n onerror,\n openWhenHidden,\n fetch: inputFetch,\n retry = 'auto',\n retryInterval = 1_000,\n retryScaler = 2,\n retryMaxWaitMs = 30_000,\n retryMaxCount = 10,\n responseOverrides,\n ...rest\n }: FetchEventSourceInit,\n): Promise => {\n return new Promise((resolve, reject) => {\n // make a copy of the input headers since we may modify it below:\n const headers: Record = {\n ...inputHeaders,\n }\n\n let curRequestController: AbortController\n const onVisibilityChange = () => {\n curRequestController.abort() // close existing request on every visibility change\n if (!document.hidden) create() // page is now visible again, recreate request.\n }\n\n if (!openWhenHidden) {\n document.addEventListener('visibilitychange', onVisibilityChange)\n }\n\n let retryTimer: ReturnType | undefined\n const dispose = () => {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n clearTimeout(retryTimer)\n curRequestController.abort()\n }\n\n // if the incoming signal aborts, dispose resources and resolve:\n inputSignal?.addEventListener('abort', () => {\n dispose()\n resolve() // don't waste time constructing/logging errors\n })\n\n const fetch = inputFetch || window.fetch\n const onopen = inputOnOpen || (() => {})\n\n let retries = 0\n let baseRetryInterval = retryInterval\n const create = async () => {\n curRequestController = new AbortController()\n const curRequestSignal = curRequestController.signal\n try {\n const response = await fetch(input, {\n ...rest,\n headers,\n signal: curRequestSignal,\n })\n\n await onopen(response)\n\n const dispatchNonSSE = async (\n dispatchType: string,\n response: Response,\n name: string,\n responseOverrides?: ResponseOverrides,\n ...argNames: string[]\n ) => {\n const argsRaw: Record = {\n [name]: await response.text(),\n }\n for (const n of argNames) {\n let v = response.headers.get(`datastar-${kebab(n)}`)\n if (responseOverrides) {\n const o = (responseOverrides as any)[n]\n if (o) v = typeof o === 'string' ? o : JSON.stringify(o)\n }\n if (v) argsRaw[n] = v\n }\n\n dispatchFetch(dispatchType, el, argsRaw)\n dispose()\n resolve()\n }\n\n const status = response.status\n const isNoContentStatus = status === 204\n const isRedirectStatus = status >= 300 && status < 400\n const isErrorStatus = status >= 400 && status < 600\n\n if (status !== 200) {\n onclose?.()\n if (\n retry !== 'never' &&\n !isNoContentStatus &&\n !isRedirectStatus &&\n (retry === 'always' || (retry === 'error' && isErrorStatus))\n ) {\n clearTimeout(retryTimer)\n retryTimer = setTimeout(create, retryInterval)\n return\n }\n dispose()\n resolve()\n return\n }\n\n // on successful connection, reset the retry logic\n retries = 0\n retryInterval = baseRetryInterval\n\n const ct = response.headers.get('Content-Type')\n if (ct?.includes('text/html')) {\n return await dispatchNonSSE(\n 'datastar-patch-elements',\n response,\n 'elements',\n responseOverrides,\n 'selector',\n 'mode',\n 'namespace',\n 'useViewTransition',\n )\n }\n\n if (ct?.includes('application/json')) {\n return await dispatchNonSSE(\n 'datastar-patch-signals',\n response,\n 'signals',\n responseOverrides,\n 'onlyIfMissing',\n )\n }\n\n if (ct?.includes('text/javascript')) {\n const script = document.createElement('script')\n const scriptAttributesHeader = response.headers.get(\n 'datastar-script-attributes',\n )\n\n if (scriptAttributesHeader) {\n for (const [name, value] of Object.entries(\n JSON.parse(scriptAttributesHeader),\n )) {\n script.setAttribute(name, value as string)\n }\n }\n script.textContent = await response.text()\n document.head.appendChild(script)\n dispose()\n return\n }\n\n await getBytes(\n response.body!,\n getLines(\n getMessages(\n (id) => {\n if (id) {\n // signals the id and send it back on the next retry:\n headers['last-event-id'] = id\n } else {\n // don't send the last-event-id header anymore:\n delete headers['last-event-id']\n }\n },\n (retry) => {\n baseRetryInterval = retryInterval = retry\n },\n onmessage,\n ),\n ),\n )\n\n onclose?.()\n\n if (retry === 'always' && !isRedirectStatus) {\n clearTimeout(retryTimer)\n retryTimer = setTimeout(create, retryInterval)\n return\n }\n\n dispose()\n resolve()\n } catch (err) {\n if (!curRequestSignal.aborted) {\n // if we haven\u2019t aborted the request ourselves:\n try {\n // check if we need to retry:\n const interval: any = onerror?.(err) || retryInterval\n clearTimeout(retryTimer)\n retryTimer = setTimeout(create, interval)\n retryInterval = Math.min(\n retryInterval * retryScaler,\n retryMaxWaitMs,\n ) // exponential backoff\n if (++retries >= retryMaxCount) {\n dispatchFetch(RETRIES_FAILED, el, {})\n // we should not retry anymore:\n dispose()\n reject('Max retries reached.') // Max retries reached, check your server or network connection\n } else {\n console.error(\n `Datastar failed to reach ${input.toString()} retrying in ${interval}ms.`,\n )\n }\n } catch (innerErr) {\n // we should not retry anymore:\n dispose()\n reject(innerErr)\n }\n }\n }\n }\n\n create()\n })\n}\n", "// Icon: material-symbols:edit-attributes-outline\n// Slug: Syncs the value of an attribute with an expression.\n// Description: Sets the value of any HTML attribute to an expression, and keeps it in sync.\n\nimport { attribute } from '@engine'\nimport { effect } from '@engine/signals'\n\nattribute({\n name: 'attr',\n requirement: { value: 'must' },\n returnsValue: true,\n apply({ el, key, rx }) {\n const syncAttr = (key: string, val: any) => {\n if (val === '' || val === true) {\n el.setAttribute(key, '')\n } else if (val === false || val == null) {\n el.removeAttribute(key)\n } else if (typeof val === 'string') {\n el.setAttribute(key, val)\n } else {\n el.setAttribute(key, JSON.stringify(val))\n }\n }\n\n const update = key\n ? () => {\n observer.disconnect()\n const val = rx() as string\n syncAttr(key, val)\n observer.observe(el, {\n attributeFilter: [key],\n })\n }\n : () => {\n observer.disconnect()\n const obj = rx() as Record\n const attributeFilter = Object.keys(obj)\n for (const key of attributeFilter) {\n syncAttr(key, obj[key])\n }\n observer.observe(el, {\n attributeFilter,\n })\n }\n\n const observer = new MutationObserver(update)\n const cleanup = effect(update)\n\n return () => {\n observer.disconnect()\n cleanup()\n }\n },\n})\n", "// Icon: akar-icons:link-chain\n// Slug: Creates a signal with two-way data binding.\n// Description: Creates a signal (if one doesn\u2019t already exist) and sets up two-way data binding between it and an element\u2019s value.\n\nimport { attribute } from '@engine'\nimport { effect, getPath, mergePaths } from '@engine/signals'\nimport type { Paths } from '@engine/types'\nimport { aliasify, modifyCasing } from '@utils/text'\n\ntype SignalFile = {\n name: string\n contents: string\n mime: string\n}\n\nconst dataURIRegex = /^data:(?[^;]+);base64,(?.*)$/\nconst empty = Symbol('empty')\n\nconst aliasedBind = aliasify('bind')\n\nattribute({\n name: 'bind',\n requirement: 'exclusive',\n apply({ el, key, mods, value, error }) {\n const signalName = key != null ? modifyCasing(key, mods) : value\n\n let get = (el: any, type: string) =>\n type === 'number' ? +el.value : el.value\n\n let set = (value: any) => {\n ;(el as HTMLInputElement).value = `${value}`\n }\n\n if (el instanceof HTMLInputElement) {\n switch (el.type) {\n case 'range':\n case 'number':\n get = (el: any, type: string) =>\n type === 'string' ? el.value : +el.value\n break\n\n case 'checkbox':\n get = (el: HTMLInputElement, type: string) => {\n if (el.value !== 'on') {\n if (type === 'boolean') {\n return el.checked\n } else {\n return el.checked ? el.value : ''\n }\n } else {\n if (type === 'string') {\n return el.checked ? el.value : ''\n } else {\n return el.checked\n }\n }\n }\n set = (value: string | boolean) => {\n el.checked = typeof value === 'string' ? value === el.value : value\n }\n break\n\n case 'radio':\n // Set up radio button name attribute if not present\n if (!el.getAttribute('name')?.length) {\n el.setAttribute('name', signalName)\n }\n\n get = (el: HTMLInputElement, type: string) =>\n el.checked ? (type === 'number' ? +el.value : el.value) : empty\n set = (value: string | number) => {\n el.checked =\n value === (typeof value === 'number' ? +el.value : el.value)\n }\n break\n case 'file': {\n const syncSignal = () => {\n const files = [...(el.files || [])]\n const signalFiles: SignalFile[] = []\n Promise.all(\n files.map(\n (f) =>\n new Promise((resolve) => {\n const reader = new FileReader()\n reader.onload = () => {\n if (typeof reader.result !== 'string') {\n throw error('InvalidFileResultType', {\n resultType: typeof reader.result,\n })\n }\n const match = reader.result.match(dataURIRegex)\n if (!match?.groups) {\n throw error('InvalidDataUri', {\n result: reader.result,\n })\n }\n signalFiles.push({\n name: f.name,\n contents: match.groups.contents,\n mime: match.groups.mime,\n })\n }\n reader.onloadend = () => resolve()\n reader.readAsDataURL(f)\n }),\n ),\n ).then(() => {\n mergePaths([[signalName, signalFiles]])\n })\n }\n\n el.addEventListener('change', syncSignal)\n el.addEventListener('input', syncSignal)\n\n return () => {\n el.removeEventListener('change', syncSignal)\n el.removeEventListener('input', syncSignal)\n }\n }\n }\n } else if (el instanceof HTMLSelectElement) {\n if (el.multiple) {\n const typeMap = new Map()\n get = (el: HTMLSelectElement) =>\n [...el.selectedOptions].map((option) => {\n const type = typeMap.get(option.value)\n return type === 'string' || type == null\n ? option.value\n : +option.value\n })\n\n set = (value: (string | number)[]) => {\n for (const option of el.options) {\n if (value.includes(option.value)) {\n typeMap.set(option.value, 'string')\n option.selected = true\n } else if (value.includes(+option.value)) {\n typeMap.set(option.value, 'number')\n option.selected = true\n } else {\n option.selected = false\n }\n }\n }\n }\n } else if (el instanceof HTMLTextAreaElement) {\n // default case\n } else {\n // web component\n get = (el: Element) =>\n 'value' in el ? el.value : el.getAttribute('value')\n set = (value: any) => {\n if ('value' in el) {\n el.value = value\n } else {\n el.setAttribute('value', value)\n }\n }\n }\n\n const initialValue = getPath(signalName)\n const type = typeof initialValue\n\n let path = signalName\n if (\n Array.isArray(initialValue) &&\n !(el instanceof HTMLSelectElement && el.multiple)\n ) {\n const signalNameKebab = key ? key : value!\n const inputs = document.querySelectorAll(\n `[${aliasedBind}\\\\:${CSS.escape(signalNameKebab)}],[${aliasedBind}=\"${CSS.escape(signalNameKebab)}\"]`,\n ) as NodeListOf\n\n const paths: Paths = []\n let i = 0\n for (const input of inputs) {\n paths.push([`${path}.${i}`, get(input, 'none')])\n\n if (el === input) {\n break\n }\n i++\n }\n mergePaths(paths, { ifMissing: true })\n path = `${path}.${i}`\n } else {\n mergePaths([[path, get(el, type)]], {\n ifMissing: true,\n })\n }\n\n const syncSignal = () => {\n const signalValue = getPath(path)\n if (signalValue != null) {\n const value = get(el, typeof signalValue)\n if (value !== empty) {\n mergePaths([[path, value]])\n }\n }\n }\n\n el.addEventListener('input', syncSignal)\n el.addEventListener('change', syncSignal)\n const cleanup = effect(() => {\n set(getPath(path))\n })\n\n return () => {\n cleanup()\n el.removeEventListener('input', syncSignal)\n el.removeEventListener('change', syncSignal)\n }\n },\n})\n", "// Icon: ic:baseline-format-paint\n// Slug: Adds or removes a class based on an expression.\n// Description: Adds or removes a class to or from an element based on an expression.\n\nimport { attribute } from '@engine'\nimport { effect } from '@engine/signals'\nimport { modifyCasing } from '@utils/text'\n\nattribute({\n name: 'class',\n requirement: {\n value: 'must',\n },\n returnsValue: true,\n apply({ key, el, mods, rx }) {\n key &&= modifyCasing(key, mods, 'kebab')\n\n let classes: Record\n const callback = () => {\n observer.disconnect()\n\n classes = key\n ? { [key]: rx() as boolean }\n : (rx() as Record)\n\n for (const k in classes) {\n const classNames = k.split(/\\s+/).filter((cn) => cn.length > 0)\n if (classes[k]) {\n for (const name of classNames) {\n if (!el.classList.contains(name)) {\n el.classList.add(name)\n }\n }\n } else {\n for (const name of classNames) {\n if (el.classList.contains(name)) {\n el.classList.remove(name)\n }\n }\n }\n }\n\n observer.observe(el, { attributeFilter: ['class'] })\n }\n\n const observer = new MutationObserver(callback)\n const cleanup = effect(callback)\n\n return () => {\n observer.disconnect()\n cleanup()\n\n for (const k in classes) {\n const classNames = k.split(/\\s+/).filter((cn) => cn.length > 0)\n for (const name of classNames) {\n el.classList.remove(name)\n }\n }\n }\n },\n})\n", "// Icon: streamline-ultimate:wifi-signal-2\n// Slug: Creates a computed signal.\n// Description: Creates a signal that is computed based on an expression.\n\nimport { attribute } from '@engine'\nimport { computed, mergePaths, mergePatch } from '@engine/signals'\nimport { modifyCasing } from '@utils/text'\nimport { updateLeaves } from '@utils/paths'\n\nattribute({\n name: 'computed',\n requirement: {\n value: 'must',\n },\n returnsValue: true,\n apply({ key, mods, rx, error }) {\n if (key) {\n mergePaths([[modifyCasing(key, mods), computed(rx)]])\n } else {\n const patch = Object.assign({}, rx() as Record any>)\n updateLeaves(patch, (old) => {\n if (typeof old === 'function') {\n return computed(old)\n } else {\n throw error('ComputedExpectedFunction')\n }\n })\n mergePatch(patch)\n }\n },\n})\n", "// Icon: oui:security-signal\n// Slug: Executes an expression when signals change.\n// Description: Executes an expression on page load and whenever any signals in the expression change.\n\nimport { attribute } from '@engine'\nimport { effect } from '@engine/signals'\n\nattribute({\n name: 'effect',\n requirement: {\n key: 'denied',\n value: 'must',\n },\n apply: ({ rx }) => effect(rx),\n})\n", "// Icon: streamline:signal-loading-remix\n// Slug: Creates an indicator for whether an SSE request is in flight.\n// Description: Creates a signal and sets its value to `true` while an SSE request request is in flight, otherwise `false`.\n\nimport { attribute } from '@engine'\nimport { DATASTAR_FETCH_EVENT } from '@engine/consts'\nimport { mergePaths } from '@engine/signals'\nimport type { DatastarFetchEvent } from '@engine/types'\nimport { FINISHED, STARTED } from '@plugins/actions/fetch'\nimport { modifyCasing } from '@utils/text'\n\nattribute({\n name: 'indicator',\n requirement: 'exclusive',\n apply({ el, key, mods, value }) {\n const signalName = key != null ? modifyCasing(key, mods) : value\n\n mergePaths([[signalName, false]])\n\n const watcher = ((event: CustomEvent) => {\n const { type, el: elt } = event.detail\n if (elt !== el) {\n return\n }\n switch (type) {\n case STARTED:\n mergePaths([[signalName, true]])\n break\n case FINISHED:\n mergePaths([[signalName, false]])\n break\n }\n }) as EventListener\n document.addEventListener(DATASTAR_FETCH_EVENT, watcher)\n return () => {\n mergePaths([[signalName, false]])\n document.removeEventListener(DATASTAR_FETCH_EVENT, watcher)\n }\n },\n})\n", "export const tagToMs = (args: Set) => {\n if (!args || args.size <= 0) return 0\n for (const arg of args) {\n if (arg.endsWith('ms')) {\n return +arg.replace('ms', '')\n }\n if (arg.endsWith('s')) {\n return +arg.replace('s', '') * 1000\n }\n try {\n return Number.parseFloat(arg)\n } catch (_) {}\n }\n return 0\n}\n\nexport const tagHas = (\n tags: Set,\n tag: string,\n defaultValue = false,\n) => {\n if (!tags) return defaultValue\n return tags.has(tag.toLowerCase())\n}\n\nexport const tagFirst = (tags?: Set, defaultValue = ''): string => {\n if (tags && tags.size > 0) {\n for (const tag of tags) {\n return tag\n }\n }\n return defaultValue\n}\n", "import type { EventCallbackHandler, Modifiers } from '@engine/types'\nimport { tagHas, tagToMs } from '@utils/tags'\n\nexport const delay = (\n callback: EventCallbackHandler,\n wait: number,\n): EventCallbackHandler => {\n return (...args: any[]) => {\n setTimeout(() => {\n callback(...args)\n }, wait)\n }\n}\n\nexport const throttle = (\n callback: EventCallbackHandler,\n wait: number,\n leading = true,\n trailing = false,\n debounce = false,\n): EventCallbackHandler => {\n let lastArgs: Parameters | null = null\n let timer = 0\n\n return (...args: any[]) => {\n if (leading && !timer) {\n callback(...args)\n lastArgs = null\n } else {\n lastArgs = args\n }\n if (!timer || debounce) {\n if (timer) {\n clearTimeout(timer)\n }\n timer = setTimeout(() => {\n if (trailing && lastArgs !== null) {\n callback(...lastArgs)\n }\n lastArgs = null\n timer = 0\n }, wait)\n }\n }\n}\n\nexport const modifyTiming = (\n callback: EventCallbackHandler,\n mods: Modifiers,\n): EventCallbackHandler => {\n const delayArgs = mods.get('delay')\n if (delayArgs) {\n const wait = tagToMs(delayArgs)\n callback = delay(callback, wait)\n }\n\n const debounceArgs = mods.get('debounce')\n if (debounceArgs) {\n const wait = tagToMs(debounceArgs)\n const leading = tagHas(debounceArgs, 'leading', false)\n const trailing = !tagHas(debounceArgs, 'notrailing', false)\n callback = throttle(callback, wait, leading, trailing, true)\n }\n\n const throttleArgs = mods.get('throttle')\n if (throttleArgs) {\n const wait = tagToMs(throttleArgs)\n const leading = !tagHas(throttleArgs, 'noleading', false)\n const trailing = tagHas(throttleArgs, 'trailing', false)\n callback = throttle(callback, wait, leading, trailing)\n }\n\n return callback\n}\n", "import type { EventCallbackHandler, Modifiers } from '@engine/types'\n\nexport const supportsViewTransitions = !!document.startViewTransition\n\nexport const modifyViewTransition = (\n callback: EventCallbackHandler,\n mods: Modifiers,\n): EventCallbackHandler => {\n if (mods.has('viewtransition') && supportsViewTransitions) {\n const cb = callback // I hate javascript\n callback = (...args: any[]) =>\n document.startViewTransition(() => cb(...args))\n }\n\n return callback\n}\n", "// Icon: material-symbols:timer-play-outline\n// Slug: Runs an expression when loaded into the DOM.\n// Description: Runs an expression when the element is loaded into the DOM.\n\nimport { attribute } from '@engine'\nimport { beginBatch, endBatch } from '@engine/signals'\nimport { tagToMs } from '@utils/tags'\nimport { delay } from '@utils/timing'\nimport { modifyViewTransition } from '@utils/view-transitions'\n\nattribute({\n name: 'init',\n requirement: {\n key: 'denied',\n value: 'must',\n },\n apply({ rx, mods }) {\n let callback = () => {\n beginBatch()\n rx()\n endBatch()\n }\n callback = modifyViewTransition(callback, mods)\n let wait = 0\n const delayArgs = mods.get('delay')\n if (delayArgs) {\n wait = tagToMs(delayArgs)\n if (wait > 0) {\n callback = delay(callback, wait)\n }\n }\n callback()\n },\n})\n", "// Icon: si:json-fill\n// Slug: Outputs a JSON stringified version of signals.\n// Description: Sets the text content of an element to a reactive JSON stringified version of signals.\n\nimport { attribute } from '@engine'\nimport { effect, filtered } from '@engine/signals'\nimport type { SignalFilterOptions } from '@engine/types'\nimport { jsStrToObject } from '@utils/text'\n\nattribute({\n name: 'json-signals',\n requirement: {\n key: 'denied',\n },\n apply({ el, value, mods }) {\n const spaces = mods.has('terse') ? 0 : 2\n let filters: SignalFilterOptions = {}\n if (value) {\n filters = jsStrToObject(value)\n }\n\n const callback = () => {\n observer.disconnect()\n el.textContent = JSON.stringify(filtered(filters), null, spaces)\n observer.observe(el, {\n childList: true,\n characterData: true,\n subtree: true,\n })\n }\n const observer = new MutationObserver(callback)\n const cleanup = effect(callback)\n\n return () => {\n observer.disconnect()\n cleanup()\n }\n },\n})\n", "// Icon: material-symbols:mail\n// Slug: Attaches an event listener to an element.\n// Description: Attaches an event listener to an element, executing an expression whenever the event is triggered.\n\nimport { attribute } from '@engine'\nimport {\n DATASTAR_FETCH_EVENT,\n DATASTAR_SIGNAL_PATCH_EVENT,\n} from '@engine/consts'\nimport { beginBatch, endBatch } from '@engine/signals'\nimport { modifyCasing } from '@utils/text'\nimport { modifyTiming } from '@utils/timing'\nimport { modifyViewTransition } from '@utils/view-transitions'\n\nattribute({\n name: 'on',\n requirement: 'must',\n argNames: ['evt'],\n apply({ el, key, mods, rx }) {\n let target: Element | Window | Document = el\n if (mods.has('window')) target = window\n let callback = (evt?: Event) => {\n if (evt) {\n if (mods.has('prevent')) {\n evt.preventDefault()\n }\n if (mods.has('stop')) {\n evt.stopPropagation()\n }\n }\n beginBatch()\n rx(evt)\n endBatch()\n }\n callback = modifyViewTransition(callback, mods)\n callback = modifyTiming(callback, mods)\n const evtListOpts: AddEventListenerOptions = {\n capture: mods.has('capture'),\n passive: mods.has('passive'),\n once: mods.has('once'),\n }\n if (mods.has('outside')) {\n target = document\n const cb = callback\n callback = (evt?: Event) => {\n if (!el.contains(evt?.target as HTMLElement)) {\n cb(evt)\n }\n }\n }\n const eventName = modifyCasing(key, mods, 'kebab')\n // Listen for Datastar events on the document\n if (\n eventName === DATASTAR_FETCH_EVENT ||\n eventName === DATASTAR_SIGNAL_PATCH_EVENT\n ) {\n target = document\n }\n // Prevent default on form submit events\n if (el instanceof HTMLFormElement && eventName === 'submit') {\n const cb = callback\n callback = (evt?: Event) => {\n evt?.preventDefault()\n cb(evt)\n }\n }\n target.addEventListener(eventName, callback, evtListOpts)\n return () => {\n target.removeEventListener(eventName, callback)\n }\n },\n})\n", "export const clamp = (value: number, min: number, max: number): number => {\n return Math.max(min, Math.min(max, value))\n}\n\nexport const lerp = (\n min: number,\n max: number,\n t: number,\n clamped = true,\n): number => {\n const v = min + (max - min) * t\n return clamped ? clamp(v, min, max) : v\n}\n\nexport const inverseLerp = (\n min: number,\n max: number,\n value: number,\n clamped = true,\n): number => {\n if (value < min) return 0\n if (value > max) return 1\n const v = (value - min) / (max - min)\n return clamped ? clamp(v, min, max) : v\n}\n\nexport const fit = (\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number,\n clamped = true,\n rounded = false,\n): number => {\n const t = inverseLerp(inMin, inMax, value, clamped)\n const fitted = lerp(outMin, outMax, t, clamped)\n return rounded ? Math.round(fitted) : fitted\n}\n", "// Icon: mdi-light:vector-intersection\n// Slug: Runs an expression on intersection.\n// Description: Runs an expression when the element intersects with the viewport.\n\nimport { attribute } from '@engine'\nimport { beginBatch, endBatch } from '@engine/signals'\nimport type { HTMLOrSVG } from '@engine/types'\nimport { clamp } from '@utils/math'\nimport { modifyTiming } from '@utils/timing'\nimport { modifyViewTransition } from '@utils/view-transitions'\n\nconst once = new WeakSet()\n\nattribute({\n name: 'on-intersect',\n requirement: {\n key: 'denied',\n value: 'must',\n },\n apply({ el, mods, rx }) {\n let callback = () => {\n beginBatch()\n rx()\n endBatch()\n }\n callback = modifyViewTransition(callback, mods)\n callback = modifyTiming(callback, mods)\n const options = { threshold: 0 }\n if (mods.has('full')) {\n options.threshold = 1\n } else if (mods.has('half')) {\n options.threshold = 0.5\n } else if (mods.get('threshold')) {\n options.threshold = clamp(Number(mods.get('threshold')), 0, 100) / 100\n }\n const exit = mods.has('exit')\n let observer: IntersectionObserver | null = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting !== exit) {\n callback()\n if (observer && once.has(el)) {\n observer.disconnect()\n }\n }\n }\n },\n options,\n )\n observer.observe(el)\n if (mods.has('once')) {\n once.add(el)\n }\n return () => {\n if (!mods.has('once')) {\n once.delete(el)\n }\n if (observer) {\n observer.disconnect()\n observer = null\n }\n }\n },\n})\n", "// Icon: material-symbols:timer-outline\n// Slug: Runs an expression at a regular interval.\n// Description: Runs an expression at a regular interval.\n\nimport { attribute } from '@engine'\nimport { beginBatch, endBatch } from '@engine/signals'\nimport { tagHas, tagToMs } from '@utils/tags'\nimport { modifyViewTransition } from '@utils/view-transitions'\n\nattribute({\n name: 'on-interval',\n requirement: {\n key: 'denied',\n value: 'must',\n },\n apply({ mods, rx }) {\n let callback = () => {\n beginBatch()\n rx()\n endBatch()\n }\n callback = modifyViewTransition(callback, mods)\n let duration = 1000\n const durationArgs = mods.get('duration')\n if (durationArgs) {\n duration = tagToMs(durationArgs)\n const leading = tagHas(durationArgs, 'leading', false)\n if (leading) {\n callback()\n }\n }\n const intervalId = setInterval(callback, duration)\n return () => {\n clearInterval(intervalId)\n }\n },\n})\n", "// Icon: material-symbols:change-circle-outline\n// Slug: Runs an expression when signals are patched.\n// Description: Runs an expression whenever one or more signals are patched.\n\nimport { attribute } from '@engine'\nimport { DATASTAR_SIGNAL_PATCH_EVENT } from '@engine/consts'\nimport { beginBatch, endBatch, filtered } from '@engine/signals'\nimport type { JSONPatch, SignalFilterOptions } from '@engine/types'\nimport { isEmpty } from '@utils/paths'\nimport { aliasify, jsStrToObject } from '@utils/text'\nimport { modifyTiming } from '@utils/timing'\n\nattribute({\n name: 'on-signal-patch',\n requirement: {\n value: 'must',\n },\n argNames: ['patch'],\n returnsValue: true,\n apply({ el, key, mods, rx, error }) {\n if (!!key && key !== 'filter') {\n throw error('KeyNotAllowed')\n }\n\n const filterAttr = aliasify(`${this.name}-filter`)\n const filtersRaw = el.getAttribute(filterAttr)\n let filters: SignalFilterOptions = {}\n if (filtersRaw) {\n filters = jsStrToObject(filtersRaw)\n }\n\n let running = false\n\n const callback: EventListener = modifyTiming(\n (evt: CustomEvent) => {\n if (running) return\n const watched = filtered(filters, evt.detail)\n if (!isEmpty(watched)) {\n running = true\n beginBatch()\n try {\n rx(watched)\n } finally {\n endBatch()\n running = false\n }\n }\n },\n mods,\n )\n\n document.addEventListener(DATASTAR_SIGNAL_PATCH_EVENT, callback)\n return () => {\n document.removeEventListener(DATASTAR_SIGNAL_PATCH_EVENT, callback)\n }\n },\n})\n", "// Icon: mdi:cursor-pointer\n// Slug: Creates a reference to an element.\n// Description: Creates a new signal that is a reference to the element on which the data attribute is placed.\n\nimport { attribute } from '@engine'\nimport { mergePaths } from '@engine/signals'\nimport { modifyCasing } from '@utils/text'\n\nattribute({\n name: 'ref',\n requirement: 'exclusive',\n apply({ el, key, mods, value }) {\n const signalName = key != null ? modifyCasing(key, mods) : value\n mergePaths([[signalName, el]])\n },\n})\n", "// Icon: streamline:interface-edit-view-eye-eyeball-open-view\n// Slug: Shows or hides an element.\n// Description: Shows or hides an element based on whether an expression evaluates to `true` or `false`.\n\nimport { attribute } from '@engine'\nimport { effect } from '@engine/signals'\n\nconst NONE = 'none'\nconst DISPLAY = 'display'\n\nattribute({\n name: 'show',\n requirement: {\n key: 'denied',\n value: 'must',\n },\n returnsValue: true,\n apply({ el, rx }) {\n const update = () => {\n observer.disconnect()\n const shouldShow = rx()\n if (shouldShow) {\n if (el.style.display === NONE) el.style.removeProperty(DISPLAY)\n } else {\n el.style.setProperty(DISPLAY, NONE)\n }\n observer.observe(el, { attributeFilter: ['style'] })\n }\n const observer = new MutationObserver(update)\n const cleanup = effect(update)\n\n return () => {\n observer.disconnect()\n cleanup()\n }\n },\n})\n", "// Icon: streamline:wifi-signal-full-remix\n// Slug: Patches signals into the existing signals.\n// Description: Patches (adds, updates or removes) one or more signals into the existing signals.\n\nimport { attribute } from '@engine'\nimport { mergePatch, mergePaths } from '@engine/signals'\nimport { modifyCasing } from '@utils/text'\n\nattribute({\n name: 'signals',\n returnsValue: true,\n apply({ key, mods, rx }) {\n const ifMissing = mods.has('ifmissing')\n\n if (key) {\n key = modifyCasing(key, mods)\n mergePaths([[key, rx?.()]], { ifMissing })\n } else {\n const patch = Object.assign({}, rx?.() as Record)\n mergePatch(patch, { ifMissing })\n }\n },\n})\n", "// Icon: material-symbols:format-paint-outline\n// Slug: Sets inline styles on an element based on an expression.\n// Description: Sets CSS styles on an element using either key-based or object syntax, and keeps them in sync with reactive signals.\n\nimport { attribute } from '@engine'\nimport { effect } from '@engine/signals'\nimport { kebab } from '@utils/text'\n\nattribute({\n name: 'style',\n requirement: {\n value: 'must',\n },\n returnsValue: true,\n apply({ key, el, rx }) {\n const { style } = el\n const initialStyles = new Map()\n\n const apply = (prop: string, value: any) => {\n const initial = initialStyles.get(prop)\n if (!value && value !== 0) {\n initial !== undefined &&\n (initial\n ? style.setProperty(prop, initial)\n : style.removeProperty(prop))\n } else {\n initial === undefined &&\n initialStyles.set(prop, style.getPropertyValue(prop))\n style.setProperty(prop, String(value))\n }\n }\n\n const update = () => {\n observer.disconnect()\n\n if (key) {\n apply(key, rx())\n } else {\n const styles = rx() as Record\n\n for (const [prop, initial] of initialStyles) {\n prop in styles ||\n (initial\n ? style.setProperty(prop, initial)\n : style.removeProperty(prop))\n }\n\n for (const prop in styles) {\n apply(kebab(prop), styles[prop])\n }\n }\n\n observer.observe(el, { attributeFilter: ['style'] })\n }\n\n const observer = new MutationObserver(update)\n const cleanup = effect(update)\n\n return () => {\n observer.disconnect()\n cleanup()\n for (const [prop, initial] of initialStyles) {\n initial ? style.setProperty(prop, initial) : style.removeProperty(prop)\n }\n }\n },\n})\n", "// Icon: icon-park-outline:text\n// Slug: Binds the text content of an element.\n// Description: Binds the text content of an element to an expression.\n\nimport { attribute } from '@engine'\nimport { effect } from '@engine/signals'\n\nattribute({\n name: 'text',\n requirement: {\n key: 'denied',\n value: 'must',\n },\n returnsValue: true,\n apply({ el, rx }) {\n const update = () => {\n observer.disconnect()\n el.textContent = `${rx()}`\n observer.observe(el, {\n childList: true,\n characterData: true,\n subtree: true,\n })\n }\n\n const observer = new MutationObserver(update)\n const cleanup = effect(update)\n\n return () => {\n observer.disconnect()\n cleanup()\n }\n },\n})\n", "// Icon: material-symbols:cloud-download\n// Slug: Patches elements into the DOM.\n// Description: Patches elements into the DOM.\n\nimport { watcher } from '@engine'\nimport type { WatcherContext } from '@engine/types'\nimport { isHTMLOrSVG } from '@utils/dom'\nimport { aliasify } from '@utils/text'\nimport { supportsViewTransitions } from '@utils/view-transitions'\n\nconst isValidType = (\n arr: T,\n value: string,\n): value is T[number] => (arr as readonly string[]).includes(value)\n\nconst PATCH_MODES = [\n 'remove',\n 'outer',\n 'inner',\n 'replace',\n 'prepend',\n 'append',\n 'before',\n 'after',\n] as const\ntype PatchElementsMode = (typeof PATCH_MODES)[number]\n\nconst NAMESPACES = ['html', 'svg', 'mathml'] as const\ntype Namespace = (typeof NAMESPACES)[number]\n\ntype PatchElementsArgs = {\n selector: string\n mode: PatchElementsMode\n namespace: Namespace\n useViewTransition: boolean\n elements: string\n}\n\nwatcher({\n name: 'datastar-patch-elements',\n apply(\n ctx,\n {\n selector = '',\n mode = 'outer',\n namespace = 'html',\n useViewTransition = '',\n elements = '',\n },\n ) {\n if (!isValidType(PATCH_MODES, mode)) {\n throw ctx.error('PatchElementsInvalidMode', { mode })\n }\n\n if (!selector && mode !== 'outer' && mode !== 'replace') {\n throw ctx.error('PatchElementsExpectedSelector')\n }\n\n if (!isValidType(NAMESPACES, namespace)) {\n throw ctx.error('PatchElementsInvalidNamespace', { namespace })\n }\n\n const args2: PatchElementsArgs = {\n selector,\n mode,\n namespace,\n useViewTransition: useViewTransition.trim() === 'true',\n elements,\n }\n\n if (supportsViewTransitions && useViewTransition) {\n document.startViewTransition(() => onPatchElements(ctx, args2))\n } else {\n onPatchElements(ctx, args2)\n }\n },\n})\n\nconst onPatchElements = (\n { error }: WatcherContext,\n { selector, mode, namespace, elements }: PatchElementsArgs,\n) => {\n const elementsWithSvgsRemoved = elements.replace(\n /]*>|>)([\\s\\S]*?)<\\/svg>/gim,\n '',\n )\n const hasHtml = /<\\/html>/.test(elementsWithSvgsRemoved)\n const hasHead = /<\\/head>/.test(elementsWithSvgsRemoved)\n const hasBody = /<\\/body>/.test(elementsWithSvgsRemoved)\n\n const wrapperTag =\n namespace === 'svg' ? 'svg' : namespace === 'mathml' ? 'math' : ''\n const wrappedEls = wrapperTag\n ? `<${wrapperTag}>${elements}`\n : elements\n\n const newDocument = new DOMParser().parseFromString(\n hasHtml || hasHead || hasBody\n ? elements\n : ``,\n 'text/html',\n )\n\n let newContent = document.createDocumentFragment()\n if (hasHtml) {\n newContent.appendChild(newDocument.documentElement)\n } else if (hasHead && hasBody) {\n newContent.appendChild(newDocument.head)\n newContent.appendChild(newDocument.body)\n } else if (hasHead) {\n newContent.appendChild(newDocument.head)\n } else if (hasBody) {\n newContent.appendChild(newDocument.body)\n } else if (wrapperTag) {\n const wrapperEl = newDocument\n .querySelector('template')!\n .content.querySelector(wrapperTag)!\n for (const child of wrapperEl.childNodes) {\n newContent.appendChild(child)\n }\n } else {\n newContent = newDocument.querySelector('template')!.content\n }\n\n if (!selector && (mode === 'outer' || mode === 'replace')) {\n for (const child of newContent.children) {\n let target: Element\n if (child instanceof HTMLHtmlElement) {\n target = document.documentElement\n } else if (child instanceof HTMLBodyElement) {\n target = document.body\n } else if (child instanceof HTMLHeadElement) {\n target = document.head\n } else {\n target = document.getElementById(child.id)!\n if (!target) {\n console.warn(error('PatchElementsNoTargetsFound'), {\n element: { id: child.id },\n })\n continue\n }\n }\n\n applyToTargets(mode as PatchElementsMode, child, [target])\n }\n } else {\n const targets = document.querySelectorAll(selector)\n if (!targets.length) {\n console.warn(error('PatchElementsNoTargetsFound'), { selector })\n return\n }\n\n applyToTargets(mode as PatchElementsMode, newContent, targets)\n }\n}\n\nconst scripts = new WeakSet()\nfor (const script of document.querySelectorAll('script')) {\n scripts.add(script)\n}\n\nconst execute = (target: Element): void => {\n const elScripts =\n target instanceof HTMLScriptElement\n ? [target]\n : target.querySelectorAll('script')\n for (const old of elScripts) {\n if (!scripts.has(old)) {\n const script = document.createElement('script')\n for (const { name, value } of old.attributes) {\n script.setAttribute(name, value)\n }\n script.text = old.text\n old.replaceWith(script)\n scripts.add(script)\n }\n }\n}\n\nconst applyPatchMode = (\n targets: Iterable,\n element: DocumentFragment | Element,\n action: string,\n) => {\n for (const target of targets) {\n const cloned = element.cloneNode(true) as Element\n execute(cloned)\n // @ts-expect-error\n target[action](cloned)\n }\n}\n\nconst applyToTargets = (\n mode: PatchElementsMode,\n element: DocumentFragment | Element,\n targets: Iterable,\n) => {\n switch (mode) {\n case 'remove':\n for (const target of targets) {\n target.remove()\n }\n break\n case 'outer':\n case 'inner':\n for (const target of targets) {\n morph(target, element.cloneNode(true) as Element, mode)\n execute(target)\n }\n break\n case 'replace':\n applyPatchMode(targets, element, 'replaceWith')\n break\n case 'prepend':\n case 'append':\n case 'before':\n case 'after':\n applyPatchMode(targets, element, mode)\n }\n}\n\nconst ctxIdMap = new Map>()\nconst ctxPersistentIds = new Set()\nconst oldIdTagNameMap = new Map()\nconst duplicateIds = new Set()\nconst ctxPantry = document.createElement('div')\nctxPantry.hidden = true\n\nconst aliasedIgnoreMorph = aliasify('ignore-morph')\nconst aliasedIgnoreMorphAttr = `[${aliasedIgnoreMorph}]`\nconst morph = (\n oldElt: Element | ShadowRoot,\n newContent: DocumentFragment | Element,\n mode: 'outer' | 'inner' = 'outer',\n): void => {\n if (\n (isHTMLOrSVG(oldElt) &&\n isHTMLOrSVG(newContent) &&\n oldElt.hasAttribute(aliasedIgnoreMorph) &&\n newContent.hasAttribute(aliasedIgnoreMorph)) ||\n oldElt.parentElement?.closest(aliasedIgnoreMorphAttr)\n ) {\n return\n }\n\n const normalizedElt = document.createElement('div')\n normalizedElt.append(newContent)\n document.body.insertAdjacentElement('afterend', ctxPantry)\n\n // Computes the set of IDs that persist between the two contents excluding duplicates\n const oldIdElements = oldElt.querySelectorAll('[id]')\n for (const { id, tagName } of oldIdElements) {\n if (oldIdTagNameMap.has(id)) {\n duplicateIds.add(id)\n } else {\n oldIdTagNameMap.set(id, tagName)\n }\n }\n if (oldElt instanceof Element && oldElt.id) {\n if (oldIdTagNameMap.has(oldElt.id)) {\n duplicateIds.add(oldElt.id)\n } else {\n oldIdTagNameMap.set(oldElt.id, oldElt.tagName)\n }\n }\n\n ctxPersistentIds.clear()\n const newIdElements = normalizedElt.querySelectorAll('[id]')\n for (const { id, tagName } of newIdElements) {\n if (ctxPersistentIds.has(id)) {\n duplicateIds.add(id)\n } else if (oldIdTagNameMap.get(id) === tagName) {\n ctxPersistentIds.add(id)\n }\n }\n\n for (const id of duplicateIds) {\n ctxPersistentIds.delete(id)\n }\n\n oldIdTagNameMap.clear()\n duplicateIds.clear()\n ctxIdMap.clear()\n\n const parent = mode === 'outer' ? oldElt.parentElement! : oldElt\n populateIdMapWithTree(parent, oldIdElements)\n populateIdMapWithTree(normalizedElt, newIdElements)\n\n morphChildren(\n parent,\n normalizedElt,\n mode === 'outer' ? oldElt : null,\n oldElt.nextSibling,\n )\n\n ctxPantry.remove()\n}\n\n// This is the core algorithm for matching up children.\n// The idea is to use ID sets to try to match up nodes as faithfully as possible.\n// We greedily match, which allows us to keep the algorithm fast,\n// but by using ID sets, we are able to better match up with content deeper in the DOM.\nconst morphChildren = (\n oldParent: Element | ShadowRoot, // the old content that we are merging the new content into\n newParent: Element, // the parent element of the new content\n insertionPoint: Node | null = null, // the point in the DOM we start morphing at (defaults to first child)\n endPoint: Node | null = null, // the point in the DOM we stop morphing at (defaults to after last child)\n): void => {\n // normalize\n if (\n oldParent instanceof HTMLTemplateElement &&\n newParent instanceof HTMLTemplateElement\n ) {\n // we can pretend the DocumentElement is an Element\n oldParent = oldParent.content as unknown as Element\n newParent = newParent.content as unknown as Element\n }\n insertionPoint ??= oldParent.firstChild\n\n // run through all the new content\n for (const newChild of newParent.childNodes) {\n // once we reach the end of the old parent content skip to the end and insert the rest\n if (insertionPoint && insertionPoint !== endPoint) {\n const bestMatch = findBestMatch(newChild, insertionPoint, endPoint)\n if (bestMatch) {\n // if the node to morph is not at the insertion point then remove/move up to it\n if (bestMatch !== insertionPoint) {\n let cursor: Node | null = insertionPoint\n // Remove nodes between the start and end nodes\n while (cursor && cursor !== bestMatch) {\n const tempNode = cursor\n cursor = cursor.nextSibling\n removeNode(tempNode)\n }\n }\n morphNode(bestMatch, newChild)\n insertionPoint = bestMatch.nextSibling\n continue\n }\n }\n\n // if the matching node is elsewhere in the original content\n if (newChild instanceof Element && ctxPersistentIds.has(newChild.id)) {\n // move it and all its children here and morph, will always be found\n // Search for an element by ID within the document and pantry, and move it using moveBefore.\n const movedChild = document.getElementById(newChild.id) as Element\n\n // Removes an element from its ancestors' ID maps.\n // This is needed when an element is moved from the \"future\" via `moveBeforeId`.\n // Otherwise, its erstwhile ancestors could be mistakenly moved to the pantry rather than being deleted,\n // preventing their removal hooks from being called.\n let current = movedChild\n while ((current = current.parentNode as Element)) {\n const idSet = ctxIdMap.get(current)\n if (idSet) {\n idSet.delete(newChild.id)\n if (!idSet.size) {\n ctxIdMap.delete(current)\n }\n }\n }\n\n moveBefore(oldParent, movedChild, insertionPoint)\n morphNode(movedChild, newChild)\n insertionPoint = movedChild.nextSibling\n continue\n }\n\n // This performs the action of inserting a new node while handling situations where the node contains\n // elements with persistent IDs and possible state info we can still preserve by moving in and then morphing\n if (ctxIdMap.has(newChild)) {\n // node has children with IDs with possible state so create a dummy elt of same type and apply full morph algorithm\n const namespaceURI = (newChild as Element).namespaceURI\n const tagName = (newChild as Element).tagName\n const newEmptyChild =\n namespaceURI && namespaceURI !== 'http://www.w3.org/1999/xhtml'\n ? document.createElementNS(namespaceURI, tagName)\n : document.createElement(tagName)\n oldParent.insertBefore(newEmptyChild, insertionPoint)\n morphNode(newEmptyChild, newChild)\n insertionPoint = newEmptyChild.nextSibling\n } else {\n // optimization: no id state to preserve so we can just insert a clone of the newChild and its descendants\n const newClonedChild = document.importNode(newChild, true) // importNode to not mutate newParent\n oldParent.insertBefore(newClonedChild, insertionPoint)\n insertionPoint = newClonedChild.nextSibling\n }\n }\n\n // remove any remaining old nodes that didn't match up with new content\n while (insertionPoint && insertionPoint !== endPoint) {\n const tempNode = insertionPoint\n insertionPoint = insertionPoint.nextSibling\n removeNode(tempNode)\n }\n}\n\n// Scans forward from the startPoint to the endPoint looking for a match for the node.\n// It looks for an id set match first, then a soft match.\n// We abort soft matching if we find two future soft matches, to reduce churn.\nconst findBestMatch = (\n node: Node,\n startPoint: Node | null,\n endPoint: Node | null,\n): Node | null => {\n let bestMatch: Node | null | undefined = null\n let nextSibling = node.nextSibling\n let siblingSoftMatchCount = 0\n let displaceMatchCount = 0\n\n // Max ID matches we are willing to displace in our search\n const nodeMatchCount = ctxIdMap.get(node)?.size || 0\n\n let cursor = startPoint\n while (cursor && cursor !== endPoint) {\n // soft matching is a prerequisite for id set matching\n if (isSoftMatch(cursor, node)) {\n let isIdSetMatch = false\n const oldSet = ctxIdMap.get(cursor)\n const newSet = ctxIdMap.get(node)\n\n if (newSet && oldSet) {\n for (const id of oldSet) {\n // a potential match is an id in the new and old nodes that\n // has not already been merged into the DOM\n // But the newNode content we call this on has not been\n // merged yet and we don't allow duplicate IDs so it is simple\n if (newSet.has(id)) {\n isIdSetMatch = true\n break\n }\n }\n }\n\n if (isIdSetMatch) {\n return cursor // found an id set match, we're done!\n }\n\n // we haven\u2019t yet saved a soft match fallback\n // the current soft match will hard match something else in the future, leave it\n if (!bestMatch && !ctxIdMap.has(cursor)) {\n // optimization: if node can't id set match, we can just return the soft match immediately\n if (!nodeMatchCount) {\n return cursor\n }\n // save this as the fallback if we get through the loop without finding a hard match\n bestMatch = cursor\n }\n }\n\n // check for IDs we may be displaced when matching\n displaceMatchCount += ctxIdMap.get(cursor)?.size || 0\n if (displaceMatchCount > nodeMatchCount) {\n // if we are going to displace more IDs than the node contains then\n // we do not have a good candidate for an ID match, so return\n break\n }\n\n if (bestMatch === null && nextSibling && isSoftMatch(cursor, nextSibling)) {\n // The next new node has a soft match with this node, so\n // increment the count of future soft matches\n siblingSoftMatchCount++\n nextSibling = nextSibling.nextSibling\n\n // If there are two future soft matches, block soft matching for this node to allow\n // future siblings to soft match. This is to reduce churn in the DOM when an element\n // is prepended.\n if (siblingSoftMatchCount >= 2) {\n bestMatch = undefined\n }\n }\n\n cursor = cursor.nextSibling\n }\n\n return bestMatch || null\n}\n\n// ok to cast: if one is not element, `id` and `tagName` will be null and we'll just compare that.\nconst isSoftMatch = (oldNode: Node, newNode: Node): boolean =>\n oldNode.nodeType === newNode.nodeType &&\n (oldNode as Element).tagName === (newNode as Element).tagName &&\n // If oldElt has an `id` with possible state and it doesn\u2019t match newElt.id then avoid morphing.\n // We'll still match an anonymous node with an IDed newElt, though, because if it got this far,\n // its not persistent, and new nodes can't have any hidden state.\n (!(oldNode as Element).id ||\n (oldNode as Element).id === (newNode as Element).id)\n\n// Gets rid of an unwanted DOM node; strategy depends on nature of its reuse:\n// - Persistent nodes will be moved to the pantry for later reuse\n// - Other nodes will have their hooks called, and then are removed\nconst removeNode = (node: Node): void => {\n // are we going to id set match this later?\n ctxIdMap.has(node)\n ? // skip callbacks and move to pantry\n moveBefore(ctxPantry, node, null)\n : // remove for realsies\n node.parentNode?.removeChild(node)\n}\n\n// Moves an element before another element within the same parent.\n// Uses the proposed `moveBefore` API if available (and working), otherwise falls back to `insertBefore`.\n// This is essentially a forward-compat wrapper.\nconst moveBefore: (parentNode: Node, node: Node, after: Node | null) => void =\n // @ts-expect-error\n removeNode.call.bind(ctxPantry.moveBefore ?? ctxPantry.insertBefore)\n\nconst aliasedPreserveAttr = aliasify('preserve-attr')\n\n// syncs the oldNode to the newNode, copying over all attributes and\n// inner element state from the newNode to the oldNode\nconst morphNode = (\n oldNode: Node, // root node to merge content into\n newNode: Node, // new content to merge\n): Node => {\n const type = newNode.nodeType\n\n // if is an element type, sync the attributes from the\n // new node into the new node\n if (type === 1 /* element type */) {\n const oldElt = oldNode as Element\n const newElt = newNode as Element\n const shouldScopeChildren = oldElt.hasAttribute('data-scope-children')\n if (\n oldElt.hasAttribute(aliasedIgnoreMorph) &&\n newElt.hasAttribute(aliasedIgnoreMorph)\n ) {\n return oldNode\n }\n\n // many bothans died to bring us this information:\n // https://github.com/patrick-steele-idem/morphdom/blob/master/src/specialElHandlers.js\n // https://github.com/choojs/nanomorph/blob/master/lib/morph.js#L113\n if (\n oldElt instanceof HTMLInputElement &&\n newElt instanceof HTMLInputElement &&\n newElt.type !== 'file'\n ) {\n // https://github.com/bigskysoftware/idiomorph/issues/27\n // | old input value | new input value | behaviour |\n // | --------------- | ---------------- | -------------------------------------- |\n // | `null` | `null` | preserve old input value |\n // | some value | the same value | preserve old input value |\n // | some value | `null` | set old input value to `\"\"` |\n // | `null` | some value | set old input value to new input value |\n // | some value | some other value | set old input value to new input value |\n if (newElt.getAttribute('value') !== oldElt.getAttribute('value')) {\n oldElt.value = newElt.getAttribute('value') ?? ''\n }\n } else if (\n oldElt instanceof HTMLTextAreaElement &&\n newElt instanceof HTMLTextAreaElement\n ) {\n if (newElt.value !== oldElt.value) {\n oldElt.value = newElt.value\n }\n if (oldElt.firstChild && oldElt.firstChild.nodeValue !== newElt.value) {\n oldElt.firstChild.nodeValue = newElt.value\n }\n }\n\n const preserveAttrs = (\n (newNode as HTMLElement).getAttribute(aliasedPreserveAttr) ?? ''\n ).split(' ')\n\n for (const { name, value } of newElt.attributes) {\n if (\n oldElt.getAttribute(name) !== value &&\n !preserveAttrs.includes(name)\n ) {\n oldElt.setAttribute(name, value)\n }\n }\n\n for (let i = oldElt.attributes.length - 1; i >= 0; i--) {\n const { name } = oldElt.attributes[i]!\n if (!newElt.hasAttribute(name) && !preserveAttrs.includes(name)) {\n oldElt.removeAttribute(name)\n }\n }\n\n // Preserve the scope marker even if the incoming markup doesn't carry it.\n if (shouldScopeChildren && !oldElt.hasAttribute('data-scope-children')) {\n oldElt.setAttribute('data-scope-children', '')\n }\n\n if (!oldElt.isEqualNode(newElt)) {\n morphChildren(oldElt, newElt)\n }\n\n if (shouldScopeChildren) {\n oldElt.dispatchEvent(\n new CustomEvent('datastar:scope-children', { bubbles: false }),\n )\n }\n }\n\n if (type === 8 /* comment */ || type === 3 /* text */) {\n if (oldNode.nodeValue !== newNode.nodeValue) {\n oldNode.nodeValue = newNode.nodeValue\n }\n }\n\n return oldNode\n}\n\n// A bottom-up algorithm that populates a map of Element -> IdSet.\n// The ID set for a given element is the set of all IDs contained within its subtree.\n// As an optimization, we filter these IDs through the given list of persistent IDs,\n// because we don't need to bother considering IDed elements that won't be in the new content.\nconst populateIdMapWithTree = (\n root: Element | ShadowRoot | null,\n elements: Iterable,\n): void => {\n for (const elt of elements) {\n if (ctxPersistentIds.has(elt.id)) {\n let current: Element | null = elt\n // walk up the parent hierarchy of that element, adding the ID of element to the parent's ID set\n while (current && current !== root) {\n let idSet = ctxIdMap.get(current)\n // if the ID set doesn\u2019t exist, create it and insert it in the map\n if (!idSet) {\n idSet = new Set()\n ctxIdMap.set(current, idSet)\n }\n idSet.add(elt.id)\n current = current.parentElement\n }\n }\n }\n}\n", "// Icon: material-symbols:settings-input-antenna\n// Slug: Patches signals.\n// Description: Patches signals.\n\nimport { watcher } from '@engine'\nimport { mergePatch } from '@engine/signals'\nimport { jsStrToObject } from '@utils/text'\n\nwatcher({\n name: 'datastar-patch-signals',\n apply({ error }, { signals, onlyIfMissing }) {\n if (signals) {\n const ifMissing = onlyIfMissing?.trim() === 'true'\n mergePatch(jsStrToObject(signals), { ifMissing })\n } else {\n throw error('PatchSignalsExpectedSignals')\n }\n },\n})\n"], + "mappings": "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", + "names": ["lol", "DSP", "DSS", "DATASTAR_FETCH_EVENT", "DATASTAR_SIGNAL_PATCH_EVENT", "hasOwn", "isPojo", "obj", "isEmpty", "prop", "hasOwn", "updateLeaves", "fn", "key", "val", "pathToObj", "paths", "result", "path", "value", "keys", "lastKey", "acc", "currentPatch", "queuedEffects", "batchDepth", "notifyIndex", "queuedEffectsLength", "prevSub", "activeSub", "version", "beginBatch", "endBatch", "flush", "dispatch", "startPeeking", "sub", "stopPeeking", "signal", "initialValue", "signalOper", "value_", "flags_", "computedSymbol", "computed", "getter", "c", "computedOper", "effect", "fn", "e", "fn_", "link", "effectOper", "run", "update", "updateComputed", "updateSignal", "startTracking", "oldValue", "endTracking", "s", "value", "notify", "flags", "subs", "subs_", "sub_", "checkDirty", "deps_", "dep", "dep_", "depFlags", "nextDep_", "propagate", "currentValue", "shallowPropagate", "unlink", "prevDep", "depsTail_", "nextDep", "version_", "subsTail_", "newLink", "prevDep_", "prevSub_", "nextSub_", "toRemove", "next", "stack", "top", "isValidLink", "subSubs", "nextSub", "prev_", "checkDepth", "dirty", "firstSub", "hasMultipleSubs", "checkLink", "getPath", "path", "result", "root", "split", "hasOwn", "deep", "prefix", "isArr", "isPojo", "deepObj", "key", "keys", "_", "prop", "newValue", "diff", "patch", "i", "pathStr", "nextVal", "detail", "pathToObj", "DATASTAR_SIGNAL_PATCH_EVENT", "mergePatch", "ifMissing", "mergeInner", "mergePaths", "paths", "options", "target", "targetParent", "toRegExp", "val", "filtered", "include", "exclude", "obj", "includeRe", "excludeRe", "node", "isHTMLOrSVG", "el", "kebab", "str", "snake", "str", "kebab", "jsStrToObject", "raw", "caseFns", "str", "x", "modifyCasing", "mods", "defaultCase", "c", "aliasify", "name", "url", "error", "ctx", "reason", "metadata", "e", "r", "snake", "q", "c", "actionPlugins", "attributePlugins", "watcherPlugins", "actions", "_", "prop", "removals", "queuedAttributes", "queuedAttributeNames", "observedRoots", "attribute", "plugin", "apply", "action", "DATASTAR_FETCH_EVENT", "evt", "watcher", "cleanupEls", "els", "el", "elCleanups", "attrCleanups", "cleanup", "aliasedIgnore", "aliasify", "aliasedIgnoreAttr", "shouldIgnore", "applyEls", "onlyNew", "key", "applyAttributePlugin", "observe", "mutations", "target", "type", "attributeName", "addedNodes", "removedNodes", "node", "isHTMLOrSVG", "value", "mutationObserver", "parseAttributeKey", "rawKey", "namePart", "rawModifiers", "pluginName", "mods", "rawMod", "label", "mod", "apply", "root", "observeRoot", "isHTMLOrSVG", "applyEls", "mutationObserver", "observedRoots", "applyAttributePlugin", "el", "attrKey", "value", "onlyNew", "rawKey", "pluginName", "key", "mods", "parseAttributeKey", "plugin", "attributePlugins", "queuedAttributeNames", "ctx", "error", "actionPlugins", "keyReq", "valueReq", "keyProvided", "valueProvided", "cleanups", "cachedRx", "args", "genRx", "cleanup", "elCleanups", "removals", "attrCleanups", "oldCleanup", "returnsValue", "argNames", "expr", "statementRe", "statements", "lastIdx", "last", "escaped", "escapeRe", "DSP", "DSS", "counter", "match", "k", "v", "_", "signalName", "acc", "part", "fn", "action", "name", "evt", "err", "actions", "e", "action", "_", "fn", "startPeeking", "stopPeeking", "action", "_", "value", "filter", "startPeeking", "masked", "filtered", "updateLeaves", "mergePatch", "stopPeeking", "action", "_", "filter", "startPeeking", "masked", "filtered", "updateLeaves", "oldValue", "mergePatch", "stopPeeking", "createHttpMethod", "name", "method", "openWhenHiddenDefault", "action", "el", "evt", "error", "cleanups", "url", "selector", "userHeaders", "contentType", "include", "exclude", "openWhenHidden", "payload", "requestCancellation", "retry", "retryInterval", "retryScaler", "retryMaxWaitMs", "retryMaxCount", "controller", "cleanupFn", "initialHeaders", "headers", "req", "response", "dispatchFetch", "ERROR", "type", "argsRawLines", "line", "i", "k", "v", "argsRaw", "isWrongContent", "RETRYING", "urlInstance", "queryParams", "startPeeking", "filtered", "stopPeeking", "body", "formEl", "formData", "submitter", "preventDefault", "multipart", "formParams", "key", "value", "STARTED", "fetchEventSource", "e", "FINISHED", "RETRIES_FAILED", "DATASTAR_FETCH_EVENT", "err", "getBytes", "stream", "onChunk", "reader", "result", "getLines", "onLine", "buffer", "position", "fieldLength", "discardTrailingNewline", "arr", "concat", "bufLength", "lineStart", "lineEnd", "getMessages", "onId", "onRetry", "onMessage", "message", "newMessage", "decoder", "field", "valueOffset", "a", "b", "res", "input", "inputSignal", "inputHeaders", "inputOnOpen", "onmessage", "onclose", "onerror", "inputFetch", "responseOverrides", "rest", "resolve", "reject", "curRequestController", "onVisibilityChange", "create", "retryTimer", "dispose", "fetch", "onopen", "retries", "baseRetryInterval", "curRequestSignal", "dispatchNonSSE", "dispatchType", "argNames", "n", "kebab", "o", "status", "isNoContentStatus", "isRedirectStatus", "isErrorStatus", "ct", "script", "scriptAttributesHeader", "id", "interval", "innerErr", "attribute", "el", "key", "rx", "syncAttr", "val", "update", "observer", "obj", "attributeFilter", "cleanup", "effect", "dataURIRegex", "empty", "aliasedBind", "aliasify", "attribute", "el", "key", "mods", "value", "error", "signalName", "modifyCasing", "get", "type", "set", "syncSignal", "files", "signalFiles", "f", "resolve", "reader", "match", "mergePaths", "typeMap", "option", "initialValue", "getPath", "path", "signalNameKebab", "inputs", "paths", "i", "input", "signalValue", "cleanup", "effect", "attribute", "key", "el", "mods", "rx", "modifyCasing", "classes", "callback", "observer", "k", "classNames", "cn", "name", "cleanup", "effect", "attribute", "key", "mods", "rx", "error", "mergePaths", "modifyCasing", "computed", "patch", "updateLeaves", "old", "mergePatch", "attribute", "rx", "effect", "attribute", "el", "key", "mods", "value", "signalName", "modifyCasing", "mergePaths", "watcher", "event", "type", "elt", "STARTED", "FINISHED", "DATASTAR_FETCH_EVENT", "tagToMs", "args", "arg", "tagHas", "tags", "tag", "defaultValue", "delay", "callback", "wait", "args", "throttle", "leading", "trailing", "debounce", "lastArgs", "timer", "modifyTiming", "mods", "delayArgs", "tagToMs", "debounceArgs", "tagHas", "throttleArgs", "supportsViewTransitions", "modifyViewTransition", "callback", "mods", "cb", "args", "attribute", "rx", "mods", "callback", "beginBatch", "endBatch", "modifyViewTransition", "wait", "delayArgs", "tagToMs", "delay", "attribute", "el", "value", "mods", "spaces", "filters", "jsStrToObject", "callback", "observer", "filtered", "cleanup", "effect", "attribute", "el", "key", "mods", "rx", "target", "callback", "evt", "beginBatch", "endBatch", "modifyViewTransition", "modifyTiming", "evtListOpts", "cb", "eventName", "modifyCasing", "DATASTAR_FETCH_EVENT", "DATASTAR_SIGNAL_PATCH_EVENT", "clamp", "value", "min", "max", "once", "attribute", "el", "mods", "rx", "callback", "beginBatch", "endBatch", "modifyViewTransition", "modifyTiming", "options", "clamp", "exit", "observer", "entries", "entry", "attribute", "mods", "rx", "callback", "beginBatch", "endBatch", "modifyViewTransition", "duration", "durationArgs", "tagToMs", "tagHas", "intervalId", "attribute", "el", "key", "mods", "rx", "error", "filterAttr", "aliasify", "filtersRaw", "filters", "jsStrToObject", "running", "callback", "modifyTiming", "evt", "watched", "filtered", "isEmpty", "beginBatch", "endBatch", "DATASTAR_SIGNAL_PATCH_EVENT", "attribute", "el", "key", "mods", "value", "signalName", "modifyCasing", "mergePaths", "NONE", "DISPLAY", "attribute", "el", "rx", "update", "observer", "cleanup", "effect", "attribute", "key", "mods", "rx", "ifMissing", "modifyCasing", "mergePaths", "patch", "mergePatch", "attribute", "key", "el", "rx", "style", "initialStyles", "apply", "prop", "value", "initial", "update", "observer", "styles", "kebab", "cleanup", "effect", "attribute", "el", "rx", "update", "observer", "cleanup", "effect", "isValidType", "arr", "value", "PATCH_MODES", "NAMESPACES", "watcher", "ctx", "selector", "mode", "namespace", "useViewTransition", "elements", "args2", "supportsViewTransitions", "onPatchElements", "error", "elementsWithSvgsRemoved", "hasHtml", "hasHead", "hasBody", "wrapperTag", "wrappedEls", "newDocument", "newContent", "wrapperEl", "child", "target", "applyToTargets", "targets", "scripts", "script", "execute", "elScripts", "old", "name", "applyPatchMode", "element", "action", "cloned", "morph", "ctxIdMap", "ctxPersistentIds", "oldIdTagNameMap", "duplicateIds", "ctxPantry", "aliasedIgnoreMorph", "aliasify", "aliasedIgnoreMorphAttr", "oldElt", "isHTMLOrSVG", "normalizedElt", "oldIdElements", "id", "tagName", "newIdElements", "parent", "populateIdMapWithTree", "morphChildren", "oldParent", "newParent", "insertionPoint", "endPoint", "newChild", "bestMatch", "findBestMatch", "cursor", "tempNode", "removeNode", "morphNode", "movedChild", "current", "idSet", "moveBefore", "namespaceURI", "newEmptyChild", "newClonedChild", "node", "startPoint", "nextSibling", "siblingSoftMatchCount", "displaceMatchCount", "nodeMatchCount", "isSoftMatch", "isIdSetMatch", "oldSet", "newSet", "oldNode", "newNode", "aliasedPreserveAttr", "type", "newElt", "shouldScopeChildren", "preserveAttrs", "i", "root", "elt", "watcher", "error", "signals", "onlyIfMissing", "ifMissing", "mergePatch", "jsStrToObject"] +} diff --git a/cmd/downloader/main.go b/cmd/downloader/main.go new file mode 100644 index 0000000..44c1547 --- /dev/null +++ b/cmd/downloader/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "path/filepath" + "sync" +) + +// Asset directories, relative to project root. +const ( + jsDir = "assets/js" + cssDir = "assets/css" +) + +// files maps download URLs to local destination paths. +var files = map[string]string{ + "https://raw.githubusercontent.com/starfederation/datastar/main/bundles/datastar.js": jsDir + "/datastar.js", + "https://raw.githubusercontent.com/starfederation/datastar/main/bundles/datastar.js.map": jsDir + "/datastar.js.map", + "https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.mjs": cssDir + "/daisyui.mjs", + "https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.mjs": cssDir + "/daisyui-theme.mjs", +} + +func main() { + if err := run(); err != nil { + slog.Error("failure", "error", err) + os.Exit(1) + } +} + +func run() error { + dirs := []string{jsDir, cssDir} + + for _, dir := range dirs { + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("create directory %s: %w", dir, err) + } + } + + return download(files) +} + +func download(files map[string]string) error { + var wg sync.WaitGroup + errCh := make(chan error, len(files)) + + for url, dest := range files { + wg.Go(func() { + base := filepath.Base(dest) + slog.Info("downloading...", "file", base, "url", url) + if err := downloadFile(url, dest); err != nil { + errCh <- fmt.Errorf("download %s: %w", base, err) + } else { + slog.Info("finished", "file", base) + } + }) + } + + wg.Wait() + close(errCh) + + var errs []error + for err := range errCh { + errs = append(errs, err) + } + + return errors.Join(errs...) +} + +func downloadFile(url, dest string) error { + resp, err := http.Get(url) //nolint:gosec,noctx // static URLs, simple tool + if err != nil { + return fmt.Errorf("GET %s: %w", url, err) + } + defer resp.Body.Close() //nolint:errcheck + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("GET %s: status %s", url, resp.Status) + } + + out, err := os.Create(dest) //nolint:gosec // paths are hardcoded constants + if err != nil { + return fmt.Errorf("create %s: %w", dest, err) + } + + if _, err := io.Copy(out, resp.Body); err != nil { + out.Close() //nolint:errcheck + return fmt.Errorf("write %s: %w", dest, err) + } + + if err := out.Close(); err != nil { + return fmt.Errorf("close %s: %w", dest, err) + } + + return nil +}