diff --git a/.air.hold.toml b/.air.hold.toml index a5af303..9ffeeeb 100644 --- a/.air.hold.toml +++ b/.air.hold.toml @@ -5,9 +5,9 @@ tmp_dir = "tmp" cmd = "go build -buildvcs=false -o ./tmp/atcr-hold ./cmd/hold" entrypoint = ["./tmp/atcr-hold"] include_ext = ["go"] -exclude_dir = ["bin", "tmp", "vendor", "deploy", "docs", ".git", "dist", "pkg/appview"] -exclude_regex = ["_test\\.go$"] -delay = 1000 +exclude_dir = ["bin", "tmp", "vendor", "deploy", "docs", ".git", "dist", "pkg/appview", "node_modules"] +exclude_regex = ["_test\\.go$", "cbor_gen\\.go$", "\\.min\\.js$"] +delay = 3000 stop_on_error = true send_interrupt = true kill_delay = 500 diff --git a/.air.toml b/.air.toml index 6236bf1..3a80573 100644 --- a/.air.toml +++ b/.air.toml @@ -3,16 +3,16 @@ tmp_dir = "tmp" [build] # Pre-build: generate assets if missing (each string is a shell command) -pre_cmd = ["[ -f pkg/appview/static/js/htmx.min.js ] || go generate ./..."] +pre_cmd = ["go generate ./..."] cmd = "go build -buildvcs=false -o ./tmp/atcr-appview ./cmd/appview" entrypoint = ["./tmp/atcr-appview", "serve"] include_ext = ["go", "html", "css", "js"] -exclude_dir = ["bin", "tmp", "vendor", "deploy", "docs", ".git", "dist"] -exclude_regex = ["_test\\.go$"] -delay = 1000 +exclude_dir = ["bin", "tmp", "vendor", "deploy", "docs", ".git", "dist", "node_modules"] +exclude_regex = ["_test\\.go$", "cbor_gen\\.go$", "\\.min\\.js$"] +delay = 3000 stop_on_error = true send_interrupt = true -kill_delay = 500 +kill_delay = 2000 [log] time = false diff --git a/.claudeignore b/.claudeignore new file mode 100644 index 0000000..b97430c --- /dev/null +++ b/.claudeignore @@ -0,0 +1,2 @@ +# Generated files +pkg/appview/public/css/style.css diff --git a/.gitignore b/.gitignore index 01093c3..4647a5f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,8 @@ quotas.yaml # Generated assets (run go generate to rebuild) pkg/appview/licenses/spdx-licenses.json -pkg/appview/static/js/htmx.min.js -pkg/appview/static/js/lucide.min.js +pkg/appview/public/js/htmx.min.js +pkg/appview/public/js/lucide.min.js # IDE .zed/ @@ -31,3 +31,4 @@ pkg/appview/static/js/lucide.min.js # OS .DS_Store Thumbs.db +node_modules diff --git a/CLAUDE.md b/CLAUDE.md index 5e9647a..5e1dfc5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -455,9 +455,9 @@ The AppView includes a web interface for browsing the registry: - `settings.go` - User settings management - `api.go` - JSON API endpoints -**Static Assets** (`pkg/appview/static/`, `pkg/appview/templates/`): +**Static Assets** (`pkg/appview/public/`, `pkg/appview/templates/`): - Templates use Go html/template -- JavaScript in `static/js/app.js` +- JavaScript in `public/js/app.js` - Minimal CSS for clean UI #### Hold Service (`cmd/hold/`) diff --git a/Dockerfile.dev b/Dockerfile.dev index 96bab16..be6d0da 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -9,7 +9,7 @@ ENV DEBIAN_FRONTEND=noninteractive ENV AIR_CONFIG=${AIR_CONFIG} RUN apt-get update && \ - apt-get install -y --no-install-recommends sqlite3 libsqlite3-dev curl && \ + apt-get install -y --no-install-recommends sqlite3 libsqlite3-dev curl nodejs npm && \ rm -rf /var/lib/apt/lists/* && \ go install github.com/air-verse/air@latest diff --git a/Makefile b/Makefile index 963139f..8f53742 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,8 @@ all: generate build ## Generate assets and build all binaries (default) # Generated asset files GENERATED_ASSETS = \ - pkg/appview/static/js/htmx.min.js \ - pkg/appview/static/js/lucide.min.js \ + pkg/appview/public/js/htmx.min.js \ + pkg/appview/public/js/lucide.min.js \ pkg/appview/licenses/spdx-licenses.json generate: $(GENERATED_ASSETS) ## Run go generate to download vendor assets @@ -113,7 +113,7 @@ develop-down: ## Stop docker-compose services clean: ## Remove built binaries and generated assets @echo "→ Cleaning build artifacts..." rm -rf bin/ - rm -f pkg/appview/static/js/htmx.min.js - rm -f pkg/appview/static/js/lucide.min.js + rm -f pkg/appview/public/js/htmx.min.js + rm -f pkg/appview/public/js/lucide.min.js rm -f pkg/appview/licenses/spdx-licenses.json @echo "✓ Clean complete" diff --git a/README.md b/README.md index a2093b7..dae6d94 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ pkg/ │ ├── jetstream/ # ATProto Jetstream consumer │ ├── middleware/ # Auth & registry middleware │ ├── storage/ # Storage routing (hold cache, blob proxy, repository) -│ ├── static/ # Static assets (JS, CSS, install scripts) +│ ├── public/ # Static assets (JS, CSS, install scripts) │ └── templates/ # HTML templates ├── atproto/ # ATProto client, records, manifest/tag stores ├── auth/ diff --git a/appview b/appview new file mode 100755 index 0000000..3d8a18f Binary files /dev/null and b/appview differ diff --git a/cmd/appview/serve.go b/cmd/appview/serve.go index 36c8b89..564c0f3 100644 --- a/cmd/appview/serve.go +++ b/cmd/appview/serve.go @@ -347,8 +347,8 @@ func serveRegistry(cmd *cobra.Command, args []string) error { // Mount static files if UI is enabled if uiSessionStore != nil && uiTemplates != nil { // Register dynamic routes for root-level files (favicons, manifests, etc.) - staticHandler := appview.StaticHandler() - rootFiles, err := appview.StaticRootFiles() + publicHandler := appview.PublicHandler() + rootFiles, err := appview.PublicRootFiles() if err != nil { slog.Warn("Failed to scan static root files", "error", err) } else { @@ -358,16 +358,16 @@ func serveRegistry(cmd *cobra.Command, args []string) error { mainRouter.Get("/"+file, func(w http.ResponseWriter, r *http.Request) { // Serve the specific file from static root r.URL.Path = "/" + file - staticHandler.ServeHTTP(w, r) + publicHandler.ServeHTTP(w, r) }) } slog.Info("Registered dynamic root file routes", "count", len(rootFiles), "files", rootFiles) } // Mount subdirectory routes with clean paths - mainRouter.Handle("/css/*", http.StripPrefix("/css/", appview.StaticSubdir("css"))) - mainRouter.Handle("/js/*", http.StripPrefix("/js/", appview.StaticSubdir("js"))) - mainRouter.Handle("/static/*", http.StripPrefix("/static/", appview.StaticSubdir("static"))) + mainRouter.Handle("/css/*", http.StripPrefix("/css/", appview.PublicSubdir("css"))) + mainRouter.Handle("/js/*", http.StripPrefix("/js/", appview.PublicSubdir("js"))) + mainRouter.Handle("/static/*", http.StripPrefix("/static/", appview.PublicSubdir("static"))) slog.Info("UI enabled", "home", "/", "settings", "/settings") } diff --git a/docs/ADMIN_PANEL.md b/docs/ADMIN_PANEL.md index 040564f..7018326 100644 --- a/docs/ADMIN_PANEL.md +++ b/docs/ADMIN_PANEL.md @@ -135,7 +135,7 @@ pkg/hold/admin/ │ ├── crew_row.html # Single crew row (for HTMX updates) │ ├── usage_stats.html # Usage stats partial │ └── top_users.html # Top users table partial -└── static/ +└── public/ ├── css/ │ └── admin.css # Admin-specific styles └── js/ @@ -406,7 +406,7 @@ This keeps all hold data together while maintaining separation between the carst | `/admin/auth/oauth/authorize` | GET | Public | OAuth authorize | Start OAuth flow | | `/admin/auth/oauth/callback` | GET | Public | `CallbackHandler` | OAuth callback | | `/admin/auth/logout` | GET | Owner | `LogoutHandler` | Logout and clear session | -| `/admin/static/*` | GET | Public | Static files | CSS, JS assets | +| `/admin/public/*` | GET | Public | Static files | CSS, JS assets | ### Route Registration @@ -418,7 +418,7 @@ func (ui *AdminUI) RegisterRoutes(r chi.Router) { r.Get("/admin/auth/oauth/callback", ui.handleCallback) // Static files (public) - r.Handle("/admin/static/*", http.StripPrefix("/admin/static/", ui.staticHandler())) + r.Handle("/admin/public/*", http.StripPrefix("/admin/public/", ui.staticHandler())) // Protected routes (require owner) r.Group(func(r chi.Router) { @@ -899,7 +899,7 @@ func (ui *AdminUI) handleStatsAPI(w http.ResponseWriter, r *http.Request) { ```html {{ define "head" }} - + {{ end }} diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index f4a7ea7..bd428d1 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -65,7 +65,7 @@ │ │ ui.go checks DEV_MODE: │ │ │ │ if DEV_MODE: │ │ │ │ templatesFS = os.DirFS("...") │ │ -│ │ staticFS = os.DirFS("...") │ │ +│ │ publicFS = os.DirFS("...") │ │ │ │ else: │ │ │ │ use embed.FS (production) │ │ │ │ │ │ @@ -78,7 +78,7 @@ #### Scenario 1: Edit CSS/JS/Templates ``` -1. Edit pkg/appview/static/css/style.css in VSCode +1. Edit pkg/appview/public/css/style.css in VSCode 2. Save file 3. Change appears in container via volume mount (instant) 4. App uses os.DirFS → reads new file from disk (instant) @@ -313,23 +313,23 @@ import ( var embeddedTemplatesFS embed.FS //go:embed static -var embeddedStaticFS embed.FS +var embeddedpublicFS embed.FS // Actual filesystems used at runtime (conditional) var templatesFS fs.FS -var staticFS fs.FS +var publicFS fs.FS func init() { // Development mode: read from filesystem for instant updates if os.Getenv("ATCR_DEV_MODE") == "true" { log.Println("🔧 DEV MODE: Using filesystem for templates and static assets") templatesFS = os.DirFS("pkg/appview/templates") - staticFS = os.DirFS("pkg/appview/static") + publicFS = os.DirFS("pkg/appview/static") } else { // Production mode: use embedded assets log.Println("📦 PRODUCTION MODE: Using embedded assets") templatesFS = embeddedTemplatesFS - staticFS = embeddedStaticFS + publicFS = embeddedpublicFS } } @@ -344,7 +344,7 @@ func Templates() *template.Template { // StaticHandler returns a handler for static files func StaticHandler() http.Handler { - sub, err := fs.Sub(staticFS, "static") + sub, err := fs.Sub(publicFS, "static") if err != nil { log.Fatalf("Failed to create static sub-filesystem: %v", err) } @@ -442,8 +442,8 @@ atcr-appview | running... ```bash # Edit any template, CSS, or JS file vim pkg/appview/templates/pages/home.html -vim pkg/appview/static/css/style.css -vim pkg/appview/static/js/app.js +vim pkg/appview/public/css/style.css +vim pkg/appview/public/js/app.js # Save file → changes appear instantly # Just refresh browser (Cmd+R / Ctrl+R) diff --git a/docs/MINIFY.md b/docs/MINIFY.md index 0ddfa98..dba9f9a 100644 --- a/docs/MINIFY.md +++ b/docs/MINIFY.md @@ -4,7 +4,7 @@ ATCR embeds static assets (CSS, JavaScript) directly into the binary using Go's `embed` directive. Currently: -- **CSS Size:** 40KB (`pkg/appview/static/css/style.css`, 2,210 lines) +- **CSS Size:** 40KB (`pkg/appview/public/css/style.css`, 2,210 lines) - **Embedded:** All static files compiled into binary at build time - **No Minification:** Source files embedded as-is @@ -37,7 +37,7 @@ require github.com/tdewolff/minify/v2 v2.20.37 ### Step 2: Create Minification Script -Create `pkg/appview/static/minify_assets.go`: +Create `pkg/appview/public/minify_assets.go`: ```go //go:build ignore @@ -68,16 +68,16 @@ func main() { // Minify CSS if err := minifyFile(m, "text/css", - filepath.Join(dir, "pkg/appview/static/css/style.css"), - filepath.Join(dir, "pkg/appview/static/css/style.min.css"), + filepath.Join(dir, "pkg/appview/public/css/style.css"), + filepath.Join(dir, "pkg/appview/public/css/style.min.css"), ); err != nil { log.Fatalf("Failed to minify CSS: %v", err) } // Minify JavaScript if err := minifyFile(m, "text/javascript", - filepath.Join(dir, "pkg/appview/static/js/app.js"), - filepath.Join(dir, "pkg/appview/static/js/app.min.js"), + filepath.Join(dir, "pkg/appview/public/js/app.js"), + filepath.Join(dir, "pkg/appview/public/js/app.min.js"), ); err != nil { log.Fatalf("Failed to minify JS: %v", err) } @@ -120,10 +120,10 @@ func minifyFile(m *minify.M, mediatype, src, dst string) error { Add to `pkg/appview/ui.go` (before the `//go:embed` directive): ```go -//go:generate go run ./static/minify_assets.go +//go:generate go run ./public/minify_assets.go -//go:embed static -var staticFS embed.FS +//go:embed public +var publicFS embed.FS ``` ### Step 4: Update HTML Templates @@ -132,14 +132,14 @@ Update all template files to reference minified assets: **Before:** ```html - - + + ``` **After:** ```html - - + + ``` **Files to update:** @@ -167,8 +167,8 @@ Add minified files to `.gitignore` since they're generated: ``` # Generated minified assets -pkg/appview/static/css/*.min.css -pkg/appview/static/js/*.min.js +pkg/appview/public/css/*.min.css +pkg/appview/public/js/*.min.js ``` **Alternative:** Commit minified files if you want reproducible builds without running `go generate`. @@ -194,23 +194,23 @@ Use build tags to serve unminified assets in development: //go:build !production //go:embed static -var staticFS embed.FS +var publicFS embed.FS -func StylePath() string { return "/static/css/style.css" } -func ScriptPath() string { return "/static/js/app.js" } +func StylePath() string { return "/public/css/style.css" } +func ScriptPath() string { return "/public/js/app.js" } ``` **pkg/appview/ui_production.go** (production): ```go //go:build production -//go:generate go run ./static/minify_assets.go +//go:generate go run ./public/minify_assets.go //go:embed static -var staticFS embed.FS +var publicFS embed.FS -func StylePath() string { return "/static/css/style.min.css" } -func ScriptPath() string { return "/static/js/app.min.js" } +func StylePath() string { return "/public/css/style.min.css" } +func ScriptPath() string { return "/public/js/app.min.js" } ``` **Usage:** @@ -230,8 +230,8 @@ go build -tags production ./cmd/appview Use Node.js-based minifiers via `go:generate`: ```go -//go:generate sh -c "npx cssnano static/css/style.css static/css/style.min.css" -//go:generate sh -c "npx esbuild static/js/app.js --minify --outfile=static/js/app.min.js" +//go:generate sh -c "npx cssnano public/css/style.css public/css/style.min.css" +//go:generate sh -c "npx esbuild public/js/app.js --minify --outfile=public/js/app.min.js" ``` **Pros:** @@ -251,7 +251,7 @@ Compress assets at runtime (complementary to minification): import "github.com/NYTimes/gziphandler" // Wrap static handler -mux.Handle("/static/", gziphandler.GzipHandler(appview.StaticHandler())) +mux.Handle("/public/", gziphandler.GzipHandler(appview.StaticHandler())) ``` **Pros:** @@ -316,8 +316,8 @@ func BrotliHandler(h http.Handler) http.Handler { ### Development Workflow 1. **Edit source files:** - - Modify `pkg/appview/static/css/style.css` - - Modify `pkg/appview/static/js/app.js` + - Modify `pkg/appview/public/css/style.css` + - Modify `pkg/appview/public/js/app.js` 2. **Test locally:** ```bash diff --git a/docs/REBRAND.md b/docs/REBRAND.md new file mode 100644 index 0000000..763a472 --- /dev/null +++ b/docs/REBRAND.md @@ -0,0 +1,558 @@ +# Website Visual Improvement Plan + +## Goal +Create a fun, personality-driven container registry that embraces its nautical theme while being clearly functional. Think GitHub's Octocat or DigitalOcean's Sammy - playful but professional. + +## Brand Identity (from seahorse logo) +- **Primary Teal**: #4ECDC4 (body color) - the "ocean" feel +- **Dark Teal**: #2E8B8B (mane/fins) - depth and contrast +- **Mint Background**: #C8F0E7 - light, airy, underwater +- **Coral Accent**: #FF6B6B (eye) - warmth, CTAs, highlights +- **Nautical theme to embrace:** + - "Ship" containers (not just push) + - "Holds" for storage (like a ship's cargo hold) + - "Sailors" are users, "Captains" own holds + - Seahorse mascot as the friendly guide + +## Design Direction: Fun but Functional +- Softer, more rounded corners +- Playful color combinations (teal + coral) +- Mascot appearances in empty states, loading, errors +- Ocean-inspired subtle backgrounds (gradients, waves) +- Friendly copy and microcopy throughout +- Still clearly a container registry with all the technical info + +## Current State +- Pure CSS with custom properties for theming +- Basic card designs for repositories +- Simple hero section with terminal mockup +- Existing badges: Helm charts, multi-arch, attestations +- Existing stats: stars, pull counts + +## Layout Wireframes + +### Current Homepage Layout +``` +┌─────────────────────────────────────────────────────────────────┐ +│ [Logo] [Search] [Theme] [User] │ Navbar +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ship containers on the open web. │ Hero +│ ┌─────────────────────────┐ │ +│ │ $ docker login atcr.io │ │ +│ └─────────────────────────┘ │ +│ [Get Started] [Learn More] │ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Benefits +│ │ Docker │ │ Your Data │ │ Discover │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Featured │ +│ ┌─────────────────────────────────────────────────────────────┐│ +│ │ [icon] user/repo ★ 12 ↓ 340 ││ WIDE cards +│ │ Description text here... ││ (current) +│ └─────────────────────────────────────────────────────────────┘│ +│ ┌─────────────────────────────────────────────────────────────┐│ +│ │ [icon] user/repo2 ★ 5 ↓ 120 ││ +│ └─────────────────────────────────────────────────────────────┘│ +│ │ +│ What's New │ +│ (similar wide cards) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Proposed Layout: Tile Grid +``` +┌─────────────────────────────────────────────────────────────────┐ +│ [Logo] [Search] [Theme] [User] │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ship containers on the open web. │ +│ ┌─────────────────────────┐ │ +│ │ $ docker login atcr.io │ │ +│ └─────────────────────────┘ │ +│ [Get Started] [Learn More] │ +│ │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ Docker │ │ Your Data │ │ Discover │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Featured [View All] │ +│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ +│ │ [icon] │ │ [icon] │ │ [icon] ││ 3 columns +│ │ user/repo │ │ user/repo2 │ │ user/repo3 ││ ~300px each +│ │ Description... │ │ Description... │ │ Description... ││ +│ │ ────────────────││ │ ────────────────││ │ ────────────────│││ +│ │ ★ 12 ↓ 340 │ │ ★ 5 ↓ 120 │ │ ★ 8 ↓ 89 ││ +│ └──────────────────┘ └──────────────────┘ └──────────────────┘│ +│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ +│ │ ... │ │ ... │ │ ... ││ +│ └──────────────────┘ └──────────────────┘ └──────────────────┘│ +│ │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ What's New │ +│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│ +│ │ ... │ │ ... │ │ ... ││ Same tile +│ └──────────────────┘ └──────────────────┘ └──────────────────┘│ layout +└─────────────────────────────────────────────────────────────────┘ +``` + +### Unified Tile Card (Same for Featured & What's New) +``` +┌─────────────────────────────┐ +│ ┌────┐ user/repo [Helm] │ Icon + name + type badge +│ │icon│ :latest │ Tag (if applicable) +│ └────┘ │ +│ │ +│ Description text that │ Description (2-3 lines max) +│ wraps nicely here... │ +│ │ +│ sha256:abcdef12 │ Digest (truncated) +│ ───────────────────────────│ Divider +│ ★ 12 ↓ 340 1 day ago │ Stats + timestamp +└─────────────────────────────┘ + +Card anatomy: +┌─────────────────────────────┐ +│ HEADER │ - Icon (48x48) +│ - icon + name + badge │ - user/repo +│ - tag (optional) │ - :tag or :latest +├─────────────────────────────┤ +│ BODY │ - Description (clamp 2-3 lines) +│ - description │ - sha256:abc... (monospace) +│ - digest │ +├─────────────────────────────┤ +│ FOOTER │ - ★ star count +│ - stats + time │ - ↓ pull count +│ │ - "2 hours ago" +└─────────────────────────────┘ +``` + +### Both Sections Use Same Card (Different Sort) +``` +Featured (by stars/curated): What's New (by last_push): +┌─────────────────────────┐ ┌─────────────────────────┐ +│ user/repo │ │ user/repo │ +│ :latest │ │ :v1.2.3 │ ← latest tag +│ Description... │ │ Description... │ +│ │ │ │ +│ sha256:abc123 │ │ sha256:def456 │ ← latest digest +│ ───────────────────────│ │ ───────────────────────│ +│ ★ 12 ↓ 340 1 day ago │ │ ★ 5 ↓ 89 2 hrs ago │ ← last_push time +└─────────────────────────┘ └─────────────────────────┘ + +Same card component, different data source: +- Featured: GetFeaturedRepos() (curated or by stars) +- What's New: GetRecentlyUpdatedRepos() (ORDER BY last_push DESC) +``` + +### Card Dimensions Comparison +``` +Current: █████████████████████████████████████████ (~800px+ wide) +Proposed: ████████████ ████████████ ████████████ (~280-320px each) + Card 1 Card 2 Card 3 +``` + +### Mobile Responsive Behavior +``` +Desktop (>1024px): [Card] [Card] [Card] 3 columns +Tablet (768-1024px): [Card] [Card] 2 columns +Mobile (<768px): [Card] 1 column (full width) +``` + +### Playful Elements +``` +Empty State (no repos): +┌─────────────────────────────────────────┐ +│ │ +│ 🐴 (seahorse) │ +│ "Nothing here yet!" │ +│ │ +│ Ship your first container to get │ +│ started on your voyage. │ +│ │ +│ [Start Shipping] │ +└─────────────────────────────────────────┘ + +Error/404: +┌─────────────────────────────────────────┐ +│ │ +│ 🐴 (confused seahorse) │ +│ "Lost at sea!" │ +│ │ +│ We couldn't find that container. │ +│ Maybe it drifted away? │ +│ │ +│ [Back to Shore] │ +└─────────────────────────────────────────┘ + +Hero with subtle ocean feel: +┌─────────────────────────────────────────┐ +│ ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋ │ Subtle wave pattern bg +│ │ +│ ship containers on the │ +│ open web. 🐴 │ Mascot appears! +│ │ +│ ┌─────────────────────┐ │ +│ │ $ docker login ... │ │ +│ └─────────────────────┘ │ +│ │ +│ ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋ │ +└─────────────────────────────────────────┘ +``` + +### Card with Personality +``` +┌───────────────────────────────────┐ +│ ┌──────┐ │ +│ │ icon │ user/repo │ +│ │ │ :latest [⚓ Helm] │ Anchor icon for Helm +│ └──────┘ │ +│ │ +│ A container that does amazing │ +│ things for your app... │ +│ │ +│ sha256:abcdef12 │ +│ ─────────────────────────────────│ +│ ★ 12 ↓ 340 1 day ago │ +│ │ +│ 🐴 Shipped by alice.bsky.social │ Playful "shipped by" line +└───────────────────────────────────┘ + +(optional: "Shipped by" could be subtle or only on hover) +``` + +## Design Improvements + +### 1. Enhanced Card Design (Priority: High) +**Files:** `pkg/appview/public/css/style.css`, `pkg/appview/templates/components/repo-card.html` + +- Add subtle gradient backgrounds on hover +- Improve shadow depth (layered shadows for modern look) +- Add smooth transitions (transform, box-shadow) +- Better icon styling with ring/border accent +- Enhanced badge visibility with better contrast +- Add "Updated X ago" timestamp to cards +- Improve stat icon/count alignment and spacing + +### 2. Hero Section Polish (Priority: High) +**Files:** `pkg/appview/public/css/style.css`, `pkg/appview/templates/pages/home.html` + +- Add subtle background pattern or gradient mesh +- Improve terminal mockup styling (better shadows, glow effect) +- Enhance benefit cards with icons and better spacing +- Add visual separation between hero and content +- Improve CTA button styling with better hover states + +### 3. Typography & Spacing (Priority: High) +**Files:** `pkg/appview/public/css/style.css` + +- Increase visual hierarchy with better font weights +- Add more breathing room (padding/margins) +- Improve heading styles with subtle underlines or accents +- Better link styling with hover states +- Add letter-spacing to badges for readability + +### 4. Badge System Enhancement (Priority: Medium) +**Files:** `pkg/appview/public/css/style.css`, templates + +- Create unified badge design language +- Add subtle icons inside badges (already using Lucide) +- Improve color coding: Helm (blue), Attestation (green), Multi-arch (purple) +- Add "Official" or "Verified" badge styling (for future use) +- Better hover states on interactive badges + +### 5. Featured Section Improvements (Priority: Medium) +**Files:** `pkg/appview/templates/pages/home.html`, `pkg/appview/public/css/style.css` + +- Add section header with subtle styling +- Improve grid responsiveness +- Add "View All" link styling +- Better visual distinction from "What's New" section + +### 6. Navigation Polish (Priority: Medium) +**Files:** `pkg/appview/public/css/style.css`, nav templates + +- Improve search bar visibility and styling +- Better user menu dropdown aesthetics +- Add subtle border or shadow to navbar +- Improve mobile responsiveness + +### 7. Loading & Empty States (Priority: Low) +**Files:** `pkg/appview/public/css/style.css` + +- Add skeleton loading animations +- Improve empty state illustrations/styling +- Better transition when content loads + +### 8. Micro-interactions (Priority: Low) +**Files:** `pkg/appview/public/css/style.css`, `pkg/appview/public/js/app.js` + +- Add subtle hover animations throughout +- Improve button press feedback +- Star button animation on click +- Copy button success animation + +## Implementation Order + +1. **Phase 1: Core Card Styling** + - Update `.featured-card` with modern shadows and transitions + - Enhance badge styling in `style.css` + - Add hover effects and transforms + +2. **Phase 2: Hero & Featured Section** + - Improve hero section gradient/background + - Polish benefit cards + - Add section separators + +3. **Phase 3: Typography & Spacing** + - Update font weights and sizes + - Improve padding throughout + - Better visual rhythm + +4. **Phase 4: Navigation & Polish** + - Navbar improvements + - Loading states + - Final micro-interactions + +## Key CSS Changes + +### Tile Grid Layout +```css +.featured-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; +} + +/* Already exists but updating min-width */ +.featured-card { + min-height: 200px; + display: flex; + flex-direction: column; + justify-content: space-between; +} +``` + +### Enhanced Shadow System (Multi-layer for depth) +```css +--shadow-card: 0 1px 3px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.05); +--shadow-card-hover: 0 8px 25px rgba(78,205,196,0.15), 0 4px 12px rgba(0,0,0,0.1); +--shadow-nav: 0 2px 8px rgba(0,0,0,0.1); +``` + +### Card Design Enhancement +```css +.featured-card { + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; + border: 1px solid var(--border); +} +.featured-card:hover { + transform: translateY(-4px); + box-shadow: var(--shadow-card-hover); + border-color: var(--primary); /* teal accent on hover */ +} +``` + +### Icon Container Styling +```css +.featured-icon-placeholder { + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); + box-shadow: 0 2px 8px rgba(78,205,196,0.3); +} +``` + +### Badge System (Consistent, Accessible) +```css +.badge-helm { + background: #0d6cbf; + color: #fff; +} +.badge-multi { + background: #7c3aed; + color: #fff; +} +.badge-attestation { + background: #059669; + color: #fff; +} +/* All badges: */ +font-weight: 600; +letter-spacing: 0.02em; +text-transform: uppercase; +font-size: 0.7rem; +padding: 0.25rem 0.5rem; +border-radius: 4px; +``` + +### Hero Section Enhancement +```css +.hero-section { + background: + linear-gradient(135deg, var(--hero-bg-start) 0%, var(--hero-bg-end) 50%, rgba(78,205,196,0.1) 100%), + url('/static/wave-pattern.svg'); /* subtle wave pattern */ + background-size: cover, 100% 50px; + background-position: center, bottom; + background-repeat: no-repeat, repeat-x; +} +.benefit-card { + border: 1px solid transparent; + border-radius: 12px; /* softer corners */ + transition: all 0.2s ease; +} +.benefit-card:hover { + border-color: var(--primary); + transform: translateY(-4px); +} +``` + +### Playful Border Radius (Softer Feel) +```css +:root { + --radius-sm: 6px; /* was 4px */ + --radius-md: 12px; /* was 8px */ + --radius-lg: 16px; /* new */ +} + +.featured-card { border-radius: var(--radius-md); } +.benefit-card { border-radius: var(--radius-md); } +.btn { border-radius: var(--radius-sm); } +.hero-terminal { border-radius: var(--radius-lg); } +``` + +### Fun Empty States +```css +.empty-state { + text-align: center; + padding: 3rem; +} +.empty-state-mascot { + width: 120px; + height: auto; + margin-bottom: 1.5rem; + animation: float 3s ease-in-out infinite; +} +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} +.empty-state-title { + font-size: 1.5rem; + font-weight: 600; + color: var(--fg); +} +.empty-state-text { + color: var(--secondary); + margin-bottom: 1.5rem; +} +``` + +### Typography Refinements +```css +.featured-title { + font-weight: 600; + letter-spacing: -0.01em; +} +.featured-description { + line-height: 1.5; + opacity: 0.85; +} +``` + +## Data Model Change + +**Current "What's New":** Shows individual pushes (each tag push is a separate card) + +**Proposed "What's New":** Shows repos ordered by last update time (same as Featured, different sort) + +**Tracking:** `repository_stats` table already has `last_push` timestamp! +```sql +SELECT * FROM repository_stats ORDER BY last_push DESC LIMIT 9; +``` + +**Unified Card Data:** +| Field | Source | +|-------|--------| +| Handle, Repository | users + manifests | +| Tag | Latest tag from `tags` table | +| Digest | From latest tag or manifest | +| Description, IconURL | repo_pages or annotations | +| StarCount, PullCount | stars count + repository_stats | +| LastUpdated | `repository_stats.last_push` | +| ArtifactType | manifests.artifact_type | + +## Files to Modify + +| File | Changes | +|------|---------| +| `pkg/appview/public/css/style.css` | Rounded corners, shadows, hover, badges, ocean theme | +| `pkg/appview/public/wave-pattern.svg` | NEW: Subtle wave pattern for hero background | +| `pkg/appview/templates/components/repo-card.html` | Add Tag, Digest, LastUpdated fields | +| `pkg/appview/templates/components/empty-state.html` | NEW: Reusable fun empty state with mascot | +| `pkg/appview/templates/pages/home.html` | Both sections use repo-card grid | +| `pkg/appview/templates/pages/404.html` | Fun "Lost at sea" error page | +| `pkg/appview/db/queries.go` | New `GetRecentlyUpdatedRepos()` query; add fields to `RepoCardData` | +| `pkg/appview/handlers/home.go` | Replace `GetRecentPushes` with `GetRecentlyUpdatedRepos` | +| `pkg/appview/templates/partials/push-list.html` | Delete or repurpose (no longer needed) | + +## Dependencies + +**Mascot Art Needed:** +- `seahorse-empty.svg` - Friendly pose for "nothing here yet" empty states +- `seahorse-confused.svg` - Lost/confused pose for 404 errors +- `seahorse-waving.svg` (optional) - For hero section accent + +**Can proceed without art:** +- CSS changes (colors, shadows, rounded corners, gradients) +- Card layout and grid changes +- Data layer changes (queries, handlers) +- Wave pattern background (simple SVG) + +**Blocked until art is ready:** +- Empty state component with mascot +- 404 page redesign with mascot +- Hero mascot integration (optional) + +## Implementation Phases + +### Phase 1: CSS & Layout (No art needed) +1. Update border-radius variables (softer corners) +2. New shadow system +3. Card hover effects with teal accent +4. Tile grid layout (`minmax(280px, 1fr)`) +5. Wave pattern SVG for hero background + +### Phase 2: Card Component & Data +1. Update `repo-card.html` with new structure +2. Add `Digest`, `Tag`, `CreatedAt` fields +3. Update queries for latest manifest info +4. Replace push list with card grid + +### Phase 3: Hero & Section Polish +1. Hero gradient + wave pattern +2. Benefit card improvements +3. Section headers and spacing +4. Mobile responsive breakpoints + +### Phase 4: Mascot Integration (BLOCKED - needs art) +1. Empty state component with mascot +2. 404 page with confused seahorse +3. Hero mascot (optional) + +### Phase 5: Testing +1. Dark mode verification +2. Mobile responsive check +3. All functionality works (stars, links, copy) + +## Verification + +1. **Visual check on homepage** - cards have depth and polish +2. **Hover states** - smooth transitions on cards, buttons, badges +3. **Dark mode** - all changes work in both themes +4. **Mobile** - responsive at all breakpoints +5. **Functionality** - stars, search, navigation all work +6. **Performance** - no jank from CSS transitions +7. **Accessibility** - badge text readable (contrast check) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..256eb1e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1573 @@ +{ + "name": "atcr-styles", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "atcr-styles", + "version": "1.0.0", + "dependencies": { + "actor-typeahead": "^0.1.2", + "htmx.org": "^2.0.8", + "lucide": "^0.562.0" + }, + "devDependencies": { + "@tailwindcss/cli": "^4.1.18", + "daisyui": "^5.5.14", + "esbuild": "^0.27.2", + "tailwindcss": "^4.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.4.tgz", + "integrity": "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.4", + "@parcel/watcher-darwin-arm64": "2.5.4", + "@parcel/watcher-darwin-x64": "2.5.4", + "@parcel/watcher-freebsd-x64": "2.5.4", + "@parcel/watcher-linux-arm-glibc": "2.5.4", + "@parcel/watcher-linux-arm-musl": "2.5.4", + "@parcel/watcher-linux-arm64-glibc": "2.5.4", + "@parcel/watcher-linux-arm64-musl": "2.5.4", + "@parcel/watcher-linux-x64-glibc": "2.5.4", + "@parcel/watcher-linux-x64-musl": "2.5.4", + "@parcel/watcher-win32-arm64": "2.5.4", + "@parcel/watcher-win32-ia32": "2.5.4", + "@parcel/watcher-win32-x64": "2.5.4" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.4.tgz", + "integrity": "sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz", + "integrity": "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.4.tgz", + "integrity": "sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.4.tgz", + "integrity": "sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.4.tgz", + "integrity": "sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.4.tgz", + "integrity": "sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.4.tgz", + "integrity": "sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.4.tgz", + "integrity": "sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.4.tgz", + "integrity": "sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.4.tgz", + "integrity": "sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.4.tgz", + "integrity": "sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.4.tgz", + "integrity": "sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.4.tgz", + "integrity": "sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/cli": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.18.tgz", + "integrity": "sha512-sMZ+lZbDyxwjD2E0L7oRUjJ01Ffjtme5OtjvvnC+cV4CEDcbqzbp25TCpxHj6kWLU9+DlqJOiNgSOgctC2aZmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@parcel/watcher": "^2.5.1", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "enhanced-resolve": "^5.18.3", + "mri": "^1.2.0", + "picocolors": "^1.1.1", + "tailwindcss": "4.1.18" + }, + "bin": { + "tailwindcss": "dist/index.mjs" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/actor-typeahead": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/actor-typeahead/-/actor-typeahead-0.1.2.tgz", + "integrity": "sha512-I97YqqNl7Kar0J/bIJvgY/KmHpssHcDElhfwVTLP7wRFlkxso2ZLBqiS2zol5A8UVUJbQK2JXYaqNpZXz8Uk2A==", + "license": "MPL-2.0" + }, + "node_modules/daisyui": { + "version": "5.5.14", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.5.14.tgz", + "integrity": "sha512-L47rvw7I7hK68TA97VB8Ee0woHew+/ohR6Lx6Ah/krfISOqcG4My7poNpX5Mo5/ytMxiR40fEaz6njzDi7cuSg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/htmx.org": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.8.tgz", + "integrity": "sha512-fm297iru0iWsNJlBrjvtN7V9zjaxd+69Oqjh4F/Vq9Wwi2kFisLcrLCiv5oBX0KLfOX/zG8AUo9ROMU5XUB44Q==", + "license": "0BSD" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide": { + "version": "0.562.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.562.0.tgz", + "integrity": "sha512-k1Fb8ZMnRQovWRlea7Jr0b9UKA29IM7/cu79+mJrhVohvA2YC/Ti3Sk+G+h/SIu3IlrKT4RAbWMHUBBQd1O6XA==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..75f0aec --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "atcr-styles", + "version": "1.0.0", + "private": true, + "scripts": { + "css:build": "BROWSERSLIST_IGNORE_OLD_DATA=1 npx tailwindcss -i ./pkg/appview/src/css/main.css -o ./pkg/appview/public/css/style.css --minify", + "css:watch": "BROWSERSLIST_IGNORE_OLD_DATA=1 npx tailwindcss -i ./pkg/appview/src/css/main.css -o ./pkg/appview/public/css/style.css --watch", + "js:build": "esbuild pkg/appview/src/js/main.js --bundle --minify --format=esm --outfile=pkg/appview/public/js/bundle.min.js", + "js:watch": "esbuild pkg/appview/src/js/main.js --bundle --watch --format=esm --outfile=pkg/appview/public/js/bundle.min.js", + "build": "npm run css:build && npm run js:build", + "watch": "npm run css:watch & npm run js:watch" + }, + "devDependencies": { + "@tailwindcss/cli": "^4.1.18", + "daisyui": "^5.5.14", + "esbuild": "^0.27.2", + "tailwindcss": "^4.1" + }, + "dependencies": { + "actor-typeahead": "^0.1.2", + "htmx.org": "^2.0.8", + "lucide": "^0.562.0" + } +} diff --git a/pkg/appview/db/models.go b/pkg/appview/db/models.go index 6e656bf..3f014e3 100644 --- a/pkg/appview/db/models.go +++ b/pkg/appview/db/models.go @@ -137,8 +137,11 @@ type RepoCardData struct { IconURL string StarCount int PullCount int - IsStarred bool // Whether the current user has starred this repository - ArtifactType string // container-image, helm-chart, unknown + IsStarred bool // Whether the current user has starred this repository + ArtifactType string // container-image, helm-chart, unknown + Tag string // Latest tag name (e.g., "latest", "v1.0.0") + Digest string // Latest manifest digest (sha256:...) + LastUpdated time.Time // When the repository was last pushed to } // PlatformInfo represents platform information (OS/Architecture) diff --git a/pkg/appview/db/queries.go b/pkg/appview/db/queries.go index 0aba278..74e9383 100644 --- a/pkg/appview/db/queries.go +++ b/pkg/appview/db/queries.go @@ -1736,6 +1736,188 @@ func GetFeaturedRepositories(db *sql.DB, limit int, currentUserDID string) ([]Fe return featured, nil } +// RepoCardSortOrder specifies how repo cards should be sorted +type RepoCardSortOrder string + +const ( + // SortByScore sorts by combined stars and pulls (for Featured) + SortByScore RepoCardSortOrder = "score" + // SortByLastUpdate sorts by most recent push (for What's New) + SortByLastUpdate RepoCardSortOrder = "last_update" +) + +// GetRepoCards fetches repository cards with full data including Tag, Digest, and LastUpdated +func GetRepoCards(db *sql.DB, limit int, currentUserDID string, sortOrder RepoCardSortOrder) ([]RepoCardData, error) { + // Build ORDER BY clause based on sort order + var orderBy string + switch sortOrder { + case SortByLastUpdate: + orderBy = "COALESCE(rs.last_push, m.created_at) DESC" + default: // SortByScore + orderBy = "repo_stats.score DESC, repo_stats.star_count DESC, repo_stats.pull_count DESC, m.created_at DESC" + } + + query := ` + WITH latest_manifests AS ( + SELECT did, repository, MAX(id) as latest_id + FROM manifests + GROUP BY did, repository + ), + repo_stats AS ( + SELECT + lm.did, + lm.repository, + COALESCE(rs.pull_count, 0) as pull_count, + COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = lm.did AND repository = lm.repository), 0) as star_count, + (COALESCE(rs.pull_count, 0) + COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = lm.did AND repository = lm.repository), 0) * 10) as score + FROM latest_manifests lm + LEFT JOIN repository_stats rs ON lm.did = rs.did AND lm.repository = rs.repository + ) + SELECT + m.did, + u.handle, + m.repository, + COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.title'), ''), + COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.description'), ''), + COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'io.atcr.icon'), ''), + repo_stats.star_count, + repo_stats.pull_count, + COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = m.did AND repository = m.repository), 0), + COALESCE(m.artifact_type, 'container-image'), + COALESCE((SELECT tag FROM tags WHERE did = m.did AND repository = m.repository ORDER BY created_at DESC LIMIT 1), ''), + COALESCE(m.digest, ''), + COALESCE(rs.last_push, m.created_at), + COALESCE(rp.avatar_cid, '') + FROM latest_manifests lm + JOIN manifests m ON lm.latest_id = m.id + JOIN users u ON m.did = u.did + JOIN repo_stats ON m.did = repo_stats.did AND m.repository = repo_stats.repository + LEFT JOIN repository_stats rs ON m.did = rs.did AND m.repository = rs.repository + LEFT JOIN repo_pages rp ON m.did = rp.did AND m.repository = rp.repository + ORDER BY ` + orderBy + ` + LIMIT ? + ` + + rows, err := db.Query(query, currentUserDID, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + var cards []RepoCardData + for rows.Next() { + var c RepoCardData + var ownerDID string + var isStarredInt int + var avatarCID string + var lastUpdatedStr sql.NullString + + if err := rows.Scan(&ownerDID, &c.OwnerHandle, &c.Repository, &c.Title, &c.Description, &c.IconURL, + &c.StarCount, &c.PullCount, &isStarredInt, &c.ArtifactType, &c.Tag, &c.Digest, &lastUpdatedStr, &avatarCID); err != nil { + return nil, err + } + c.IsStarred = isStarredInt > 0 + if lastUpdatedStr.Valid { + if t, err := parseTimestamp(lastUpdatedStr.String); err == nil { + c.LastUpdated = t + } + } + // Prefer repo page avatar over annotation icon + if avatarCID != "" { + c.IconURL = BlobCDNURL(ownerDID, avatarCID) + } + + cards = append(cards, c) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return cards, nil +} + +// GetUserRepoCards fetches repository cards for a specific user with full data +func GetUserRepoCards(db *sql.DB, userDID string, currentUserDID string) ([]RepoCardData, error) { + query := ` + WITH latest_manifests AS ( + SELECT did, repository, MAX(id) as latest_id + FROM manifests + WHERE did = ? + GROUP BY did, repository + ), + repo_stats AS ( + SELECT + lm.did, + lm.repository, + COALESCE(rs.pull_count, 0) as pull_count, + COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = lm.did AND repository = lm.repository), 0) as star_count + FROM latest_manifests lm + LEFT JOIN repository_stats rs ON lm.did = rs.did AND lm.repository = rs.repository + ) + SELECT + m.did, + u.handle, + m.repository, + COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.title'), ''), + COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.description'), ''), + COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'io.atcr.icon'), ''), + repo_stats.star_count, + repo_stats.pull_count, + COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = m.did AND repository = m.repository), 0), + COALESCE(m.artifact_type, 'container-image'), + COALESCE((SELECT tag FROM tags WHERE did = m.did AND repository = m.repository ORDER BY created_at DESC LIMIT 1), ''), + COALESCE(m.digest, ''), + COALESCE(rs.last_push, m.created_at), + COALESCE(rp.avatar_cid, '') + FROM latest_manifests lm + JOIN manifests m ON lm.latest_id = m.id + JOIN users u ON m.did = u.did + JOIN repo_stats ON m.did = repo_stats.did AND m.repository = repo_stats.repository + LEFT JOIN repository_stats rs ON m.did = rs.did AND m.repository = rs.repository + LEFT JOIN repo_pages rp ON m.did = rp.did AND m.repository = rp.repository + ORDER BY COALESCE(rs.last_push, m.created_at) DESC + ` + + rows, err := db.Query(query, userDID, currentUserDID) + if err != nil { + return nil, err + } + defer rows.Close() + + var cards []RepoCardData + for rows.Next() { + var c RepoCardData + var ownerDID string + var isStarredInt int + var avatarCID string + var lastUpdatedStr sql.NullString + + if err := rows.Scan(&ownerDID, &c.OwnerHandle, &c.Repository, &c.Title, &c.Description, &c.IconURL, + &c.StarCount, &c.PullCount, &isStarredInt, &c.ArtifactType, &c.Tag, &c.Digest, &lastUpdatedStr, &avatarCID); err != nil { + return nil, err + } + c.IsStarred = isStarredInt > 0 + if lastUpdatedStr.Valid { + if t, err := parseTimestamp(lastUpdatedStr.String); err == nil { + c.LastUpdated = t + } + } + // Prefer repo page avatar over annotation icon + if avatarCID != "" { + c.IconURL = BlobCDNURL(ownerDID, avatarCID) + } + + cards = append(cards, c) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return cards, nil +} + // RepoPage represents a repository page record cached from PDS type RepoPage struct { DID string diff --git a/pkg/appview/handlers/api.go b/pkg/appview/handlers/api.go index 9ac6197..a04cfd8 100644 --- a/pkg/appview/handlers/api.go +++ b/pkg/appview/handlers/api.go @@ -1,9 +1,11 @@ package handlers import ( + "bytes" "database/sql" "errors" "fmt" + "html/template" "log/slog" "net/http" @@ -21,6 +23,7 @@ type StarRepositoryHandler struct { DB *sql.DB Directory identity.Directory Refresher *oauth.Refresher + Templates *template.Template } func (h *StarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -64,7 +67,21 @@ func (h *StarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request return } - // Return success + // Check if HTMX request - return HTML component + if r.Header.Get("HX-Request") == "true" && h.Templates != nil { + // Get current star count and do optimistic increment + stats, _ := db.GetRepositoryStats(h.DB, ownerDID, repository) + starCount := 0 + if stats != nil { + starCount = stats.StarCount + } + starCount++ // Optimistic increment + + renderStarComponent(w, h.Templates, handle, repository, true, starCount) + return + } + + // Return JSON for API clients w.WriteHeader(http.StatusCreated) render.JSON(w, r, map[string]bool{"starred": true}) } @@ -74,6 +91,7 @@ type UnstarRepositoryHandler struct { DB *sql.DB Directory identity.Directory Refresher *oauth.Refresher + Templates *template.Template } func (h *UnstarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -119,7 +137,23 @@ func (h *UnstarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque slog.Debug("Star record not found, already unstarred") } - // Return success + // Check if HTMX request - return HTML component + if r.Header.Get("HX-Request") == "true" && h.Templates != nil { + // Get current star count and do optimistic decrement + stats, _ := db.GetRepositoryStats(h.DB, ownerDID, repository) + starCount := 0 + if stats != nil { + starCount = stats.StarCount + } + if starCount > 0 { + starCount-- // Optimistic decrement + } + + renderStarComponent(w, h.Templates, handle, repository, false, starCount) + return + } + + // Return JSON for API clients render.JSON(w, r, map[string]bool{"starred": false}) } @@ -264,3 +298,24 @@ func (h *CredentialHelperVersionHandler) ServeHTTP(w http.ResponseWriter, r *htt w.Header().Set("Cache-Control", "public, max-age=300") // Cache for 5 minutes render.JSON(w, r, response) } + +// renderStarComponent renders the star component HTML for HTMX responses +func renderStarComponent(w http.ResponseWriter, tmpl *template.Template, handle, repository string, isStarred bool, starCount int) { + data := map[string]any{ + "Interactive": true, + "Handle": handle, + "Repository": repository, + "IsStarred": isStarred, + "StarCount": starCount, + } + + var buf bytes.Buffer + if err := tmpl.ExecuteTemplate(&buf, "star", data); err != nil { + slog.Error("Failed to render star component", "error", err) + http.Error(w, "Failed to render component", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write(buf.Bytes()) +} diff --git a/pkg/appview/handlers/home.go b/pkg/appview/handlers/home.go index 1cba00f..f895f5b 100644 --- a/pkg/appview/handlers/home.go +++ b/pkg/appview/handlers/home.go @@ -6,6 +6,7 @@ package handlers import ( "database/sql" "html/template" + "log" "net/http" "strconv" @@ -14,6 +15,13 @@ import ( "atcr.io/pkg/appview/middleware" ) +// BenefitCard represents a feature benefit card on the home page +type BenefitCard struct { + Icon string + Title string + Description string +} + // HomeHandler handles the home page type HomeHandler struct { DB *sql.DB @@ -28,35 +36,36 @@ func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { currentUserDID = user.DID } - // Fetch featured repositories (top 6) - featured, err := db.GetFeaturedRepositories(h.DB, 6, currentUserDID) + // Fetch featured repositories (top 6 by score - carousel cycles through them) + featuredCards, err := db.GetRepoCards(h.DB, 6, currentUserDID, db.SortByScore) if err != nil { - // Log error but continue - featured section will be empty - featured = []db.FeaturedRepository{} + log.Printf("Error fetching featured repos: %v", err) + featuredCards = []db.RepoCardData{} } - // Convert to RepoCardData for template - cards := make([]db.RepoCardData, len(featured)) - for i, repo := range featured { - cards[i] = db.RepoCardData{ - OwnerHandle: repo.OwnerHandle, - Repository: repo.Repository, - Title: repo.Title, - Description: repo.Description, - IconURL: repo.IconURL, - StarCount: repo.StarCount, - PullCount: repo.PullCount, - IsStarred: repo.IsStarred, - ArtifactType: repo.ArtifactType, - } + // Fetch recently updated repositories (top 18 by last push - 6 rows) + recentCards, err := db.GetRepoCards(h.DB, 18, currentUserDID, db.SortByLastUpdate) + if err != nil { + log.Printf("Error fetching recent repos: %v", err) + recentCards = []db.RepoCardData{} + } + + benefits := []BenefitCard{ + {Icon: "ship", Title: "Works with Docker", Description: "Use docker push & pull. No new tools to learn."}, + {Icon: "anchor", Title: "Your Data", Description: "Join shared holds or captain your own storage."}, + {Icon: "compass", Title: "Discover Images", Description: "Browse and star public container registries."}, } data := struct { PageData FeaturedRepos []db.RepoCardData + RecentRepos []db.RepoCardData + Benefits []BenefitCard }{ PageData: NewPageData(r, h.RegistryURL), - FeaturedRepos: cards, + FeaturedRepos: featuredCards, + RecentRepos: recentCards, + Benefits: benefits, } if err := h.Templates.ExecuteTemplate(w, "home", data); err != nil { diff --git a/pkg/appview/handlers/repository.go b/pkg/appview/handlers/repository.go index bc48f63..62c0fc5 100644 --- a/pkg/appview/handlers/repository.go +++ b/pkg/appview/handlers/repository.go @@ -245,6 +245,7 @@ func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request Tags []db.TagWithPlatforms // Tags with platform info Manifests []db.ManifestWithMetadata // Top-level manifests only StarCount int + PullCount int IsStarred bool IsOwner bool // Whether current user owns this repository ReadmeHTML template.HTML @@ -256,6 +257,7 @@ func (h *RepositoryPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request Tags: tagsWithPlatforms, Manifests: manifests, StarCount: stats.StarCount, + PullCount: stats.PullCount, IsStarred: isStarred, IsOwner: isOwner, ReadmeHTML: readmeHTML, diff --git a/pkg/appview/handlers/user.go b/pkg/appview/handlers/user.go index f91f5be..ed4bb4a 100644 --- a/pkg/appview/handlers/user.go +++ b/pkg/appview/handlers/user.go @@ -3,9 +3,11 @@ package handlers import ( "database/sql" "html/template" + "log" "net/http" "atcr.io/pkg/appview/db" + "atcr.io/pkg/appview/middleware" "atcr.io/pkg/atproto" "github.com/go-chi/chi/v5" ) @@ -50,33 +52,17 @@ func (h *UserPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { viewedUser.Handle = resolvedHandle } - // Fetch repositories for this user - repos, err := db.GetUserRepositories(h.DB, viewedUser.DID) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + // Get current user DID for star state (empty string if not logged in) + var currentUserDID string + if user := middleware.GetUser(r); user != nil { + currentUserDID = user.DID } - // Convert to RepoCardData for template - cards := make([]db.RepoCardData, 0, len(repos)) - for _, repo := range repos { - stats, err := db.GetRepositoryStats(h.DB, viewedUser.DID, repo.Name) - if err != nil { - // Continue with zero stats on error - stats = &db.RepositoryStats{ - DID: viewedUser.DID, - Repository: repo.Name, - } - } - cards = append(cards, db.RepoCardData{ - OwnerHandle: viewedUser.Handle, - Repository: repo.Name, - Title: repo.Title, - Description: repo.Description, - IconURL: repo.IconURL, - StarCount: stats.StarCount, - PullCount: stats.PullCount, - }) + // Fetch repository cards for this user + cards, err := db.GetUserRepoCards(h.DB, viewedUser.DID, currentUserDID) + if err != nil { + log.Printf("Error fetching repo cards for user %s: %v", viewedUser.DID, err) + cards = []db.RepoCardData{} } data := struct { diff --git a/pkg/appview/public/css/style.css b/pkg/appview/public/css/style.css new file mode 100644 index 0000000..c5cb43c --- /dev/null +++ b/pkg/appview/public/css/style.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-ease:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-amber-400:oklch(82.8% .189 84.429);--color-gray-500:oklch(55.1% .027 264.364);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--shadow-sm:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--shadow-md:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--shadow-lg:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--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}@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{--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=light]:checked),[data-theme=light]{color-scheme:normal;--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(75% .12 175);--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(68% .18 25);--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:normal;--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(78% .12 175);--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(72% .16 25);--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}}@layer components{.cmd{align-items:center;gap:calc(var(--spacing)*2);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-base-300);background-color:var(--color-base-200);width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);display:flex;position:relative;overflow:hidden}.cmd code{text-overflow:ellipsis;white-space:nowrap;font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow:hidden}.nav-search-wrapper{align-items:center;display:flex;position:relative}.nav-search-form{margin-right:calc(var(--spacing)*2);width:calc(var(--spacing)*0);opacity:0;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s;position:absolute;right:100%;overflow:hidden}.nav-search-wrapper.expanded .nav-search-form{width:calc(var(--spacing)*62);opacity:1}.card-interactive{cursor:pointer;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.5s;transition-duration:.5s}.card-interactive:hover{box-shadow:var(--shadow-card-hover);transform:translateY(-2px)}actor-typeahead{--color-background:var(--color-base-100);--color-border:var(--color-base-300);--color-shadow:var(--color-base-content);--color-hover:var(--color-base-200);--color-avatar-fallback:var(--color-base-300);--radius:.5rem;--padding-menu:.25rem;z-index:50}actor-typeahead::part(handle){color:var(--color-base-content)}actor-typeahead::part(menu){--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);margin-top:.25rem}.recent-accounts-dropdown{top:100%;right:calc(var(--spacing)*0);left:calc(var(--spacing)*0);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-base-300);background-color:var(--color-base-100);border-radius:var(--radius-lg);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);z-index:50;max-height:calc(var(--spacing)*60);margin-top:.25rem;position:absolute;overflow-y:auto}.recent-accounts-header{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);text-transform:uppercase;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-base-300);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.recent-accounts-header{color:color-mix(in oklab,var(--color-base-content)60%,transparent)}}.recent-accounts-item{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5);cursor:pointer;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));--tw-duration:.15s;color:var(--color-base-content);transition-duration:.15s}.recent-accounts-item:hover,.recent-accounts-item.focused{background-color:var(--color-base-200)}}@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}}.modal{pointer-events:none;visibility:hidden;width:100%;max-width:none;height:100%;max-height:none;color:inherit;transition:visibility .3s allow-discrete,background-color .3s ease-out,opacity .1s ease-out;overscroll-behavior:contain;z-index:999;scrollbar-gutter:auto;background-color:#0000;place-items:center;margin:0;padding:0;display:grid;position:fixed;inset:0;overflow:clip}.modal::backdrop{display:none}.fab{pointer-events:none;z-index:999;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:nowrap;inset-inline-end:1rem;flex-direction:column-reverse;align-items:flex-end;gap:.5rem;display:flex;position:fixed;bottom:1rem}.fab>*{pointer-events:auto;align-items:center;gap:.5rem;display:flex}.fab>:hover,.fab>:has(:focus-visible){z-index:1}.fab>[tabindex]:first-child{transition-property:opacity,visibility,rotate;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:grid;position:relative}.fab .fab-close,.fab .fab-main-action{inset-inline-end:0;position:absolute;bottom:0}:is(.fab:focus-within:has(.fab-close),.fab:focus-within:has(.fab-main-action))>[tabindex]{opacity:0;rotate:90deg}.fab:focus-within>[tabindex]:first-child{pointer-events:none}.fab:focus-within>: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:1}.fab>: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:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.fab>:nth-child(n+2).fab-main-action,.fab>:nth-child(n+2).fab-close{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x)var(--tw-scale-y)}.fab>:nth-child(3){transition-delay:30ms}.fab>:nth-child(4){transition-delay:60ms}.fab>:nth-child(5){transition-delay:90ms}.fab>:nth-child(6){transition-delay:.12s}.tooltip{--tt-bg:var(--color-neutral);--tt-off:calc(100% + .5rem);--tt-tail:calc(100% + 1px + .25rem);display:inline-block;position:relative}.tooltip>.tooltip-content,.tooltip[data-tip]:before{border-radius:var(--radius-field);text-align:center;white-space:normal;max-width:20rem;color:var(--color-neutral-content);opacity:0;background-color:var(--tt-bg);pointer-events:none;z-index:2;--tw-content:attr(data-tip);content:var(--tw-content);width:max-content;padding-block:.25rem;padding-inline:.5rem;font-size:.875rem;line-height:1.25;position:absolute}.tooltip:after{opacity:0;background-color:var(--tt-bg);content:"";pointer-events:none;--mask-tooltip:url("data:image/svg+xml,%3Csvg width='10' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.500009 1C3.5 1 3.00001 4 5.00001 4C7 4 6.5 1 9.5 1C10 1 10 0.499897 10 0H0C-1.99338e-08 0.5 0 1 0.500009 1Z' fill='black'/%3E%3C/svg%3E%0A");width:.625rem;height:.25rem;-webkit-mask-position:-1px 0;mask-position:-1px 0;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-image:var(--mask-tooltip);-webkit-mask-image:var(--mask-tooltip);mask-image:var(--mask-tooltip);display:block;position:absolute}@media (prefers-reduced-motion:no-preference){.tooltip>.tooltip-content,.tooltip[data-tip]:before,.tooltip:after{transition:opacity .2s cubic-bezier(.4,0,.2,1) 75ms,transform .2s cubic-bezier(.4,0,.2,1) 75ms}}:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))>.tooltip-content,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))[data-tip]:before,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible)):after{opacity:1;--tt-pos:0rem}@media (prefers-reduced-motion:no-preference){:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))>.tooltip-content,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible))[data-tip]:before,:is(.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))).tooltip-open,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):hover,.tooltip:is([data-tip]:not([data-tip=""]),:has(.tooltip-content:not(:empty))):has(:focus-visible)):after{transition:opacity .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1)}}.tab{cursor:pointer;appearance:none;text-align:center;webkit-user-select:none;-webkit-user-select:none;user-select:none;flex-wrap:wrap;justify-content:center;align-items:center;display:inline-flex;position:relative}@media (hover:hover){.tab:hover{color:var(--color-base-content)}}.tab{--tab-p:.75rem;--tab-bg:var(--color-base-100);--tab-border-color:var(--color-base-300);--tab-radius-ss:0;--tab-radius-se:0;--tab-radius-es:0;--tab-radius-ee:0;--tab-order:0;--tab-radius-min:calc(.75rem - var(--border));--tab-radius-limit:min(var(--radius-field),var(--tab-radius-min));--tab-radius-grad:#0000 calc(69% - var(--border)),var(--tab-border-color)calc(69% - var(--border) + .25px),var(--tab-border-color)69%,var(--tab-bg)calc(69% + .25px);order:var(--tab-order);height:var(--tab-height);padding-inline:var(--tab-p);border-color:#0000;font-size:.875rem}.tab:is(input[type=radio]){min-width:fit-content}.tab:is(input[type=radio]):after{--tw-content:attr(aria-label);content:var(--tw-content)}.tab:is(label){position:relative}.tab:is(label) input{cursor:pointer;appearance:none;opacity:0;position:absolute;inset:0}:is(.tab:checked,.tab:is(label:has(:checked)),.tab:is(.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]))+.tab-content{display:block}.tab:not(:checked,label:has(:checked),:hover,.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]){color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.tab:not(:checked,label:has(:checked),:hover,.tab-active,[aria-selected=true],[aria-current=true],[aria-current=page]){color:color-mix(in oklab,var(--color-base-content)50%,transparent)}}.tab:not(input):empty{cursor:default;flex-grow:1}.tab:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tab:focus{outline-offset:2px;outline:2px solid #0000}}.tab:focus-visible,.tab:is(label:has(:checked:focus-visible)){outline-offset:-5px;outline:2px solid}.tab[disabled]{pointer-events:none;opacity:.4}.menu{--menu-active-fg:var(--color-neutral-content);--menu-active-bg:var(--color-neutral);flex-flow:column wrap;width:fit-content;padding:.5rem;font-size:.875rem;display:flex}.menu :where(li ul){white-space:nowrap;margin-inline-start:1rem;padding-inline-start:.5rem;position:relative}.menu :where(li ul):before{background-color:var(--color-base-content);opacity:.1;width:var(--border);content:"";inset-inline-start:0;position:absolute;top:.75rem;bottom:.75rem}.menu :where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}.menu :where(li:not(.menu-title)>:not(ul,details,.menu-title,.btn)),.menu :where(li:not(.menu-title)>details>summary:not(.menu-title)){border-radius:var(--radius-field);text-align:start;text-wrap:balance;-webkit-user-select:none;user-select:none;grid-auto-columns:minmax(auto,max-content) auto max-content;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:.5rem;padding-block:.375rem;padding-inline:.75rem;transition-property:color,background-color,box-shadow;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);display:grid}.menu :where(li>details>summary){--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li>details>summary){outline-offset:2px;outline:2px solid #0000}}.menu :where(li>details>summary)::-webkit-details-marker{display:none}:is(.menu :where(li>details>summary),.menu :where(li>.menu-dropdown-toggle)):after{content:"";transform-origin:50%;pointer-events:none;justify-self:flex-end;width:.375rem;height:.375rem;transition-property:rotate,translate;transition-duration:.2s;display:block;translate:0 -1px;rotate:-135deg;box-shadow:inset 2px 2px}.menu details{interpolate-size:allow-keywords;overflow:hidden}.menu details::details-content{block-size:0}@media (prefers-reduced-motion:no-preference){.menu details::details-content{transition-behavior:allow-discrete;transition-property:block-size,content-visibility;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}}.menu details[open]::details-content{block-size:auto}.menu :where(li>details[open]>summary):after,.menu :where(li>.menu-dropdown-toggle.menu-dropdown-show):after{translate:0 1px;rotate:45deg}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{color:var(--color-base-content);--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn).menu-focus,.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title),li:not(.menu-title,.disabled)>details>summary:not(.menu-title)):not(.menu-active,:active,.btn):focus-visible{outline-offset:2px;outline:2px solid #0000}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){cursor:pointer;background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){outline-offset:2px;outline:2px solid #0000}}.menu :where(li:not(.menu-title,.disabled)>:not(ul,details,.menu-title):not(.menu-active,:active,.btn):hover,li:not(.menu-title,.disabled)>details>summary:not(.menu-title):not(.menu-active,:active,.btn):hover){box-shadow:inset 0 1px oklch(0% 0 0/.01),inset 0 -1px oklch(100% 0 0/.01)}.menu :where(li:empty){background-color:var(--color-base-content);opacity:.1;height:1px;margin:.5rem 1rem}.menu :where(li){flex-flow:column wrap;flex-shrink:0;align-items:stretch;display:flex;position:relative}.menu :where(li) .badge{justify-self:flex-end}.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{outline-offset:2px;outline:2px solid #0000}}.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active{color:var(--menu-active-fg);background-color:var(--menu-active-bg);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise)}:is(.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active):not(:is(.menu :where(li)>:not(ul,.menu-title,details,.btn):active,.menu :where(li)>:not(ul,.menu-title,details,.btn).menu-active,.menu :where(li)>details>summary:active):active){box-shadow:0 2px calc(var(--depth)*3px)-2px var(--menu-active-bg)}.menu :where(li).menu-disabled{pointer-events:none;color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.menu :where(li).menu-disabled{color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.menu .dropdown:focus-within .menu-dropdown-toggle:after{translate:0 1px;rotate:45deg}.menu .dropdown-content{margin-top:.5rem;padding:.5rem}.menu .dropdown-content:before{display:none}.dropdown{position-area:var(--anchor-v,bottom)var(--anchor-h,span-right);display:inline-block;position:relative}.dropdown>:not(:has(~[class*=dropdown-content])):focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.dropdown>:not(:has(~[class*=dropdown-content])):focus{outline-offset:2px;outline:2px solid #0000}}.dropdown .dropdown-content{position:absolute}.dropdown.dropdown-close .dropdown-content,.dropdown:not(details,.dropdown-open,.dropdown-hover:hover,:focus-within) .dropdown-content,.dropdown.dropdown-hover:not(:hover) [tabindex]:first-child:focus:not(:focus-visible)~.dropdown-content{transform-origin:top;opacity:0;display:none;scale:95%}.dropdown[popover],.dropdown .dropdown-content{z-index:999}@media (prefers-reduced-motion:no-preference){.dropdown[popover],.dropdown .dropdown-content{transition-behavior:allow-discrete;transition-property:opacity,scale,display;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);animation:.2s dropdown}}@starting-style{.dropdown[popover],.dropdown .dropdown-content{opacity:0;scale:95%}}:is(.dropdown:not(.dropdown-close).dropdown-open,.dropdown:not(.dropdown-close):not(.dropdown-hover):focus,.dropdown:not(.dropdown-close):focus-within)>[tabindex]:first-child{pointer-events:none}:is(.dropdown:not(.dropdown-close).dropdown-open,.dropdown:not(.dropdown-close):not(.dropdown-hover):focus,.dropdown:not(.dropdown-close):focus-within) .dropdown-content,.dropdown:not(.dropdown-close).dropdown-hover:hover .dropdown-content{opacity:1;scale:100%}.dropdown:is(details) summary::-webkit-details-marker{display:none}.dropdown:where([popover]){background:0 0}.dropdown[popover]{color:inherit;position:fixed}@supports not (position-area:bottom){.dropdown[popover]{margin:auto}.dropdown[popover].dropdown-close{transform-origin:top;opacity:0;display:none;scale:95%}.dropdown[popover].dropdown-open:not(:popover-open){transform-origin:top;opacity:0;display:none;scale:95%}.dropdown[popover]::backdrop{background-color:oklab(0% none none/.3)}}:is(.dropdown[popover].dropdown-close,.dropdown[popover]:not(.dropdown-open,:popover-open)){transform-origin:top;opacity:0;display:none;scale:95%}:where(.btn){width:unset}.btn{cursor:pointer;text-align:center;vertical-align:middle;outline-offset:2px;webkit-user-select:none;-webkit-user-select:none;user-select:none;padding-inline:var(--btn-p);color:var(--btn-fg);--tw-prose-links:var(--btn-fg);height:var(--size);font-size:var(--fontsize,.875rem);outline-color:var(--btn-color,var(--color-base-content));background-color:var(--btn-bg);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--btn-noise);border-width:var(--border);border-style:solid;border-color:var(--btn-border);text-shadow:0 .5px oklch(100% 0 0/calc(var(--depth)*.15));touch-action:manipulation;box-shadow:0 .5px 0 .5px oklch(100% 0 0/calc(var(--depth)*6%))inset,var(--btn-shadow);--size:calc(var(--size-field,.25rem)*10);--btn-bg:var(--btn-color,var(--color-base-200));--btn-fg:var(--color-base-content);--btn-p:1rem;--btn-border:var(--btn-bg);border-start-start-radius:var(--join-ss,var(--radius-field));border-start-end-radius:var(--join-se,var(--radius-field));border-end-end-radius:var(--join-ee,var(--radius-field));border-end-start-radius:var(--join-es,var(--radius-field));flex-wrap:nowrap;flex-shrink:0;justify-content:center;align-items:center;gap:.375rem;font-weight:600;transition-property:color,background-color,border-color,box-shadow;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1);display:inline-flex}@supports (color:color-mix(in lab, red, red)){.btn{--btn-border:color-mix(in oklab,var(--btn-bg),#000 calc(var(--depth)*5%))}}.btn{--btn-shadow:0 3px 2px -2px var(--btn-bg),0 4px 3px -2px var(--btn-bg)}@supports (color:color-mix(in lab, red, red)){.btn{--btn-shadow:0 3px 2px -2px color-mix(in oklab,var(--btn-bg)calc(var(--depth)*30%),#0000),0 4px 3px -2px color-mix(in oklab,var(--btn-bg)calc(var(--depth)*30%),#0000)}}.btn{--btn-noise:var(--fx-noise)}@media (hover:hover){.btn:hover{--btn-bg:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn:hover{--btn-bg:color-mix(in oklab,var(--btn-color,var(--color-base-200)),#000 7%)}}}.btn:focus-visible,.btn:has(:focus-visible){isolation:isolate;outline-width:2px;outline-style:solid}.btn:active:not(.btn-active){--btn-bg:var(--btn-color,var(--color-base-200));translate:0 .5px}@supports (color:color-mix(in lab, red, red)){.btn:active:not(.btn-active){--btn-bg:color-mix(in oklab,var(--btn-color,var(--color-base-200)),#000 5%)}}.btn:active:not(.btn-active){--btn-border:var(--btn-color,var(--color-base-200))}@supports (color:color-mix(in lab, red, red)){.btn:active:not(.btn-active){--btn-border:color-mix(in oklab,var(--btn-color,var(--color-base-200)),#000 7%)}}.btn:active:not(.btn-active){--btn-shadow:0 0 0 0 oklch(0% 0 0/0),0 0 0 0 oklch(0% 0 0/0)}.btn:is(input[type=checkbox],input[type=radio]){appearance:none}.btn:is(input[type=checkbox],input[type=radio])[aria-label]:after{--tw-content:attr(aria-label);content:var(--tw-content)}.btn:where(input:checked:not(.filter .btn)){--btn-color:var(--color-primary);--btn-fg:var(--color-primary-content);isolation:isolate}.loading{pointer-events:none;aspect-ratio:1;vertical-align:middle;width:calc(var(--size-selector,.25rem)*6);background-color:currentColor;display:inline-block;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.collapse{border-radius:var(--radius-box,1rem);isolation:isolate;grid-template-rows:max-content 0fr;grid-template-columns:minmax(0,1fr);width:100%;display:grid;position:relative;overflow:hidden}@media (prefers-reduced-motion:no-preference){.collapse{transition:grid-template-rows .2s}}.collapse>input:is([type=checkbox],[type=radio]){appearance:none;opacity:0;z-index:1;grid-row-start:1;grid-column-start:1;width:100%;min-height:1lh;padding:1rem;padding-inline-end:3rem;transition:background-color .2s ease-out}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close)),.collapse:not(.collapse-close):has(>input:is([type=checkbox],[type=radio]):checked){grid-template-rows:max-content 1fr}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>.collapse-content,.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){content-visibility:visible;min-height:fit-content}@supports not (content-visibility:visible){.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>.collapse-content,.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){visibility:visible}}.collapse:focus-visible,.collapse:has(>input:is([type=checkbox],[type=radio]):focus-visible),.collapse:has(summary:focus-visible){outline-color:var(--color-base-content);outline-offset:2px;outline-width:2px;outline-style:solid}.collapse:not(.collapse-close)>input[type=checkbox],.collapse:not(.collapse-close)>input[type=radio]:not(:checked),.collapse:not(.collapse-close)>.collapse-title{cursor:pointer}:is(.collapse[tabindex]:focus:not(.collapse-close,.collapse[open]),.collapse[tabindex]:focus-within:not(.collapse-close,.collapse[open]))>.collapse-title{cursor:unset}.collapse:is([open],[tabindex]:focus:not(.collapse-close),[tabindex]:focus-within:not(.collapse-close))>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input:is([type=checkbox],[type=radio]):checked~.collapse-content){padding-bottom:1rem}.collapse:is(details){width:100%}@media (prefers-reduced-motion:no-preference){.collapse:is(details)::details-content{transition:content-visibility .2s allow-discrete,visibility .2s allow-discrete,min-height .2s ease-out allow-discrete,padding .1s ease-out 20ms,background-color .2s ease-out,height .2s;interpolate-size:allow-keywords;height:0}.collapse:is(details):where([open])::details-content{height:auto}}.collapse:is(details) summary{display:block;position:relative}.collapse:is(details) summary::-webkit-details-marker{display:none}.collapse:is(details)>.collapse-content{content-visibility:visible}.collapse:is(details) summary{outline:none}.collapse-content{content-visibility:hidden;min-height:0;cursor:unset;grid-row-start:2;grid-column-start:1;padding-left:1rem;padding-right:1rem}@supports not (content-visibility:hidden){.collapse-content{visibility:hidden}}@media (prefers-reduced-motion:no-preference){.collapse-content{transition:content-visibility .2s allow-discrete,visibility .2s allow-discrete,min-height .2s ease-out allow-discrete,padding .1s ease-out 20ms,background-color .2s ease-out}}.validator-hint{visibility:hidden;margin-top:.5rem;font-size:.75rem}.validator:user-valid{--input-color:var(--color-success)}.validator:user-valid:focus{--input-color:var(--color-success)}.validator:user-valid:checked{--input-color:var(--color-success)}.validator:user-valid[aria-checked=true]{--input-color:var(--color-success)}.validator:user-valid:focus-within{--input-color:var(--color-success)}.validator:has(:user-valid){--input-color:var(--color-success)}.validator:has(:user-valid):focus{--input-color:var(--color-success)}.validator:has(:user-valid):checked{--input-color:var(--color-success)}.validator:has(:user-valid)[aria-checked=true]{--input-color:var(--color-success)}.validator:has(:user-valid):focus-within{--input-color:var(--color-success)}.validator:user-invalid{--input-color:var(--color-error)}.validator:user-invalid:focus{--input-color:var(--color-error)}.validator:user-invalid:checked{--input-color:var(--color-error)}.validator:user-invalid[aria-checked=true]{--input-color:var(--color-error)}.validator:user-invalid:focus-within{--input-color:var(--color-error)}.validator:user-invalid~.validator-hint{visibility:visible;color:var(--color-error)}.validator:has(:user-invalid){--input-color:var(--color-error)}.validator:has(:user-invalid):focus{--input-color:var(--color-error)}.validator:has(:user-invalid):checked{--input-color:var(--color-error)}.validator:has(:user-invalid)[aria-checked=true]{--input-color:var(--color-error)}.validator:has(:user-invalid):focus-within{--input-color:var(--color-error)}.validator:has(:user-invalid)~.validator-hint{visibility:visible;color:var(--color-error)}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))),:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):focus,:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):checked,:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))[aria-checked=true],:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false]))):focus-within{--input-color:var(--color-error)}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))~.validator-hint{visibility:visible;color:var(--color-error)}.list{flex-direction:column;font-size:.875rem;display:flex}.list .list-row{--list-grid-cols:minmax(0,auto)1fr;border-radius:var(--radius-box);word-break:break-word;grid-auto-flow:column;grid-template-columns:var(--list-grid-cols);gap:1rem;padding:1rem;display:grid;position:relative}:is(.list>:not(:last-child).list-row,.list>:not(:last-child) .list-row):after{content:"";border-bottom:var(--border)solid;inset-inline:var(--radius-box);border-color:var(--color-base-content);position:absolute;bottom:0}@supports (color:color-mix(in lab, red, red)){:is(.list>:not(:last-child).list-row,.list>:not(:last-child) .list-row):after{border-color:color-mix(in oklab,var(--color-base-content)5%,transparent)}}.toast{translate:var(--toast-x,0)var(--toast-y,0);inset-inline:auto 1rem;background-color:#0000;flex-direction:column;gap:.5rem;width:max-content;max-width:calc(100vw - 2rem);display:flex;position:fixed;top:auto;bottom:1rem}@media (prefers-reduced-motion:no-preference){.toast>*{animation:.25s ease-out toast}}.toggle{border:var(--border)solid currentColor;color:var(--input-color);cursor:pointer;appearance:none;vertical-align:middle;webkit-user-select:none;-webkit-user-select:none;user-select:none;--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);flex-shrink:0;grid-template-columns:0fr 1fr 1fr;place-content:center;display:inline-grid;position:relative;box-shadow:inset 0 1px}@supports (color:color-mix(in lab, red, red)){.toggle{box-shadow:0 1px color-mix(in oklab,currentColor calc(var(--depth)*10%),#0000)inset}}.toggle{--input-color:var(--color-base-content);transition:color .3s,grid-template-columns .2s}@supports (color:color-mix(in lab, red, red)){.toggle{--input-color:color-mix(in oklab,var(--color-base-content)50%,#0000)}}.toggle{--toggle-p:calc(var(--size)*.125);--size:calc(var(--size-selector,.25rem)*6);width:calc((var(--size)*2) - (var(--border) + var(--toggle-p))*2);height:var(--size)}.toggle>*{z-index:1;cursor:pointer;appearance:none;background-color:#0000;border:none;grid-column:2/span 1;grid-row-start:1;height:100%;padding:.125rem;transition:opacity .2s,rotate .4s}.toggle>:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.toggle>:focus{outline-offset:2px;outline:2px solid #0000}}.toggle>:nth-child(2){color:var(--color-base-100);rotate:none}.toggle>:nth-child(3){color:var(--color-base-100);opacity:0;rotate:-15deg}.toggle:has(:checked)>:nth-child(2){opacity:0;rotate:15deg}.toggle:has(:checked)>:nth-child(3){opacity:1;rotate:none}.toggle:before{aspect-ratio:1;border-radius:var(--radius-selector);--tw-content:"";content:var(--tw-content);height:100%;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;background-color:currentColor;grid-row-start:1;grid-column-start:2;transition:background-color .1s,translate .2s,inset-inline-start .2s;position:relative;inset-inline-start:0;translate:0}@supports (color:color-mix(in lab, red, red)){.toggle: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 color-mix(in oklab,currentColor calc(var(--depth)*10%),#0000)}}.toggle:before{background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise)}@media (forced-colors:active){.toggle:before{outline-style:var(--tw-outline-style);outline-offset:calc(1px*-1);outline-width:1px}}@media print{.toggle:before{outline-offset:-1rem;outline:.25rem solid}}.toggle:focus-visible,.toggle:has(:focus-visible){outline-offset:2px;outline:2px solid}.toggle:checked,.toggle[aria-checked=true],.toggle:has(>input:checked){background-color:var(--color-base-100);--input-color:var(--color-base-content);grid-template-columns:1fr 1fr 0fr}:is(.toggle:checked,.toggle[aria-checked=true],.toggle:has(>input:checked)):before{background-color:currentColor}@starting-style{:is(.toggle:checked,.toggle[aria-checked=true],.toggle:has(>input:checked)):before{opacity:0}}.toggle:indeterminate{grid-template-columns:.5fr 1fr .5fr}.toggle:disabled{cursor:not-allowed;opacity:.3}.toggle:disabled:before{border:var(--border)solid currentColor;background-color:#0000}.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}.table{border-collapse:separate;--tw-border-spacing-x:calc(.25rem*0);--tw-border-spacing-y:calc(.25rem*0);width:100%;border-spacing:var(--tw-border-spacing-x)var(--tw-border-spacing-y);border-radius:var(--radius-box);text-align:left;font-size:.875rem;position:relative}.table:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:right}@media (hover:hover){:is(.table tr.row-hover,.table tr.row-hover:nth-child(2n)):hover{background-color:var(--color-base-200)}}.table :where(th,td){vertical-align:middle;padding-block:.75rem;padding-inline:1rem}.table :where(thead,tfoot){white-space:nowrap;color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(thead,tfoot){color:color-mix(in oklab,var(--color-base-content)60%,transparent)}}.table :where(thead,tfoot){font-size:.875rem;font-weight:600}.table :where(tfoot tr:first-child :is(td,th)){border-top:var(--border)solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(tfoot tr:first-child :is(td,th)){border-top:var(--border)solid color-mix(in oklch,var(--color-base-content)5%,#0000)}}.table :where(.table-pin-rows thead tr){z-index:1;background-color:var(--color-base-100);position:sticky;top:0}.table :where(.table-pin-rows tfoot tr){z-index:1;background-color:var(--color-base-100);position:sticky;bottom:0}.table :where(.table-pin-cols tr th){background-color:var(--color-base-100);position:sticky;left:0;right:0}.table :where(thead tr :is(td,th),tbody tr:not(:last-child) :is(td,th)){border-bottom:var(--border)solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.table :where(thead tr :is(td,th),tbody tr:not(:last-child) :is(td,th)){border-bottom:var(--border)solid color-mix(in oklch,var(--color-base-content)5%,#0000)}}.steps{counter-reset:step;grid-auto-columns:1fr;grid-auto-flow:column;display:inline-grid;overflow:auto hidden}.steps .step{text-align:center;--step-bg:var(--color-base-300);--step-fg:var(--color-base-content);grid-template-rows:40px 1fr;grid-template-columns:auto;place-items:center;min-width:4rem;display:grid}.steps .step:before{width:100%;height:.5rem;color:var(--step-bg);background-color:var(--step-bg);content:"";border:1px solid;grid-row-start:1;grid-column-start:1;margin-inline-start:-100%;top:0}.steps .step>.step-icon,.steps .step:not(:has(.step-icon)):after{--tw-content:counter(step);content:var(--tw-content);counter-increment:step;z-index:1;color:var(--step-fg);background-color:var(--step-bg);border:1px solid var(--step-bg);border-radius:3.40282e38px;grid-row-start:1;grid-column-start:1;place-self:center;place-items:center;width:2rem;height:2rem;display:grid;position:relative}.steps .step:first-child:before{--tw-content:none;content:var(--tw-content)}.steps .step[data-content]:after{--tw-content:attr(data-content);content:var(--tw-content)}.range{appearance:none;webkit-appearance:none;--range-thumb:var(--color-base-100);--range-thumb-size:calc(var(--size-selector,.25rem)*6);--range-progress:currentColor;--range-fill:1;--range-p:.25rem;--range-bg:currentColor}@supports (color:color-mix(in lab, red, red)){.range{--range-bg:color-mix(in oklab,currentColor 10%,#0000)}}.range{cursor:pointer;vertical-align:middle;--radius-selector-max:calc(var(--radius-selector) + var(--radius-selector) + var(--radius-selector));border-radius:calc(var(--radius-selector) + min(var(--range-p),var(--radius-selector-max)));width:clamp(3rem,20rem,100%);height:var(--range-thumb-size);background-color:#0000;border:none;overflow:hidden}[dir=rtl] .range{--range-dir:-1}.range:focus{outline:none}.range:focus-visible{outline-offset:2px;outline:2px solid}.range::-webkit-slider-runnable-track{background-color:var(--range-bg);border-radius:var(--radius-selector);width:100%;height:calc(var(--range-thumb-size)*.5)}@media (forced-colors:active){.range::-webkit-slider-runnable-track{border:1px solid}.range::-moz-range-track{border:1px solid}}.range::-webkit-slider-thumb{box-sizing:border-box;border-radius:calc(var(--radius-selector) + min(var(--range-p),var(--radius-selector-max)));background-color:var(--range-thumb);height:var(--range-thumb-size);width:var(--range-thumb-size);border:var(--range-p)solid;appearance:none;webkit-appearance:none;color:var(--range-progress);box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px currentColor,0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill));position:relative;top:50%;transform:translateY(-50%)}@supports (color:color-mix(in lab, red, red)){.range::-webkit-slider-thumb{box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px color-mix(in oklab,currentColor calc(var(--depth)*10%),#0000),0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill))}}.range::-moz-range-track{background-color:var(--range-bg);border-radius:var(--radius-selector);width:100%;height:calc(var(--range-thumb-size)*.5)}.range::-moz-range-thumb{box-sizing:border-box;border-radius:calc(var(--radius-selector) + min(var(--range-p),var(--radius-selector-max)));height:var(--range-thumb-size);width:var(--range-thumb-size);border:var(--range-p)solid;color:var(--range-progress);box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px currentColor,0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill));background-color:currentColor;position:relative;top:50%}@supports (color:color-mix(in lab, red, red)){.range::-moz-range-thumb{box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px color-mix(in oklab,currentColor calc(var(--depth)*10%),#0000),0 0 0 2rem var(--range-thumb)inset,calc((var(--range-dir,1)*-100cqw) - (var(--range-dir,1)*var(--range-thumb-size)/2))0 0 calc(100cqw*var(--range-fill))}}.range:disabled{cursor:not-allowed;opacity:.3}.diff-resizer{isolation:isolate;z-index:2;resize:horizontal;opacity:0;cursor:ew-resize;transform-origin:100% 100%;clip-path:inset(calc(100% - .75rem) 0 0 calc(100% - .75rem));grid-row-start:2;grid-column-start:1;width:50cqi;min-width:1rem;max-width:calc(100cqi - 1rem);height:.75rem;transition:min-width .3s ease-out,max-width .3s ease-out;position:relative;overflow:hidden;transform:scaleY(5)translate(.32rem,50%)}.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)}.timeline{display:flex;position:relative}.timeline>li{grid-template-rows:var(--timeline-row-start,minmax(0,1fr))auto var(--timeline-row-end,minmax(0,1fr));grid-template-columns:var(--timeline-col-start,minmax(0,1fr))auto var(--timeline-col-end,minmax(0,1fr));flex-shrink:0;align-items:center;display:grid;position:relative}.timeline>li>hr{border:none;width:100%}.timeline>li>hr:first-child{grid-row-start:2;grid-column-start:1}.timeline>li>hr:last-child{grid-area:2/3/auto/none}@media print{.timeline>li>hr{border:.1px solid var(--color-base-300)}}.timeline :where(hr){background-color:var(--color-base-300);height:.25rem}.timeline:has(.timeline-middle hr):first-child{border-start-start-radius:0;border-start-end-radius:var(--radius-selector);border-end-end-radius:var(--radius-selector);border-end-start-radius:0}.timeline:has(.timeline-middle hr):last-child,.timeline:not(:has(.timeline-middle)) :first-child hr:last-child{border-start-start-radius:var(--radius-selector);border-start-end-radius:0;border-end-end-radius:0;border-end-start-radius:var(--radius-selector)}.timeline:not(:has(.timeline-middle)) :last-child hr:first-child{border-start-start-radius:0;border-start-end-radius:var(--radius-selector);border-end-end-radius:var(--radius-selector);border-end-start-radius:0}.swap{cursor:pointer;vertical-align:middle;webkit-user-select:none;-webkit-user-select:none;user-select:none;place-content:center;display:inline-grid;position:relative}.swap input{appearance:none;border:none}.swap>*{grid-row-start:1;grid-column-start:1}@media (prefers-reduced-motion:no-preference){.swap>*{transition-property:transform,rotate,opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(0,0,.2,1)}}.swap .swap-on,.swap .swap-indeterminate,.swap input:indeterminate~.swap-on,.swap input:is(:checked,:indeterminate)~.swap-off{opacity:0}.swap input:checked~.swap-on,.swap input:indeterminate~.swap-indeterminate{opacity:1;backface-visibility:visible}.collapse-title{grid-row-start:1;grid-column-start:1;width:100%;min-height:1lh;padding:1rem;padding-inline-end:3rem;transition:background-color .2s ease-out;position:relative}.mockup-code{border-radius:var(--radius-box);background-color:var(--color-neutral);color:var(--color-neutral-content);direction:ltr;padding-block:1.25rem;font-size:.875rem;position:relative;overflow:auto hidden}.mockup-code:before{content:"";opacity:.3;border-radius:3.40282e38px;width:.75rem;height:.75rem;margin-bottom:1rem;display:block;box-shadow:1.4em 0,2.8em 0,4.2em 0}.mockup-code pre{padding-right:1.25rem}.mockup-code pre:before{content:"";margin-right:2ch}.mockup-code pre[data-prefix]:before{--tw-content:attr(data-prefix);content:var(--tw-content);text-align:right;opacity:.5;width:2rem;display:inline-block}.avatar{vertical-align:middle;display:inline-flex;position:relative}.avatar>div{aspect-ratio:1;display:block;overflow:hidden}.avatar img{object-fit:cover;width:100%;height:100%}.checkbox{border:var(--border)solid var(--input-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.checkbox{border:var(--border)solid var(--input-color,color-mix(in oklab,var(--color-base-content)20%,#0000))}}.checkbox{cursor:pointer;appearance:none;border-radius:var(--radius-selector);vertical-align:middle;color:var(--color-base-content);box-shadow:0 1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 0 #0000 inset,0 0 #0000;--size:calc(var(--size-selector,.25rem)*6);width:var(--size);height:var(--size);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise);flex-shrink:0;padding:.25rem;transition:background-color .2s,box-shadow .2s;display:inline-block;position:relative}.checkbox:before{--tw-content:"";content:var(--tw-content);opacity:0;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,70% 80%,70% 100%);width:100%;height:100%;box-shadow:0px 3px 0 0px oklch(100% 0 0/calc(var(--depth)*.1))inset;background-color:currentColor;font-size:1rem;line-height:.75;transition:clip-path .3s .1s,opacity .1s .1s,rotate .3s .1s,translate .3s .1s;display:block;rotate:45deg}.checkbox:focus-visible{outline:2px solid var(--input-color,currentColor);outline-offset:2px}.checkbox:checked,.checkbox[aria-checked=true]{background-color:var(--input-color,#0000);box-shadow:0 0 #0000 inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px oklch(0% 0 0/calc(var(--depth)*.1))}:is(.checkbox:checked,.checkbox[aria-checked=true]):before{clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 0%,70% 0%,70% 100%);opacity:1}@media (forced-colors:active){:is(.checkbox:checked,.checkbox[aria-checked=true]):before{--tw-content:"✔︎";clip-path:none;background-color:#0000;rotate:none}}@media print{:is(.checkbox:checked,.checkbox[aria-checked=true]):before{--tw-content:"✔︎";clip-path:none;background-color:#0000;rotate:none}}.checkbox:indeterminate{background-color:var(--input-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.checkbox:indeterminate{background-color:var(--input-color,color-mix(in oklab,var(--color-base-content)20%,#0000))}}.checkbox:indeterminate:before{opacity:1;clip-path:polygon(20% 100%,20% 80%,50% 80%,50% 80%,80% 80%,80% 100%);translate:0 -35%;rotate:none}.radio{cursor:pointer;appearance:none;vertical-align:middle;border:var(--border)solid var(--input-color,currentColor);border-radius:3.40282e38px;flex-shrink:0;padding:.25rem;display:inline-block;position:relative}@supports (color:color-mix(in lab, red, red)){.radio{border:var(--border)solid var(--input-color,color-mix(in srgb,currentColor 20%,#0000))}}.radio{box-shadow:0 1px oklch(0% 0 0/calc(var(--depth)*.1))inset;--size:calc(var(--size-selector,.25rem)*6);width:var(--size);height:var(--size);color:var(--input-color,currentColor)}.radio:before{--tw-content:"";content:var(--tw-content);background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise);border-radius:3.40282e38px;width:100%;height:100%;display:block}.radio:focus-visible{outline:2px solid}.radio:checked,.radio[aria-checked=true]{background-color:var(--color-base-100);border-color:currentColor}@media (prefers-reduced-motion:no-preference){.radio:checked,.radio[aria-checked=true]{animation:.2s ease-out radio}}:is(.radio:checked,.radio[aria-checked=true]):before{box-shadow:0 -1px oklch(0% 0 0/calc(var(--depth)*.1))inset,0 8px 0 -4px oklch(100% 0 0/calc(var(--depth)*.1))inset,0 1px oklch(0% 0 0/calc(var(--depth)*.1));background-color:currentColor}@media (forced-colors:active){:is(.radio:checked,.radio[aria-checked=true]):before{outline-style:var(--tw-outline-style);outline-offset:calc(1px*-1);outline-width:1px}}@media print{:is(.radio:checked,.radio[aria-checked=true]):before{outline-offset:-1rem;outline:.25rem solid}}.rating{vertical-align:middle;display:inline-flex;position:relative}.rating input{appearance:none;border:none}.rating :where(*){background-color:var(--color-base-content);opacity:.2;border-radius:0;width:1.5rem;height:1.5rem}@media (prefers-reduced-motion:no-preference){.rating :where(*){animation:.25s ease-out rating}}.rating :where(*):is(input){cursor:pointer}.rating .rating-hidden{background-color:#0000;width:.5rem}.rating input[type=radio]:checked{background-image:none}.rating :checked,.rating [aria-checked=true],.rating [aria-current=true],.rating :has(~:checked,~[aria-checked=true],~[aria-current=true]){opacity:1}.rating :focus-visible{scale:1.1}@media (prefers-reduced-motion:no-preference){.rating :focus-visible{transition:scale .2s ease-out}}.rating :active:focus{animation:none;scale:1.1}.navbar{align-items:center;width:100%;min-height:4rem;padding:.5rem;display:flex}.card{border-radius:var(--radius-box);outline-offset:2px;outline:0 solid #0000;flex-direction:column;transition:outline .2s ease-in-out;display:flex;position:relative}.card:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.card:focus{outline-offset:2px;outline:2px solid #0000}}.card:focus-visible{outline-color:currentColor}.card :where(figure:first-child){border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-end-radius:unset;border-end-start-radius:unset;overflow:hidden}.card :where(figure:last-child){border-start-start-radius:unset;border-start-end-radius:unset;border-end-end-radius:inherit;border-end-start-radius:inherit;overflow:hidden}.card figure{justify-content:center;align-items:center;display:flex}.card:has(>input:is(input[type=checkbox],input[type=radio])){cursor:pointer;-webkit-user-select:none;user-select:none}.card:has(>:checked){outline:2px solid}.stats{border-radius:var(--radius-box);grid-auto-flow:column;display:inline-grid;position:relative;overflow-x:auto}.progress{appearance:none;border-radius:var(--radius-box);background-color:currentColor;width:100%;height:.5rem;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.progress{background-color:color-mix(in oklab,currentcolor 20%,transparent)}}.progress{color:var(--color-base-content)}.progress:indeterminate{background-image:repeating-linear-gradient(90deg,currentColor -1% 10%,#0000 10% 90%);background-position-x:15%;background-size:200%}@media (prefers-reduced-motion:no-preference){.progress:indeterminate{animation:5s ease-in-out infinite progress}}@supports ((-moz-appearance:none)){.progress:indeterminate::-moz-progress-bar{background-color:#0000}@media (prefers-reduced-motion:no-preference){.progress:indeterminate::-moz-progress-bar{background-image:repeating-linear-gradient(90deg,currentColor -1% 10%,#0000 10% 90%);background-position-x:15%;background-size:200%;animation:5s ease-in-out infinite progress}}.progress::-moz-progress-bar{border-radius:var(--radius-box);background-color:currentColor}}@supports ((-webkit-appearance:none)){.progress::-webkit-progress-bar{border-radius:var(--radius-box);background-color:#0000}.progress::-webkit-progress-value{border-radius:var(--radius-box);background-color:currentColor}}.hero-content{isolation:isolate;justify-content:center;align-items:center;gap:1rem;max-width:80rem;padding:1rem;display:flex}.textarea{border:var(--border)solid #0000;appearance:none;border-radius:var(--radius-field);background-color:var(--color-base-100);vertical-align:middle;width:clamp(3rem,20rem,100%);min-height:5rem;font-size:max(var(--font-size,.875rem),.875rem);touch-action:manipulation;border-color:var(--input-color);box-shadow:0 1px var(--input-color)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset;flex-shrink:1;padding-block:.5rem;padding-inline:.75rem}@supports (color:color-mix(in lab, red, red)){.textarea{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)inset,0 -1px oklch(100% 0 0/calc(var(--depth)*.1))inset}}.textarea{--input-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.textarea{--input-color:color-mix(in oklab,var(--color-base-content)20%,#0000)}}.textarea textarea{appearance:none;background-color:#0000;border:none}.textarea textarea:focus,.textarea textarea:focus-within{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.textarea textarea:focus,.textarea textarea:focus-within{outline-offset:2px;outline:2px solid #0000}}.textarea:focus,.textarea:focus-within{--input-color:var(--color-base-content);box-shadow:0 1px var(--input-color)}@supports (color:color-mix(in lab, red, red)){.textarea:focus,.textarea:focus-within{box-shadow:0 1px color-mix(in oklab,var(--input-color)calc(var(--depth)*10%),#0000)}}.textarea:focus,.textarea:focus-within{outline:2px solid var(--input-color);outline-offset:2px;isolation:isolate}@media (pointer:coarse){@supports (-webkit-touch-callout:none){.textarea:focus,.textarea:focus-within{--font-size:1rem}}}.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){cursor:not-allowed;border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){color:color-mix(in oklab,var(--color-base-content)40%,transparent)}}:is(.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]))::placeholder{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:is(.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]))::placeholder{color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.textarea:has(>textarea[disabled]),.textarea:is(:disabled,[disabled]){box-shadow:none}.textarea:has(>textarea[disabled])>textarea[disabled]{cursor:not-allowed}.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%}.modal-backdrop{color:#0000;z-index:-1;grid-row-start:1;grid-column-start:1;place-self:stretch stretch;display:grid}.modal-backdrop button{cursor:pointer}.tab-content{order:var(--tabcontent-order);--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-color:#0000;border-width:var(--border);border-start-start-radius:var(--tabcontent-radius-ss);border-start-end-radius:var(--tabcontent-radius-se);border-end-end-radius:var(--tabcontent-radius-ee);border-end-start-radius:var(--tabcontent-radius-es);display:none}.hero{background-position:50%;background-size:cover;place-items:center;width:100%;display:grid}.hero>*{grid-row-start:1;grid-column-start:1}.modal-box{background-color:var(--color-base-100);border-top-left-radius:var(--modal-tl,var(--radius-box));border-top-right-radius:var(--modal-tr,var(--radius-box));border-bottom-left-radius:var(--modal-bl,var(--radius-box));border-bottom-right-radius:var(--modal-br,var(--radius-box));opacity:0;overscroll-behavior:contain;grid-row-start:1;grid-column-start:1;width:91.6667%;max-width:32rem;max-height:100vh;padding:1.5rem;transition:translate .3s ease-out,scale .3s ease-out,opacity .2s ease-out 50ms,box-shadow .3s ease-out;overflow-y:auto;scale:95%;box-shadow:0 25px 50px -12px oklch(0% 0 0/.25)}.timeline-middle{grid-row-start:2;grid-column-start:2}.stat-value{white-space:nowrap;grid-column-start:1;font-size:2rem;font-weight:800}.divider{white-space:nowrap;height:1rem;margin:var(--divider-m,1rem 0);--divider-color:var(--color-base-content);flex-direction:row;align-self:stretch;align-items:center;display:flex}@supports (color:color-mix(in lab, red, red)){.divider{--divider-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.divider:before,.divider:after{content:"";background-color:var(--divider-color);flex-grow:1;width:100%;height:.125rem}@media print{.divider:before,.divider:after{border:.5px solid}}.divider:not(:empty){gap:1rem}.filter{flex-wrap:wrap;display:flex}.filter input[type=radio]{width:auto}.filter input{opacity:1;transition:margin .1s,opacity .3s,padding .3s,border-width .1s;overflow:hidden;scale:1}.filter input:not(:last-child){margin-inline-end:.25rem}.filter input.filter-reset{aspect-ratio:1}.filter input.filter-reset:after{--tw-content:"×";content:var(--tw-content)}.filter:not(:has(input:checked:not(.filter-reset))) .filter-reset,.filter:not(:has(input:checked:not(.filter-reset))) input[type=reset],.filter:has(input:checked:not(.filter-reset)) input:not(:checked,.filter-reset,input[type=reset]){opacity:0;border-width:0;width:0;margin-inline:0;padding-inline:0;scale:0}.label{white-space:nowrap;color:currentColor;align-items:center;gap:.375rem;display:inline-flex}@supports (color:color-mix(in lab, red, red)){.label{color:color-mix(in oklab,currentcolor 60%,transparent)}}.label:has(input){cursor:pointer}.label:is(.input>*,.select>*){white-space:nowrap;height:calc(100% - .5rem);font-size:inherit;align-items:center;padding-inline:.75rem;display:flex}.label:is(.input>*,.select>*):first-child{border-inline-end:var(--border)solid currentColor;margin-inline:-.75rem .75rem}@supports (color:color-mix(in lab, red, red)){.label:is(.input>*,.select>*):first-child{border-inline-end:var(--border)solid color-mix(in oklab,currentColor 10%,#0000)}}.label:is(.input>*,.select>*):last-child{border-inline-start:var(--border)solid currentColor;margin-inline:.75rem -.75rem}@supports (color:color-mix(in lab, red, red)){.label:is(.input>*,.select>*):last-child{border-inline-start:var(--border)solid color-mix(in oklab,currentColor 10%,#0000)}}.modal-action{justify-content:flex-end;gap:.5rem;margin-top:1.5rem;display:flex}.carousel-item{box-sizing:content-box;scroll-snap-align:start;flex:none;display:flex}.status{aspect-ratio:1;border-radius:var(--radius-selector);background-color:var(--color-base-content);width:.5rem;height:.5rem;display:inline-block}@supports (color:color-mix(in lab, red, red)){.status{background-color:color-mix(in oklab,var(--color-base-content)20%,transparent)}}.status{vertical-align:middle;color:#0000004d;background-position:50%;background-repeat:no-repeat}@supports (color:color-mix(in lab, red, red)){.status{color:color-mix(in oklab,var(--color-black)30%,transparent)}}.status{background-image:radial-gradient(circle at 35% 30%,oklch(1 0 0/calc(var(--depth)*.5)),#0000);box-shadow:0 2px 3px -1px}@supports (color:color-mix(in lab, red, red)){.status{box-shadow:0 2px 3px -1px color-mix(in oklab,currentColor calc(var(--depth)*100%),#0000)}}.badge{border-radius:var(--radius-selector);vertical-align:middle;color:var(--badge-fg);border:var(--border)solid var(--badge-color,var(--color-base-200));background-size:auto,calc(var(--noise)*100%);background-image:none,var(--fx-noise);background-color:var(--badge-bg);--badge-bg:var(--badge-color,var(--color-base-100));--badge-fg:var(--color-base-content);--size:calc(var(--size-selector,.25rem)*6);width:fit-content;height:var(--size);padding-inline:calc(var(--size)/2 - var(--border));justify-content:center;align-items:center;gap:.5rem;font-size:.875rem;display:inline-flex}.kbd{border-radius:var(--radius-field);background-color:var(--color-base-200);vertical-align:middle;border:var(--border)solid var(--color-base-content);justify-content:center;align-items:center;padding-inline:.5em;display:inline-flex}@supports (color:color-mix(in lab, red, red)){.kbd{border:var(--border)solid color-mix(in srgb,var(--color-base-content)20%,#0000)}}.kbd{border-bottom:calc(var(--border) + 1px)solid var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.kbd{border-bottom:calc(var(--border) + 1px)solid color-mix(in srgb,var(--color-base-content)20%,#0000)}}.kbd{--size:calc(var(--size-selector,.25rem)*6);height:var(--size);min-width:var(--size);font-size:.875rem}.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}.footer{grid-auto-flow:row;place-items:start;gap:2.5rem 1rem;width:100%;font-size:.875rem;line-height:1.25rem;display:grid}.footer>*{place-items:start;gap:.5rem;display:grid}.footer.footer-center{text-align:center;grid-auto-flow:column dense;place-items:center}.footer.footer-center>*{place-items:center}.stat{grid-template-columns:repeat(1,1fr);column-gap:1rem;width:100%;padding-block:1rem;padding-inline:1.5rem;display:inline-grid}.stat:not(:last-child){border-inline-end:var(--border)dashed currentColor}@supports (color:color-mix(in lab, red, red)){.stat:not(:last-child){border-inline-end:var(--border)dashed color-mix(in oklab,currentColor 10%,#0000)}}.stat:not(:last-child){border-block-end:none}.navbar-end{justify-content:flex-end;align-items:center;width:50%;display:inline-flex}.navbar-start{justify-content:flex-start;align-items:center;width:50%;display:inline-flex}.card-body{padding:var(--card-p,1.5rem);font-size:var(--card-fs,.875rem);flex-direction:column;flex:auto;gap:.5rem;display:flex}.card-body :where(p){flex-grow:1}.carousel{scroll-snap-type:x mandatory;scrollbar-width:none;display:inline-flex;overflow-x:scroll}@media (prefers-reduced-motion:no-preference){.carousel{scroll-behavior:smooth}}.carousel::-webkit-scrollbar{display:none}.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}.card-title{font-size:var(--cardtitle-fs,1.125rem);align-items:center;gap:.5rem;font-weight:600;display:flex}.mask{vertical-align:middle;display:inline-block;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.skeleton{border-radius:var(--radius-box);background-color:var(--color-base-300)}@media (prefers-reduced-motion:reduce){.skeleton{transition-duration:15s}}.skeleton{will-change:background-position;background-image:linear-gradient(105deg,#0000 0% 40%,var(--color-base-100)50%,#0000 60% 100%);background-position-x:-50%;background-size:200%}@media (prefers-reduced-motion:no-preference){.skeleton{animation:1.8s ease-in-out infinite skeleton}}.link{cursor:pointer;text-decoration-line:underline}.link:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.link:focus{outline-offset:2px;outline:2px solid #0000}}.link:focus-visible{outline-offset:2px;outline:2px solid}.btn-error{--btn-color:var(--color-error);--btn-fg:var(--color-error-content)}.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)}}@layer daisyui.l1.l2{.modal.modal-open,.modal[open],.modal:target,.modal-toggle:checked+.modal{pointer-events:auto;visibility:visible;opacity:1;transition:visibility 0s allow-discrete,background-color .3s ease-out,opacity .1s ease-out;background-color:oklch(0% 0 0/.4)}:is(.modal.modal-open,.modal[open],.modal:target,.modal-toggle:checked+.modal) .modal-box{opacity:1;translate:0;scale:1}:root:has(:is(.modal.modal-open,.modal[open],.modal:target,.modal-toggle:checked+.modal)){--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:forwards set-page-has-scroll;animation-timeline:scroll()}@starting-style{.modal.modal-open,.modal[open],.modal:target,.modal-toggle:checked+.modal{opacity:0}}.tooltip>.tooltip-content,.tooltip[data-tip]:before{transform:translateX(-50%)translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-off)50%}.tooltip:after{transform:translateX(-50%)translateY(var(--tt-pos,.25rem));inset:auto auto var(--tt-tail)50%}.btn:disabled:not(.btn-link,.btn-ghost){background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn:disabled:not(.btn-link,.btn-ghost){background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.btn:disabled:not(.btn-link,.btn-ghost){box-shadow:none}.btn:disabled{pointer-events:none;--btn-border:#0000;--btn-noise:none;--btn-fg:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn:disabled{--btn-fg:color-mix(in oklch,var(--color-base-content)20%,#0000)}}.btn[disabled]:not(.btn-link,.btn-ghost){background-color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn[disabled]:not(.btn-link,.btn-ghost){background-color:color-mix(in oklab,var(--color-base-content)10%,transparent)}}.btn[disabled]:not(.btn-link,.btn-ghost){box-shadow:none}.btn[disabled]{pointer-events:none;--btn-border:#0000;--btn-noise:none;--btn-fg:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.btn[disabled]{--btn-fg:color-mix(in oklch,var(--color-base-content)20%,#0000)}}@media (prefers-reduced-motion:no-preference){.collapse[open].collapse-arrow>.collapse-title:after,.collapse.collapse-open.collapse-arrow>.collapse-title:after{transform:translateY(-50%)rotate(225deg)}}.collapse.collapse-open.collapse-plus>.collapse-title:after{--tw-content:"−";content:var(--tw-content)}:is(.collapse[tabindex].collapse-arrow:focus:not(.collapse-close),.collapse.collapse-arrow[tabindex]:focus-within:not(.collapse-close))>.collapse-title:after,.collapse.collapse-arrow:not(.collapse-close)>input:is([type=checkbox],[type=radio]):checked~.collapse-title:after{transform:translateY(-50%)rotate(225deg)}.collapse[open].collapse-plus>.collapse-title:after,.collapse[tabindex].collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse.collapse-plus:not(.collapse-close)>input:is([type=checkbox],[type=radio]):checked~.collapse-title:after{--tw-content:"−";content:var(--tw-content)}.list .list-row:has(.list-col-grow:first-child){--list-grid-cols:1fr}.list .list-row:has(.list-col-grow:nth-child(2)){--list-grid-cols:minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(3)){--list-grid-cols:minmax(0,auto)minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(4)){--list-grid-cols:minmax(0,auto)minmax(0,auto)minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(5)){--list-grid-cols:minmax(0,auto)minmax(0,auto)minmax(0,auto)minmax(0,auto)1fr}.list .list-row:has(.list-col-grow:nth-child(6)){--list-grid-cols:minmax(0,auto)minmax(0,auto)minmax(0,auto)minmax(0,auto)minmax(0,auto)1fr}.list .list-row>*{grid-row-start:1}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after,.steps .step-neutral>.step-icon{--step-bg:var(--color-neutral);--step-fg:var(--color-neutral-content)}.steps .step-primary+.step-primary:before,.steps .step-primary:after,.steps .step-primary>.step-icon{--step-bg:var(--color-primary);--step-fg:var(--color-primary-content)}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after,.steps .step-secondary>.step-icon{--step-bg:var(--color-secondary);--step-fg:var(--color-secondary-content)}.steps .step-accent+.step-accent:before,.steps .step-accent:after,.steps .step-accent>.step-icon{--step-bg:var(--color-accent);--step-fg:var(--color-accent-content)}.steps .step-info+.step-info:before,.steps .step-info:after,.steps .step-info>.step-icon{--step-bg:var(--color-info);--step-fg:var(--color-info-content)}.steps .step-success+.step-success:before,.steps .step-success:after,.steps .step-success>.step-icon{--step-bg:var(--color-success);--step-fg:var(--color-success-content)}.steps .step-warning+.step-warning:before,.steps .step-warning:after,.steps .step-warning>.step-icon{--step-bg:var(--color-warning);--step-fg:var(--color-warning-content)}.steps .step-error+.step-error:before,.steps .step-error:after,.steps .step-error>.step-icon{--step-bg:var(--color-error);--step-fg:var(--color-error-content)}.checkbox:disabled,.radio:disabled{cursor:not-allowed;opacity:.2}.rating.rating-xs :where(:not(.rating-hidden)){width:1rem;height:1rem}.rating.rating-sm :where(:not(.rating-hidden)){width:1.25rem;height:1.25rem}.rating.rating-md :where(:not(.rating-hidden)){width:1.5rem;height:1.5rem}.rating.rating-lg :where(:not(.rating-hidden)){width:1.75rem;height:1.75rem}.rating.rating-xl :where(:not(.rating-hidden)){width:2rem;height:2rem}:where(.navbar){position:relative}.dropdown-right{--anchor-h:right;--anchor-v:span-bottom}.dropdown-right .dropdown-content{transform-origin:0;inset-inline-start:100%;top:0;bottom:auto}.dropdown-left{--anchor-h:left;--anchor-v:span-bottom}.dropdown-left .dropdown-content{transform-origin:100%;inset-inline-end:100%;top:0;bottom:auto}.dropdown-end{--anchor-h:span-left}.dropdown-end :where(.dropdown-content){inset-inline-end:0;translate:0}[dir=rtl] :is(.dropdown-end :where(.dropdown-content)){translate:0}.dropdown-end.dropdown-left{--anchor-h:left;--anchor-v:span-top}.dropdown-end.dropdown-left .dropdown-content{top:auto;bottom:0}.dropdown-end.dropdown-right{--anchor-h:right;--anchor-v:span-top}.dropdown-end.dropdown-right .dropdown-content{top:auto;bottom:0}: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}.input-sm{--size:calc(var(--size-field,.25rem)*8);font-size:max(var(--font-size,.75rem),.75rem)}.input-sm[type=number]::-webkit-inner-spin-button{margin-block:-.5rem;margin-inline-end:-.75rem}.avatar-placeholder>div{justify-content:center;align-items:center;display:flex}.btn-circle{width:var(--size);height:var(--size);border-radius:3.40282e38px;padding-inline:0}.btn-block{width:100%}.badge-ghost{border-color:var(--color-base-200);background-color:var(--color-base-200);color:var(--color-base-content);background-image:none}.badge-soft{color:var(--badge-color,var(--color-base-content));background-color:var(--badge-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.badge-soft{background-color:color-mix(in oklab,var(--badge-color,var(--color-base-content))8%,var(--color-base-100))}}.badge-soft{border-color:var(--badge-color,var(--color-base-content))}@supports (color:color-mix(in lab, red, red)){.badge-soft{border-color:color-mix(in oklab,var(--badge-color,var(--color-base-content))10%,var(--color-base-100))}}.badge-soft{background-image:none}.badge-outline{color:var(--badge-color);--badge-bg:#0000;background-image:none;border-color:currentColor}.table-zebra tbody tr:where(:nth-child(2n)),.table-zebra tbody tr:where(:nth-child(2n)) :where(.table-pin-cols tr th){background-color:var(--color-base-200)}@media (hover:hover){:is(.table-zebra tbody tr.row-hover,.table-zebra tbody tr.row-hover:where(:nth-child(2n))):hover{background-color:var(--color-base-300)}}.checkbox-sm{--size:calc(var(--size-selector,.25rem)*5);padding:.1875rem}.badge-lg{--size:calc(var(--size-selector,.25rem)*7);font-size:1rem}.badge-md{--size:calc(var(--size-selector,.25rem)*6);font-size:.875rem}.badge-sm{--size:calc(var(--size-selector,.25rem)*5);font-size:.75rem}.badge-xs{--size:calc(var(--size-selector,.25rem)*4);font-size:.625rem}.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)}.link-primary{color:var(--color-primary)}@media (hover:hover){.link-primary:hover{color:var(--color-primary)}@supports (color:color-mix(in lab, red, red)){.link-primary:hover{color:color-mix(in oklab,var(--color-primary)80%,#000)}}}.progress-error{color:var(--color-error)}.progress-success{color:var(--color-success)}.progress-warning{color:var(--color-warning)}.btn-lg{--fontsize:1.125rem;--btn-p:1.25rem;--size:calc(var(--size-field,.25rem)*12)}.btn-sm{--fontsize:.75rem;--btn-p:.75rem;--size:calc(var(--size-field,.25rem)*8)}.btn-xs{--fontsize:.6875rem;--btn-p:.5rem;--size:calc(var(--size-field,.25rem)*6)}.card-lg .card-body{--card-p:2rem;--card-fs:1rem}.card-lg .card-title{--cardtitle-fs:1.25rem}.card-sm .card-body{--card-p:1rem;--card-fs:.75rem}.card-sm .card-title{--cardtitle-fs:1rem}.badge-accent{--badge-color:var(--color-accent);--badge-fg:var(--color-accent-content)}.badge-info{--badge-color:var(--color-info);--badge-fg:var(--color-info-content)}.badge-primary{--badge-color:var(--color-primary);--badge-fg:var(--color-primary-content)}.badge-secondary{--badge-color:var(--color-secondary);--badge-fg:var(--color-secondary-content)}.badge-success{--badge-color:var(--color-success);--badge-fg:var(--color-success-content)}.badge-warning{--badge-color:var(--color-warning);--badge-fg:var(--color-warning-content)}.card-border{border:var(--border)solid var(--color-base-200)}}.pointer-events-none{pointer-events:none}.collapse:not(td,tr,colgroup){visibility:revert-layer}.validator:user-invalid~.validator-hint{display:revert-layer}.validator:has(:user-invalid)~.validator-hint{display:revert-layer}:is(.validator[aria-invalid]:not([aria-invalid=false]),.validator:has([aria-invalid]:not([aria-invalid=false])))~.validator-hint{display:revert-layer}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.top-1{top:calc(var(--spacing)*1)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing)*2)}.top-full{top:100%}.right-2{right:calc(var(--spacing)*2)}.right-full{right:100%}.bottom-0{bottom:calc(var(--spacing)*0)}.left-0{left:calc(var(--spacing)*0)}.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}}.z-8{z-index:8}.z-50{z-index:50}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing)*0)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-auto{margin-inline:auto}.my-2{margin-block:calc(var(--spacing)*2)}.my-4{margin-block:calc(var(--spacing)*4)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-12{margin-top:calc(var(--spacing)*12)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-7{margin-left:calc(var(--spacing)*7)}.ml-auto{margin-left:auto}.kbd{box-shadow:none}.alert{border-width:var(--border);border-color:var(--alert-border-color,var(--color-base-200))}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:root .prose{--tw-prose-body:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-body:color-mix(in oklab,var(--color-base-content)80%,#0000)}}:root .prose{--tw-prose-headings:var(--color-base-content);--tw-prose-lead:var(--color-base-content);--tw-prose-links:var(--color-base-content);--tw-prose-bold:var(--color-base-content);--tw-prose-counters:var(--color-base-content);--tw-prose-bullets:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-bullets:color-mix(in oklab,var(--color-base-content)50%,#0000)}}:root .prose{--tw-prose-hr:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-hr:color-mix(in oklab,var(--color-base-content)20%,#0000)}}:root .prose{--tw-prose-quotes:var(--color-base-content);--tw-prose-quote-borders:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-quote-borders:color-mix(in oklab,var(--color-base-content)20%,#0000)}}:root .prose{--tw-prose-captions:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-captions:color-mix(in oklab,var(--color-base-content)50%,#0000)}}:root .prose{--tw-prose-code:var(--color-base-content);--tw-prose-pre-code:var(--color-neutral-content);--tw-prose-pre-bg:var(--color-neutral);--tw-prose-th-borders:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-th-borders:color-mix(in oklab,var(--color-base-content)50%,#0000)}}:root .prose{--tw-prose-td-borders:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-td-borders:color-mix(in oklab,var(--color-base-content)20%,#0000)}}:root .prose{--tw-prose-kbd:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){:root .prose{--tw-prose-kbd:color-mix(in oklab,var(--color-base-content)80%,#0000)}}:root .prose :where(code):not(pre>code){background-color:var(--color-base-200);border-radius:var(--radius-selector);border:var(--border)solid var(--color-base-300);font-weight:inherit;padding-block:.2em;padding-inline:.5em}:root .prose :where(code):not(pre>code):before,:root .prose :where(code):not(pre>code):after{display:none}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-8{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.size-12{width:calc(var(--spacing)*12);height:calc(var(--spacing)*12)}.size-16{width:calc(var(--spacing)*16);height:calc(var(--spacing)*16)}.size-20{width:calc(var(--spacing)*20);height:calc(var(--spacing)*20)}.size-24{width:calc(var(--spacing)*24);height:calc(var(--spacing)*24)}.size-\[1\.1rem\]{width:1.1rem;height:1.1rem}.h-7{height:calc(var(--spacing)*7)}.h-9{height:calc(var(--spacing)*9)}.h-16{height:calc(var(--spacing)*16)}.max-h-75{max-height:calc(var(--spacing)*75)}.min-h-60{min-height:calc(var(--spacing)*60)}.min-h-\[60vh\]{min-height:60vh}.min-h-\[calc\(100vh-4rem\)\]{min-height:calc(100vh - 4rem)}.w-0{width:calc(var(--spacing)*0)}.w-7{width:calc(var(--spacing)*7)}.w-12{width:calc(var(--spacing)*12)}.w-20{width:calc(var(--spacing)*20)}.w-40{width:calc(var(--spacing)*40)}.w-52{width:calc(var(--spacing)*52)}.w-62{width:calc(var(--spacing)*62)}.w-full{width:100%}.max-w-4xl{max-width:var(--container-4xl)}.max-w-40{max-width:calc(var(--spacing)*40)}.max-w-\[200px\]{max-width:200px}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[150px\]{min-width:150px}.flex-1{flex:1}.flex-shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.-translate-y-1{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-12>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*12)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*12)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-4{column-gap:calc(var(--spacing)*4)}.gap-y-2{row-gap:calc(var(--spacing)*2)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.scroll-smooth{scroll-behavior:smooth}.rounded{border-radius:.25rem}.rounded-box{border-radius:var(--radius-box);border-radius:var(--radius-box)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-3{border-left-style:var(--tw-border-style);border-left-width:3px}.border-base-300{border-color:var(--color-base-300)}.border-error{border-color:var(--color-error)}.bg-base-100{background-color:var(--color-base-100)}.bg-base-200{background-color:var(--color-base-200)}.bg-base-300{background-color:var(--color-base-300)}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-neutral{background-color:var(--color-neutral)}.bg-primary{background-color:var(--color-primary)}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-primary{--tw-gradient-from:var(--color-primary);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.fill-amber-400{fill:var(--color-amber-400)}.fill-none{fill:none}.stroke-amber-400{stroke:var(--color-amber-400)}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing)*0)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.py-2{padding-block:calc(var(--spacing)*2)}.py-4{padding-block:calc(var(--spacing)*4)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pb-24{padding-bottom:calc(var(--spacing)*24)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.whitespace-nowrap{white-space:nowrap}.text-amber-400{color:var(--color-amber-400)}.text-base-content,.text-base-content\/50{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.text-base-content\/50{color:color-mix(in oklab,var(--color-base-content)50%,transparent)}}.text-base-content\/60{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.text-base-content\/60{color:color-mix(in oklab,var(--color-base-content)60%,transparent)}}.text-base-content\/70{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.text-base-content\/70{color:color-mix(in oklab,var(--color-base-content)70%,transparent)}}.text-base-content\/80{color:var(--color-base-content)}@supports (color:color-mix(in lab, red, red)){.text-base-content\/80{color:color-mix(in oklab,var(--color-base-content)80%,transparent)}}.text-error{color:var(--color-error)}.text-gray-500{color:var(--color-gray-500)}.text-neutral{color:var(--color-neutral)}.text-neutral-content{color:var(--color-neutral-content)}.text-primary{color:var(--color-primary)}.text-primary-content{color:var(--color-primary-content)}.text-success{color:var(--color-success)}.text-white{color:var(--color-white)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.prose :where(.btn-link):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:none}@layer daisyui.l1{.btn-link{--btn-border:#0000;--btn-bg:#0000;--btn-noise:none;--btn-shadow:"";outline-color:currentColor;text-decoration-line:underline}.btn-link:not(.btn-disabled,.btn:disabled,.btn[disabled]){--btn-fg:var(--btn-color,var(--color-primary))}.btn-link:is(.btn-active,:hover,:active:focus,:focus-visible){--btn-border:#0000;--btn-bg:#0000}.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}}}.no-underline,.prose :where(.btn-link):not(:where([class~=not-prose],[class~=not-prose] *)){text-decoration-line:none}.opacity-0{opacity:0}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);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,)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a))drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a)drop-shadow(0 1px 1px #0000000f);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,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.\[rows\:\%v\]{rows:%v}@media (hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}@media (min-width:40rem){.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:48rem){.md\:w-\[calc\(50\%-0\.75rem\)\]{width:calc(50% - .75rem)}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}}@media (min-width:64rem){.lg\:w-\[calc\(33\.333\%-1rem\)\]{width:calc(33.333% - 1rem)}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[3fr_2fr\]{grid-template-columns:3fr 2fr}}@media (min-width:80rem){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}:root{--shadow-card-hover:0 8px 25px oklch(75% .12 175/.15),0 4px 12px #0000001a}[data-theme=dark]{--shadow-card-hover:0 8px 25px oklch(78% .12 175/.1),0 4px 12px #0003}@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-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/pkg/appview/public/js/bundle.min.js b/pkg/appview/public/js/bundle.min.js new file mode 100644 index 0000000..b205518 --- /dev/null +++ b/pkg/appview/public/js/bundle.min.js @@ -0,0 +1,139 @@ +var qe=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){return getInputValues(e,t||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.8"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function parseInterval(e){if(e==null)return;let t=NaN;return e.slice(-2)=="ms"?t=parseFloat(e.slice(0,-2)):e.slice(-1)=="s"?t=parseFloat(e.slice(0,-1))*1e3:e.slice(-1)=="m"?t=parseFloat(e.slice(0,-1))*1e3*60:t=parseFloat(e),isNaN(t)?void 0:t}function getRawAttribute(e,t){return e instanceof Element&&e.getAttribute(t)}function hasAttribute(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function getAttributeValue(e,t){return getRawAttribute(e,t)||getRawAttribute(e,"data-"+t)}function parentElt(e){let t=e.parentElement;return!t&&e.parentNode instanceof ShadowRoot?e.parentNode:t}function getDocument(){return document}function getRootNode(e,t){return e.getRootNode?e.getRootNode({composed:t}):getDocument()}function getClosestMatch(e,t){for(;e&&!t(e);)e=parentElt(e);return e||null}function getAttributeValueWithDisinheritance(e,t,r){let a=getAttributeValue(t,r),o=getAttributeValue(t,"hx-disinherit");var s=getAttributeValue(t,"hx-inherit");if(e!==t){if(htmx.config.disableInheritance)return s&&(s==="*"||s.split(" ").indexOf(r)>=0)?a:null;if(o&&(o==="*"||o.split(" ").indexOf(r)>=0))return"unset"}return a}function getClosestAttributeValue(e,t){let r=null;if(getClosestMatch(e,function(a){return!!(r=getAttributeValueWithDisinheritance(e,asElement(a),t))}),r!=="unset")return r}function matches(e,t){return e instanceof Element&&e.matches(t)}function getStartTag(e){let r=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(e);return r?r[1].toLowerCase():""}function parseHTML(e){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(e):new DOMParser().parseFromString(e,"text/html")}function takeChildrenFor(e,t){for(;t.childNodes.length>0;)e.append(t.childNodes[0])}function duplicateScript(e){let t=getDocument().createElement("script");return forEach(e.attributes,function(r){t.setAttribute(r.name,r.value)}),t.textContent=e.textContent,t.async=!1,htmx.config.inlineScriptNonce&&(t.nonce=htmx.config.inlineScriptNonce),t}function isJavaScriptScriptNode(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function normalizeScriptTags(e){Array.from(e.querySelectorAll("script")).forEach(t=>{if(isJavaScriptScriptNode(t)){let r=duplicateScript(t),a=t.parentNode;try{a.insertBefore(r,t)}catch(o){logError(o)}finally{t.remove()}}})}function makeFragment(e){let t=e.replace(/]*)?>[\s\S]*?<\/head>/i,""),r=getStartTag(t),a;if(r==="html"){a=new DocumentFragment;let s=parseHTML(e);takeChildrenFor(a,s.body),a.title=s.title}else if(r==="body"){a=new DocumentFragment;let s=parseHTML(t);takeChildrenFor(a,s.body),a.title=s.title}else{let s=parseHTML(''+t+"");a=s.querySelector("template").content,a.title=s.title;var o=a.querySelector("title");o&&o.parentNode===a&&(o.remove(),a.title=o.innerText)}return a&&(htmx.config.allowScriptTags?normalizeScriptTags(a):a.querySelectorAll("script").forEach(s=>s.remove())),a}function maybeCall(e){e&&e()}function isType(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function isFunction(e){return typeof e=="function"}function isRawObject(e){return isType(e,"Object")}function getInternalData(e){let t="htmx-internal-data",r=e[t];return r||(r=e[t]={}),r}function toArray(e){let t=[];if(e)for(let r=0;r=0}function bodyContains(e){return e.getRootNode({composed:!0})===document}function splitOnWhitespace(e){return e.trim().split(/\s+/)}function mergeObjects(e,t){for(let r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}function parseJSON(e){try{return JSON.parse(e)}catch(t){return logError(t),null}}function canAccessLocalStorage(){let e="htmx:sessionStorageTest";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch{return!1}}function normalizePath(e){let t=new URL(e,"http://x");return t&&(e=t.pathname+t.search),e!="/"&&(e=e.replace(/\/+$/,"")),e}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(e){return htmx.on("htmx:load",function(r){e(r.detail.elt)})}function logAll(){htmx.logger=function(e,t,r){console&&console.log(t,e,r)}}function logNone(){htmx.logger=null}function find(e,t){return typeof e!="string"?e.querySelector(t):find(getDocument(),e)}function findAll(e,t){return typeof e!="string"?e.querySelectorAll(t):findAll(getDocument(),e)}function getWindow(){return window}function removeElement(e,t){e=resolveTarget(e),t?getWindow().setTimeout(function(){removeElement(e),e=null},t):parentElt(e).removeChild(e)}function asElement(e){return e instanceof Element?e:null}function asHtmlElement(e){return e instanceof HTMLElement?e:null}function asString(e){return typeof e=="string"?e:null}function asParentNode(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function addClassToElement(e,t,r){e=asElement(resolveTarget(e)),e&&(r?getWindow().setTimeout(function(){addClassToElement(e,t),e=null},r):e.classList&&e.classList.add(t))}function removeClassFromElement(e,t,r){let a=asElement(resolveTarget(e));a&&(r?getWindow().setTimeout(function(){removeClassFromElement(a,t),a=null},r):a.classList&&(a.classList.remove(t),a.classList.length===0&&a.removeAttribute("class")))}function toggleClassOnElement(e,t){e=resolveTarget(e),e.classList.toggle(t)}function takeClassForElement(e,t){e=resolveTarget(e),forEach(e.parentElement.children,function(r){removeClassFromElement(r,t)}),addClassToElement(asElement(e),t)}function closest(e,t){return e=asElement(resolveTarget(e)),e?e.closest(t):null}function startsWith(e,t){return e.substring(0,t.length)===t}function endsWith(e,t){return e.substring(e.length-t.length)===t}function normalizeSelector(e){let t=e.trim();return startsWith(t,"<")&&endsWith(t,"/>")?t.substring(1,t.length-2):t}function querySelectorAllExt(e,t,r){if(t.indexOf("global ")===0)return querySelectorAllExt(e,t.slice(7),!0);e=resolveTarget(e);let a=[];{let l=0,f=0;for(let n=0;n"&&l--}f0;){let l=normalizeSelector(a.shift()),f;l.indexOf("closest ")===0?f=closest(asElement(e),normalizeSelector(l.slice(8))):l.indexOf("find ")===0?f=find(asParentNode(e),normalizeSelector(l.slice(5))):l==="next"||l==="nextElementSibling"?f=asElement(e).nextElementSibling:l.indexOf("next ")===0?f=scanForwardQuery(e,normalizeSelector(l.slice(5)),!!r):l==="previous"||l==="previousElementSibling"?f=asElement(e).previousElementSibling:l.indexOf("previous ")===0?f=scanBackwardsQuery(e,normalizeSelector(l.slice(9)),!!r):l==="document"?f=document:l==="window"?f=window:l==="body"?f=document.body:l==="root"?f=getRootNode(e,!!r):l==="host"?f=e.getRootNode().host:s.push(l),f&&o.push(f)}if(s.length>0){let l=s.join(","),f=asParentNode(getRootNode(e,!!r));o.push(...toArray(f.querySelectorAll(l)))}return o}var scanForwardQuery=function(e,t,r){let a=asParentNode(getRootNode(e,r)).querySelectorAll(t);for(let o=0;o=0;o--){let s=a[o];if(s.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING)return s}};function querySelectorExt(e,t){return typeof e!="string"?querySelectorAllExt(e,t)[0]:querySelectorAllExt(getDocument().body,e)[0]}function resolveTarget(e,t){return typeof e=="string"?find(asParentNode(t)||document,e):e}function processEventArgs(e,t,r,a){return isFunction(t)?{target:getDocument().body,event:asString(e),listener:t,options:r}:{target:resolveTarget(e),event:asString(t),listener:r,options:a}}function addEventListenerImpl(e,t,r,a){return ready(function(){let s=processEventArgs(e,t,r,a);s.target.addEventListener(s.event,s.listener,s.options)}),isFunction(t)?t:r}function removeEventListenerImpl(e,t,r){return ready(function(){let a=processEventArgs(e,t,r);a.target.removeEventListener(a.event,a.listener)}),isFunction(t)?t:r}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(e,t){let r=getClosestAttributeValue(e,t);if(r){if(r==="this")return[findThisElement(e,t)];{let a=querySelectorAllExt(e,r);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(r)){let s=asElement(getClosestMatch(e,function(l){return l!==e&&hasAttribute(asElement(l),t)}));s&&a.push(...findAttributeTargets(s,t))}return a.length===0?(logError('The selector "'+r+'" on '+t+" returned no matches!"),[DUMMY_ELT]):a}}}function findThisElement(e,t){return asElement(getClosestMatch(e,function(r){return getAttributeValue(asElement(r),t)!=null}))}function getTarget(e){let t=getClosestAttributeValue(e,"hx-target");return t?t==="this"?findThisElement(e,"hx-target"):querySelectorExt(e,t):getInternalData(e).boosted?getDocument().body:e}function shouldSettleAttribute(e){return htmx.config.attributesToSettle.includes(e)}function cloneAttributes(e,t){forEach(Array.from(e.attributes),function(r){!t.hasAttribute(r.name)&&shouldSettleAttribute(r.name)&&e.removeAttribute(r.name)}),forEach(t.attributes,function(r){shouldSettleAttribute(r.name)&&e.setAttribute(r.name,r.value)})}function isInlineSwap(e,t){let r=getExtensions(t);for(let a=0;a0?(s=e.substring(0,e.indexOf(":")),o=e.substring(e.indexOf(":")+1)):s=e),t.removeAttribute("hx-swap-oob"),t.removeAttribute("data-hx-swap-oob");let l=querySelectorAllExt(a,o,!1);return l.length?(forEach(l,function(f){let n,u=t.cloneNode(!0);n=getDocument().createDocumentFragment(),n.appendChild(u),isInlineSwap(s,f)||(n=asParentNode(u));let m={shouldSwap:!0,target:f,fragment:n};triggerEvent(f,"htmx:oobBeforeSwap",m)&&(f=m.target,m.shouldSwap&&(handlePreservedElements(n),swapWithStyle(s,f,f,n,r),restorePreservedElements()),forEach(r.elts,function(i){triggerEvent(i,"htmx:oobAfterSwap",m)}))}),t.parentNode.removeChild(t)):(t.parentNode.removeChild(t),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:t})),e}function restorePreservedElements(){let e=find("#--htmx-preserve-pantry--");if(e){for(let t of[...e.children]){let r=find("#"+t.id);r.parentNode.moveBefore(t,r),r.remove()}e.remove()}}function handlePreservedElements(e){forEach(findAll(e,"[hx-preserve], [data-hx-preserve]"),function(t){let r=getAttributeValue(t,"id"),a=getDocument().getElementById(r);if(a!=null)if(t.moveBefore){let o=find("#--htmx-preserve-pantry--");o==null&&(getDocument().body.insertAdjacentHTML("afterend",""),o=find("#--htmx-preserve-pantry--")),o.moveBefore(a,null)}else t.parentNode.replaceChild(a,t)})}function handleAttributes(e,t,r){forEach(t.querySelectorAll("[id]"),function(a){let o=getRawAttribute(a,"id");if(o&&o.length>0){let s=o.replace("'","\\'"),l=a.tagName.replace(":","\\:"),f=asParentNode(e),n=f&&f.querySelector(l+"[id='"+s+"']");if(n&&n!==f){let u=a.cloneNode();cloneAttributes(a,n),r.tasks.push(function(){cloneAttributes(a,u)})}}})}function makeAjaxLoadTask(e){return function(){removeClassFromElement(e,htmx.config.addedClass),processNode(asElement(e)),processFocus(asParentNode(e)),triggerEvent(e,"htmx:load")}}function processFocus(e){let t="[autofocus]",r=asHtmlElement(matches(e,t)?e:e.querySelector(t));r?.focus()}function insertNodesBefore(e,t,r,a){for(handleAttributes(e,r,a);r.childNodes.length>0;){let o=r.firstChild;addClassToElement(asElement(o),htmx.config.addedClass),e.insertBefore(o,t),o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE&&a.tasks.push(makeAjaxLoadTask(o))}}function stringHash(e,t){let r=0;for(;r0}function swap(e,t,r,a){a||(a={});let o=null,s=null,l=function(){maybeCall(a.beforeSwapCallback),e=resolveTarget(e);let u=a.contextElement?getRootNode(a.contextElement,!1):getDocument(),m=document.activeElement,i={};i={elt:m,start:m?m.selectionStart:null,end:m?m.selectionEnd:null};let c=makeSettleInfo(e);if(r.swapStyle==="textContent")e.textContent=t;else{let d=makeFragment(t);if(c.title=a.title||d.title,a.historyRequest&&(d=d.querySelector("[hx-history-elt],[data-hx-history-elt]")||d),a.selectOOB){let p=a.selectOOB.split(",");for(let g=0;g0?getWindow().setTimeout(x,r.settleDelay):x()},f=htmx.config.globalViewTransitions;r.hasOwnProperty("transition")&&(f=r.transition);let n=a.contextElement||getDocument();if(f&&triggerEvent(n,"htmx:beforeTransition",a.eventInfo)&&typeof Promise<"u"&&document.startViewTransition){let u=new Promise(function(i,c){o=i,s=c}),m=l;l=function(){document.startViewTransition(function(){return m(),u})}}try{r?.swapDelay&&r.swapDelay>0?getWindow().setTimeout(l,r.swapDelay):l()}catch(u){throw triggerErrorEvent(n,"htmx:swapError",a.eventInfo),maybeCall(s),u}}function handleTriggerHeader(e,t,r){let a=e.getResponseHeader(t);if(a.indexOf("{")===0){let o=parseJSON(a);for(let s in o)if(o.hasOwnProperty(s)){let l=o[s];isRawObject(l)?r=l.target!==void 0?l.target:r:l={value:l},triggerEvent(r,s,l)}}else{let o=a.split(",");for(let s=0;s0;){let l=t[0];if(l==="]"){if(a--,a===0){s===null&&(o=o+"true"),t.shift(),o+=")})";try{let f=maybeEval(e,function(){return Function(o)()},function(){return!0});return f.source=o,f}catch(f){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:f,source:o}),null}}}else l==="["&&a++;isPossibleRelativeReference(l,s,r)?o+="(("+r+"."+l+") ? ("+r+"."+l+") : (window."+l+"))":o=o+l,s=t.shift()}}}function consumeUntil(e,t){let r="";for(;e.length>0&&!t.test(e[0]);)r+=e.shift();return r}function consumeCSSSelector(e){let t;return e.length>0&&COMBINED_SELECTOR_START.test(e[0])?(e.shift(),t=consumeUntil(e,COMBINED_SELECTOR_END).trim(),e.shift()):t=consumeUntil(e,WHITESPACE_OR_COMMA),t}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(e,t,r){let a=[],o=tokenizeString(t);do{consumeUntil(o,NOT_WHITESPACE);let f=o.length,n=consumeUntil(o,/[,\[\s]/);if(n!=="")if(n==="every"){let u={trigger:"every"};consumeUntil(o,NOT_WHITESPACE),u.pollInterval=parseInterval(consumeUntil(o,/[,\[\s]/)),consumeUntil(o,NOT_WHITESPACE);var s=maybeGenerateConditional(e,o,"event");s&&(u.eventFilter=s),a.push(u)}else{let u={trigger:n};var s=maybeGenerateConditional(e,o,"event");for(s&&(u.eventFilter=s),consumeUntil(o,NOT_WHITESPACE);o.length>0&&o[0]!==",";){let i=o.shift();if(i==="changed")u.changed=!0;else if(i==="once")u.once=!0;else if(i==="consume")u.consume=!0;else if(i==="delay"&&o[0]===":")o.shift(),u.delay=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA));else if(i==="from"&&o[0]===":"){if(o.shift(),COMBINED_SELECTOR_START.test(o[0]))var l=consumeCSSSelector(o);else{var l=consumeUntil(o,WHITESPACE_OR_COMMA);if(l==="closest"||l==="find"||l==="next"||l==="previous"){o.shift();let x=consumeCSSSelector(o);x.length>0&&(l+=" "+x)}}u.from=l}else i==="target"&&o[0]===":"?(o.shift(),u.target=consumeCSSSelector(o)):i==="throttle"&&o[0]===":"?(o.shift(),u.throttle=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA))):i==="queue"&&o[0]===":"?(o.shift(),u.queue=consumeUntil(o,WHITESPACE_OR_COMMA)):i==="root"&&o[0]===":"?(o.shift(),u[i]=consumeCSSSelector(o)):i==="threshold"&&o[0]===":"?(o.shift(),u[i]=consumeUntil(o,WHITESPACE_OR_COMMA)):triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()});consumeUntil(o,NOT_WHITESPACE)}a.push(u)}o.length===f&&triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()}),consumeUntil(o,NOT_WHITESPACE)}while(o[0]===","&&o.shift());return r&&(r[t]=a),a}function getTriggerSpecs(e){let t=getAttributeValue(e,"hx-trigger"),r=[];if(t){let a=htmx.config.triggerSpecsCache;r=a&&a[t]||parseAndCacheTrigger(e,t,a)}return r.length>0?r:matches(e,"form")?[{trigger:"submit"}]:matches(e,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(e,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(e){getInternalData(e).cancelled=!0}function processPolling(e,t,r){let a=getInternalData(e);a.timeout=getWindow().setTimeout(function(){bodyContains(e)&&a.cancelled!==!0&&(maybeFilterEvent(r,e,makeEvent("hx:poll:trigger",{triggerSpec:r,target:e}))||t(e),processPolling(e,t,r))},r.pollInterval)}function isLocalLink(e){return location.hostname===e.hostname&&getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")!==0}function eltIsDisabled(e){return closest(e,htmx.config.disableSelector)}function boostElement(e,t,r){if(e instanceof HTMLAnchorElement&&isLocalLink(e)&&(e.target===""||e.target==="_self")||e.tagName==="FORM"&&String(getRawAttribute(e,"method")).toLowerCase()!=="dialog"){t.boosted=!0;let a,o;if(e.tagName==="A")a="get",o=getRawAttribute(e,"href");else{let s=getRawAttribute(e,"method");a=s?s.toLowerCase():"get",o=getRawAttribute(e,"action"),(o==null||o==="")&&(o=location.href),a==="get"&&o.includes("?")&&(o=o.replace(/\?[^#]+/,""))}r.forEach(function(s){addEventListener(e,function(l,f){let n=asElement(l);if(eltIsDisabled(n)){cleanUpElement(n);return}issueAjaxRequest(a,o,n,f)},t,s,!0)})}}function shouldCancel(e,t){if(e.type==="submit"&&t.tagName==="FORM")return!0;if(e.type==="click"){let r=t.closest('input[type="submit"], button');if(r&&r.form&&r.type==="submit")return!0;let a=t.closest("a"),o=/^#.+/;if(a&&a.href&&!o.test(a.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(e,t){return getInternalData(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function maybeFilterEvent(e,t,r){let a=e.eventFilter;if(a)try{return a.call(t,r)!==!0}catch(o){let s=a.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:o,source:s}),!0}return!1}function addEventListener(e,t,r,a,o){let s=getInternalData(e),l;a.from?l=querySelectorAllExt(e,a.from):l=[e],a.changed&&("lastValue"in s||(s.lastValue=new WeakMap),l.forEach(function(f){s.lastValue.has(a)||s.lastValue.set(a,new WeakMap),s.lastValue.get(a).set(f,f.value)})),forEach(l,function(f){let n=function(u){if(!bodyContains(e)){f.removeEventListener(a.trigger,n);return}if(ignoreBoostedAnchorCtrlClick(e,u)||((o||shouldCancel(u,f))&&u.preventDefault(),maybeFilterEvent(a,e,u)))return;let m=getInternalData(u);if(m.triggerSpec=a,m.handledFor==null&&(m.handledFor=[]),m.handledFor.indexOf(e)<0){if(m.handledFor.push(e),a.consume&&u.stopPropagation(),a.target&&u.target&&!matches(asElement(u.target),a.target))return;if(a.once){if(s.triggeredOnce)return;s.triggeredOnce=!0}if(a.changed){let i=u.target,c=i.value,x=s.lastValue.get(a);if(x.has(i)&&x.get(i)===c)return;x.set(i,c)}if(s.delayed&&clearTimeout(s.delayed),s.throttle)return;a.throttle>0?s.throttle||(triggerEvent(e,"htmx:trigger"),t(e,u),s.throttle=getWindow().setTimeout(function(){s.throttle=null},a.throttle)):a.delay>0?s.delayed=getWindow().setTimeout(function(){triggerEvent(e,"htmx:trigger"),t(e,u)},a.delay):(triggerEvent(e,"htmx:trigger"),t(e,u))}};r.listenerInfos==null&&(r.listenerInfos=[]),r.listenerInfos.push({trigger:a.trigger,listener:n,on:f}),f.addEventListener(a.trigger,n)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){maybeReveal(e)}))},200))}function maybeReveal(e){!hasAttribute(e,"data-hx-revealed")&&isScrolledIntoView(e)&&(e.setAttribute("data-hx-revealed","true"),getInternalData(e).initHash?triggerEvent(e,"revealed"):e.addEventListener("htmx:afterProcessNode",function(){triggerEvent(e,"revealed")},{once:!0}))}function loadImmediately(e,t,r,a){let o=function(){r.loaded||(r.loaded=!0,triggerEvent(e,"htmx:trigger"),t(e))};a>0?getWindow().setTimeout(o,a):o()}function processVerbs(e,t,r){let a=!1;return forEach(VERBS,function(o){if(hasAttribute(e,"hx-"+o)){let s=getAttributeValue(e,"hx-"+o);a=!0,t.path=s,t.verb=o,r.forEach(function(l){addTriggerHandler(e,l,t,function(f,n){let u=asElement(f);if(eltIsDisabled(u)){cleanUpElement(u);return}issueAjaxRequest(o,s,u,n)})})}}),a}function addTriggerHandler(e,t,r,a){if(t.trigger==="revealed")initScrollHandler(),addEventListener(e,a,r,t),maybeReveal(asElement(e));else if(t.trigger==="intersect"){let o={};t.root&&(o.root=querySelectorExt(e,t.root)),t.threshold&&(o.threshold=parseFloat(t.threshold)),new IntersectionObserver(function(l){for(let f=0;f0?(r.polling=!0,processPolling(asElement(e),a,t)):addEventListener(e,a,r,t)}function shouldProcessHxOn(e){let t=asElement(e);if(!t)return!1;let r=t.attributes;for(let a=0;a", "+s).join(""))}else return[]}function maybeSetLastButtonClicked(e){let t=getTargetButton(e.target),r=getRelatedFormData(e);r&&(r.lastButtonClicked=t)}function maybeUnsetLastButtonClicked(e){let t=getRelatedFormData(e);t&&(t.lastButtonClicked=null)}function getTargetButton(e){return closest(asElement(e),"button, input[type='submit']")}function getRelatedForm(e){return e.form||closest(e,"form")}function getRelatedFormData(e){let t=getTargetButton(e.target);if(!t)return;let r=getRelatedForm(t);if(r)return getInternalData(r)}function initButtonTracking(e){e.addEventListener("click",maybeSetLastButtonClicked),e.addEventListener("focusin",maybeSetLastButtonClicked),e.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(e,t,r){let a=getInternalData(e);Array.isArray(a.onHandlers)||(a.onHandlers=[]);let o,s=function(l){maybeEval(e,function(){eltIsDisabled(e)||(o||(o=new Function("event",r)),o.call(e,l))})};e.addEventListener(t,s),a.onHandlers.push({event:t,listener:s})}function processHxOnWildcard(e){deInitOnHandlers(e);for(let t=0;thtmx.config.historyCacheSize;)s.shift();for(;s.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(s));break}catch(f){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:f,cache:s}),s.shift()}}function getCachedHistory(e){if(!canAccessLocalStorage())return null;e=normalizePath(e);let t=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let r=0;r=200&&this.status<400?(a.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",a),swap(a.historyElt,a.response,r,{contextElement:a.historyElt,historyRequest:!0}),setCurrentPathForHistory(a.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:e,cacheMiss:!0,serverResponse:a.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",a)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",a)&&t.send()}function restoreHistory(e){saveCurrentPageToHistory(),e=e||location.pathname+location.search;let t=getCachedHistory(e);if(t){let r={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll},a={path:e,item:t,historyElt:getHistoryElement(),swapSpec:r};triggerEvent(getDocument().body,"htmx:historyCacheHit",a)&&(swap(a.historyElt,t.content,r,{contextElement:a.historyElt,title:t.title}),setCurrentPathForHistory(a.path),triggerEvent(getDocument().body,"htmx:historyRestore",a))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(e)}function addRequestIndicatorClasses(e){let t=findAttributeTargets(e,"hx-indicator");return t==null&&(t=[e]),forEach(t,function(r){let a=getInternalData(r);a.requestCount=(a.requestCount||0)+1,r.classList.add.call(r.classList,htmx.config.requestClass)}),t}function disableElements(e){let t=findAttributeTargets(e,"hx-disabled-elt");return t==null&&(t=[]),forEach(t,function(r){let a=getInternalData(r);a.requestCount=(a.requestCount||0)+1,r.setAttribute("disabled",""),r.setAttribute("data-disabled-by-htmx","")}),t}function removeRequestIndicators(e,t){forEach(e.concat(t),function(r){let a=getInternalData(r);a.requestCount=(a.requestCount||1)-1}),forEach(e,function(r){getInternalData(r).requestCount===0&&r.classList.remove.call(r.classList,htmx.config.requestClass)}),forEach(t,function(r){getInternalData(r).requestCount===0&&(r.removeAttribute("disabled"),r.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(e,t){for(let r=0;rt.indexOf(o)<0):a=a.filter(o=>o!==t),r.delete(e),forEach(a,o=>r.append(e,o))}}function getValueFromInput(e){return e instanceof HTMLSelectElement&&e.multiple?toArray(e.querySelectorAll("option:checked")).map(function(t){return t.value}):e instanceof HTMLInputElement&&e.files?toArray(e.files):e.value}function processInputValue(e,t,r,a,o){if(!(a==null||haveSeenNode(e,a))){if(e.push(a),shouldInclude(a)){let s=getRawAttribute(a,"name");addValueToFormData(s,getValueFromInput(a),t),o&&validateElement(a,r)}a instanceof HTMLFormElement&&(forEach(a.elements,function(s){e.indexOf(s)>=0?removeValueFromFormData(s.name,getValueFromInput(s),t):e.push(s),o&&validateElement(s,r)}),new FormData(a).forEach(function(s,l){s instanceof File&&s.name===""||addValueToFormData(l,s,t)}))}}function validateElement(e,t){let r=e;r.willValidate&&(triggerEvent(r,"htmx:validation:validate"),r.checkValidity()||(triggerEvent(r,"htmx:validation:failed",{message:r.validationMessage,validity:r.validity})&&!t.length&&htmx.config.reportValidityOfForms&&r.reportValidity(),t.push({elt:r,message:r.validationMessage,validity:r.validity})))}function overrideFormData(e,t){for(let r of t.keys())e.delete(r);return t.forEach(function(r,a){e.append(a,r)}),e}function getInputValues(e,t){let r=[],a=new FormData,o=new FormData,s=[],l=getInternalData(e);l.lastButtonClicked&&!bodyContains(l.lastButtonClicked)&&(l.lastButtonClicked=null);let f=e instanceof HTMLFormElement&&e.noValidate!==!0||getAttributeValue(e,"hx-validate")==="true";if(l.lastButtonClicked&&(f=f&&l.lastButtonClicked.formNoValidate!==!0),t!=="get"&&processInputValue(r,o,s,getRelatedForm(e),f),processInputValue(r,a,s,e,f),l.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&getRawAttribute(e,"type")==="submit"){let u=l.lastButtonClicked||e,m=getRawAttribute(u,"name");addValueToFormData(m,u.value,o)}let n=findAttributeTargets(e,"hx-include");return forEach(n,function(u){processInputValue(r,a,s,asElement(u),f),matches(u,"form")||forEach(asParentNode(u).querySelectorAll(INPUT_SELECTOR),function(m){processInputValue(r,a,s,m,f)})}),overrideFormData(a,o),{errors:s,formData:a,values:formDataProxy(a)}}function appendParam(e,t,r){e!==""&&(e+="&"),String(r)==="[object Object]"&&(r=JSON.stringify(r));let a=encodeURIComponent(r);return e+=encodeURIComponent(t)+"="+a,e}function urlEncode(e){e=formDataFromObject(e);let t="";return e.forEach(function(r,a){t=appendParam(t,a,r)}),t}function getHeaders(e,t,r){let a={"HX-Request":"true","HX-Trigger":getRawAttribute(e,"id"),"HX-Trigger-Name":getRawAttribute(e,"name"),"HX-Target":getAttributeValue(t,"id"),"HX-Current-URL":location.href};return getValuesForElement(e,"hx-headers",!1,a),r!==void 0&&(a["HX-Prompt"]=r),getInternalData(e).boosted&&(a["HX-Boosted"]="true"),a}function filterValues(e,t){let r=getClosestAttributeValue(t,"hx-params");if(r){if(r==="none")return new FormData;if(r==="*")return e;if(r.indexOf("not ")===0)return forEach(r.slice(4).split(","),function(a){a=a.trim(),e.delete(a)}),e;{let a=new FormData;return forEach(r.split(","),function(o){o=o.trim(),e.has(o)&&e.getAll(o).forEach(function(s){a.append(o,s)})}),a}}else return e}function isAnchorLink(e){return!!getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")>=0}function getSwapSpecification(e,t){let r=t||getClosestAttributeValue(e,"hx-swap"),a={swapStyle:getInternalData(e).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(e).boosted&&!isAnchorLink(e)&&(a.show="top"),r){let l=splitOnWhitespace(r);if(l.length>0)for(let f=0;f0?o.join(":"):null;a.scroll=m,a.scrollTarget=s}else if(n.indexOf("show:")===0){var o=n.slice(5).split(":");let i=o.pop();var s=o.length>0?o.join(":"):null;a.show=i,a.showTarget=s}else if(n.indexOf("focus-scroll:")===0){let u=n.slice(13);a.focusScroll=u=="true"}else f==0?a.swapStyle=n:logError("Unknown modifier in hx-swap: "+n)}}return a}function usesFormData(e){return getClosestAttributeValue(e,"hx-encoding")==="multipart/form-data"||matches(e,"form")&&getRawAttribute(e,"enctype")==="multipart/form-data"}function encodeParamsForBody(e,t,r){let a=null;return withExtensions(t,function(o){a==null&&(a=o.encodeParameters(e,r,t))}),a??(usesFormData(t)?overrideFormData(new FormData,formDataFromObject(r)):urlEncode(r))}function makeSettleInfo(e){return{tasks:[],elts:[e]}}function updateScrollState(e,t){let r=e[0],a=e[e.length-1];if(t.scroll){var o=null;t.scrollTarget&&(o=asElement(querySelectorExt(r,t.scrollTarget))),t.scroll==="top"&&(r||o)&&(o=o||r,o.scrollTop=0),t.scroll==="bottom"&&(a||o)&&(o=o||a,o.scrollTop=o.scrollHeight),typeof t.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}if(t.show){var o=null;if(t.showTarget){let l=t.showTarget;t.showTarget==="window"&&(l="body"),o=asElement(querySelectorExt(r,l))}t.show==="top"&&(r||o)&&(o=o||r,o.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),t.show==="bottom"&&(a||o)&&(o=o||a,o.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(e,t,r,a,o){if(a==null&&(a={}),e==null)return a;let s=getAttributeValue(e,t);if(s){let l=s.trim(),f=r;if(l==="unset")return null;l.indexOf("javascript:")===0?(l=l.slice(11),f=!0):l.indexOf("js:")===0&&(l=l.slice(3),f=!0),l.indexOf("{")!==0&&(l="{"+l+"}");let n;f?n=maybeEval(e,function(){return o?Function("event","return ("+l+")").call(e,o):Function("return ("+l+")").call(e)},{}):n=parseJSON(l);for(let u in n)n.hasOwnProperty(u)&&a[u]==null&&(a[u]=n[u])}return getValuesForElement(asElement(parentElt(e)),t,r,a,o)}function maybeEval(e,t,r){return htmx.config.allowEval?t():(triggerErrorEvent(e,"htmx:evalDisallowedError"),r)}function getHXVarsForElement(e,t,r){return getValuesForElement(e,"hx-vars",!0,r,t)}function getHXValsForElement(e,t,r){return getValuesForElement(e,"hx-vals",!1,r,t)}function getExpressionVars(e,t){return mergeObjects(getHXVarsForElement(e,t),getHXValsForElement(e,t))}function safelySetHeaderValue(e,t,r){if(r!==null)try{e.setRequestHeader(t,r)}catch{e.setRequestHeader(t,encodeURIComponent(r)),e.setRequestHeader(t+"-URI-AutoEncoded","true")}}function getPathFromResponse(e){if(e.responseURL)try{let t=new URL(e.responseURL);return t.pathname+t.search}catch{triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:e.responseURL})}}function hasHeader(e,t){return t.test(e.getAllResponseHeaders())}function ajaxHelper(e,t,r){if(e=e.toLowerCase(),r){if(r instanceof Element||typeof r=="string")return issueAjaxRequest(e,t,null,null,{targetOverride:resolveTarget(r)||DUMMY_ELT,returnPromise:!0});{let a=resolveTarget(r.target);return(r.target&&!a||r.source&&!a&&!resolveTarget(r.source))&&(a=DUMMY_ELT),issueAjaxRequest(e,t,resolveTarget(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:a,swapOverride:r.swap,select:r.select,returnPromise:!0,push:r.push,replace:r.replace,selectOOB:r.selectOOB})}}else return issueAjaxRequest(e,t,null,null,{returnPromise:!0})}function hierarchyForElt(e){let t=[];for(;e;)t.push(e),e=e.parentElement;return t}function verifyPath(e,t,r){let a=new URL(t,location.protocol!=="about:"?location.href:window.origin),s=(location.protocol!=="about:"?location.origin:window.origin)===a.origin;return htmx.config.selfRequestsOnly&&!s?!1:triggerEvent(e,"htmx:validateUrl",mergeObjects({url:a,sameHost:s},r))}function formDataFromObject(e){if(e instanceof FormData)return e;let t=new FormData;for(let r in e)e.hasOwnProperty(r)&&(e[r]&&typeof e[r].forEach=="function"?e[r].forEach(function(a){t.append(r,a)}):typeof e[r]=="object"&&!(e[r]instanceof Blob)?t.append(r,JSON.stringify(e[r])):t.append(r,e[r]));return t}function formDataArrayProxy(e,t,r){return new Proxy(r,{get:function(a,o){return typeof o=="number"?a[o]:o==="length"?a.length:o==="push"?function(s){a.push(s),e.append(t,s)}:typeof a[o]=="function"?function(){a[o].apply(a,arguments),e.delete(t),a.forEach(function(s){e.append(t,s)})}:a[o]&&a[o].length===1?a[o][0]:a[o]},set:function(a,o,s){return a[o]=s,e.delete(t),a.forEach(function(l){e.append(t,l)}),!0}})}function formDataProxy(e){return new Proxy(e,{get:function(t,r){if(typeof r=="symbol"){let o=Reflect.get(t,r);return typeof o=="function"?function(){return o.apply(e,arguments)}:o}if(r==="toJSON")return()=>Object.fromEntries(e);if(r in t&&typeof t[r]=="function")return function(){return e[r].apply(e,arguments)};let a=e.getAll(r);if(a.length!==0)return a.length===1?a[0]:formDataArrayProxy(t,r,a)},set:function(t,r,a){return typeof r!="string"?!1:(t.delete(r),a&&typeof a.forEach=="function"?a.forEach(function(o){t.append(r,o)}):typeof a=="object"&&!(a instanceof Blob)?t.append(r,JSON.stringify(a)):t.append(r,a),!0)},deleteProperty:function(t,r){return typeof r=="string"&&t.delete(r),!0},ownKeys:function(t){return Reflect.ownKeys(Object.fromEntries(t))},getOwnPropertyDescriptor:function(t,r){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(t),r)}})}function issueAjaxRequest(e,t,r,a,o,s){let l=null,f=null;if(o=o??{},o.returnPromise&&typeof Promise<"u")var n=new Promise(function(h,y){l=h,f=y});r==null&&(r=getDocument().body);let u=o.handler||handleAjaxResponse,m=o.select||null;if(!bodyContains(r))return maybeCall(l),n;let i=o.targetOverride||asElement(getTarget(r));if(i==null||i==DUMMY_ELT)return triggerErrorEvent(r,"htmx:targetError",{target:getClosestAttributeValue(r,"hx-target")}),maybeCall(f),n;let c=getInternalData(r),x=c.lastButtonClicked;if(x){let h=getRawAttribute(x,"formaction");h!=null&&(t=h);let y=getRawAttribute(x,"formmethod");if(y!=null)if(VERBS.includes(y.toLowerCase()))e=y;else return maybeCall(l),n}let d=getClosestAttributeValue(r,"hx-confirm");if(s===void 0&&triggerEvent(r,"htmx:confirm",{target:i,elt:r,path:t,verb:e,triggeringEvent:a,etc:o,issueRequest:function(T){return issueAjaxRequest(e,t,r,a,o,!!T)},question:d})===!1)return maybeCall(l),n;let p=r,g=getClosestAttributeValue(r,"hx-sync"),E=null,b=!1;if(g){let h=g.split(":"),y=h[0].trim();if(y==="this"?p=findThisElement(r,"hx-sync"):p=asElement(querySelectorExt(r,y)),g=(h[1]||"drop").trim(),c=getInternalData(p),g==="drop"&&c.xhr&&c.abortable!==!0)return maybeCall(l),n;if(g==="abort"){if(c.xhr)return maybeCall(l),n;b=!0}else g==="replace"?triggerEvent(p,"htmx:abort"):g.indexOf("queue")===0&&(E=(g.split(" ")[1]||"last").trim())}if(c.xhr)if(c.abortable)triggerEvent(p,"htmx:abort");else{if(E==null){if(a){let h=getInternalData(a);h&&h.triggerSpec&&h.triggerSpec.queue&&(E=h.triggerSpec.queue)}E==null&&(E="last")}return c.queuedRequests==null&&(c.queuedRequests=[]),E==="first"&&c.queuedRequests.length===0?c.queuedRequests.push(function(){issueAjaxRequest(e,t,r,a,o)}):E==="all"?c.queuedRequests.push(function(){issueAjaxRequest(e,t,r,a,o)}):E==="last"&&(c.queuedRequests=[],c.queuedRequests.push(function(){issueAjaxRequest(e,t,r,a,o)})),maybeCall(l),n}let S=new XMLHttpRequest;c.xhr=S,c.abortable=b;let C=function(){c.xhr=null,c.abortable=!1,c.queuedRequests!=null&&c.queuedRequests.length>0&&c.queuedRequests.shift()()},ye=getClosestAttributeValue(r,"hx-prompt");if(ye){var U=prompt(ye);if(U===null||!triggerEvent(r,"htmx:prompt",{prompt:U,target:i}))return maybeCall(l),C(),n}if(d&&!s&&!confirm(d))return maybeCall(l),C(),n;let D=getHeaders(r,i,U);e!=="get"&&!usesFormData(r)&&(D["Content-Type"]="application/x-www-form-urlencoded"),o.headers&&(D=mergeObjects(D,o.headers));let we=getInputValues(r,e),R=we.errors,Ee=we.formData;o.values&&overrideFormData(Ee,formDataFromObject(o.values));let He=formDataFromObject(getExpressionVars(r,a)),N=overrideFormData(Ee,He),k=filterValues(N,r);htmx.config.getCacheBusterParam&&e==="get"&&k.set("org.htmx.cache-buster",getRawAttribute(i,"id")||"true"),(t==null||t==="")&&(t=location.href);let V=getValuesForElement(r,"hx-request"),be=getInternalData(r).boosted,M=htmx.config.methodsThatUseUrlParams.indexOf(e)>=0,v={boosted:be,useUrlParams:M,formData:k,parameters:formDataProxy(k),unfilteredFormData:N,unfilteredParameters:formDataProxy(N),headers:D,elt:r,target:i,verb:e,errors:R,withCredentials:o.credentials||V.credentials||htmx.config.withCredentials,timeout:o.timeout||V.timeout||htmx.config.timeout,path:t,triggeringEvent:a};if(!triggerEvent(r,"htmx:configRequest",v))return maybeCall(l),C(),n;if(t=v.path,e=v.verb,D=v.headers,k=formDataFromObject(v.parameters),R=v.errors,M=v.useUrlParams,R&&R.length>0)return triggerEvent(r,"htmx:validation:halted",v),maybeCall(l),C(),n;let ve=t.split("#"),Be=ve[0],W=ve[1],A=t;if(M&&(A=Be,!k.keys().next().done&&(A.indexOf("?")<0?A+="?":A+="&",A+=urlEncode(k),W&&(A+="#"+W))),!verifyPath(r,A,v))return triggerErrorEvent(r,"htmx:invalidPath",v),maybeCall(f),C(),n;if(S.open(e.toUpperCase(),A,!0),S.overrideMimeType("text/html"),S.withCredentials=v.withCredentials,S.timeout=v.timeout,!V.noHeaders){for(let h in D)if(D.hasOwnProperty(h)){let y=D[h];safelySetHeaderValue(S,h,y)}}let w={xhr:S,target:i,requestConfig:v,etc:o,boosted:be,select:m,pathInfo:{requestPath:t,finalRequestPath:A,responsePath:null,anchor:W}};if(S.onload=function(){try{let h=hierarchyForElt(r);if(w.pathInfo.responsePath=getPathFromResponse(S),u(r,w),w.keepIndicators!==!0&&removeRequestIndicators(F,H),triggerEvent(r,"htmx:afterRequest",w),triggerEvent(r,"htmx:afterOnLoad",w),!bodyContains(r)){let y=null;for(;h.length>0&&y==null;){let T=h.shift();bodyContains(T)&&(y=T)}y&&(triggerEvent(y,"htmx:afterRequest",w),triggerEvent(y,"htmx:afterOnLoad",w))}maybeCall(l)}catch(h){throw triggerErrorEvent(r,"htmx:onLoadError",mergeObjects({error:h},w)),h}finally{C()}},S.onerror=function(){removeRequestIndicators(F,H),triggerErrorEvent(r,"htmx:afterRequest",w),triggerErrorEvent(r,"htmx:sendError",w),maybeCall(f),C()},S.onabort=function(){removeRequestIndicators(F,H),triggerErrorEvent(r,"htmx:afterRequest",w),triggerErrorEvent(r,"htmx:sendAbort",w),maybeCall(f),C()},S.ontimeout=function(){removeRequestIndicators(F,H),triggerErrorEvent(r,"htmx:afterRequest",w),triggerErrorEvent(r,"htmx:timeout",w),maybeCall(f),C()},!triggerEvent(r,"htmx:beforeRequest",w))return maybeCall(l),C(),n;var F=addRequestIndicatorClasses(r),H=disableElements(r);forEach(["loadstart","loadend","progress","abort"],function(h){forEach([S,S.upload],function(y){y.addEventListener(h,function(T){triggerEvent(r,"htmx:xhr:"+h,{lengthComputable:T.lengthComputable,loaded:T.loaded,total:T.total})})})}),triggerEvent(r,"htmx:beforeSend",w);let Oe=M?null:encodeParamsForBody(S,r,k);return S.send(Oe),n}function determineHistoryUpdates(e,t){let r=t.xhr,a=null,o=null;if(hasHeader(r,/HX-Push:/i)?(a=r.getResponseHeader("HX-Push"),o="push"):hasHeader(r,/HX-Push-Url:/i)?(a=r.getResponseHeader("HX-Push-Url"),o="push"):hasHeader(r,/HX-Replace-Url:/i)&&(a=r.getResponseHeader("HX-Replace-Url"),o="replace"),a)return a==="false"?{}:{type:o,path:a};let s=t.pathInfo.finalRequestPath,l=t.pathInfo.responsePath,f=t.etc.push||getClosestAttributeValue(e,"hx-push-url"),n=t.etc.replace||getClosestAttributeValue(e,"hx-replace-url"),u=getInternalData(e).boosted,m=null,i=null;return f?(m="push",i=f):n?(m="replace",i=n):u&&(m="push",i=l||s),i?i==="false"?{}:(i==="true"&&(i=l||s),t.pathInfo.anchor&&i.indexOf("#")===-1&&(i=i+"#"+t.pathInfo.anchor),{type:m,path:i}):{}}function codeMatches(e,t){var r=new RegExp(e.code);return r.test(t.toString(10))}function resolveResponseHandling(e){for(var t=0;t.${t}{opacity:0;visibility: hidden} .${r} .${t}, .${r}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`)}}function getMetaConfig(){let e=getDocument().querySelector('meta[name="htmx-config"]');return e?parseJSON(e.content):null}function mergeMetaConfig(){let e=getMetaConfig();e&&(htmx.config=mergeObjects(htmx.config,e))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let e=getDocument().body;processNode(e);let t=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(a){let o=a.detail.elt||a.target,s=getInternalData(o);s&&s.xhr&&s.xhr.abort()});let r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(a){a.state&&a.state.htmx?(restoreHistory(),forEach(t,function(o){triggerEvent(o,"htmx:restored",{document:getDocument(),triggerEvent})})):r&&r(a)},getWindow().setTimeout(function(){triggerEvent(e,"htmx:load",{}),e=null},0)}),htmx})(),Ae=qe;var De=document.createElement("template");De.innerHTML=` + + + + + +`;var ke=document.createElement("template");ke.innerHTML=` + + + + + + + + +`;function Te(e){return e.cloneNode(!0)}var X=class extends HTMLElement{static tag="actor-typeahead";static define(t=this.tag){this.tag=t;let r=customElements.getName(this);if(r&&r!==t)return console.warn(`${this.name} already defined as <${r}>!`);let a=customElements.get(t);if(a&&a!==this)return console.warn(`<${t}> already defined as ${a.name}!`);customElements.define(t,this)}static{let t=new URL(import.meta.url).searchParams.get("tag")||this.tag;t!=="none"&&this.define(t)}#r=this.attachShadow({mode:"closed"});#a=[];#e=-1;#o=!1;constructor(){super(),this.#r.append(Te(De).content),this.#t(),this.addEventListener("input",this),this.addEventListener("focusout",this),this.addEventListener("keydown",this),this.#r.addEventListener("pointerdown",this),this.#r.addEventListener("pointerup",this),this.#r.addEventListener("click",this)}get#s(){let t=Number.parseInt(this.getAttribute("rows")??"");return Number.isNaN(t)?5:t}handleEvent(t){switch(t.type){case"input":this.#f(t);break;case"keydown":this.#l(t);break;case"focusout":this.#n(t);break;case"pointerdown":this.#u(t);break;case"pointerup":this.#i(t);break}}#l(t){switch(t.key){case"ArrowDown":t.preventDefault(),this.#e=Math.min(this.#e+1,this.#s-1),this.#t();break;case"PageDown":t.preventDefault(),this.#e=this.#s-1,this.#t();break;case"ArrowUp":t.preventDefault(),this.#e=Math.max(this.#e-1,0),this.#t();break;case"PageUp":t.preventDefault(),this.#e=0,this.#t();break;case"Escape":t.preventDefault(),this.#a=[],this.#e=-1,this.#t();break;case"Enter":t.preventDefault(),this.#r.querySelectorAll("button")[this.#e]?.dispatchEvent(new PointerEvent("pointerup",{bubbles:!0}));break}}async#f(t){let r=t.target?.value;if(!r){this.#a=[],this.#t();return}let a=this.getAttribute("host")??"https://public.api.bsky.app",o=new URL("xrpc/app.bsky.actor.searchActorsTypeahead",a);o.searchParams.set("q",r),o.searchParams.set("limit",`${this.#s}`);let l=await(await fetch(o)).json();this.#a=l.actors,this.#e=-1,this.#t()}async#n(t){this.#o||(this.#a=[],this.#e=-1,this.#t())}#t(){let t=document.createDocumentFragment(),r=-1;for(let a of this.#a){let o=Te(ke).content,s=o.querySelector("button");s&&(s.dataset.handle=a.handle,++r===this.#e&&(s.dataset.active="true"));let l=o.querySelector("img");l&&a.avatar&&(l.src=a.avatar);let f=o.querySelector(".handle");f&&(f.textContent=a.handle),t.append(o)}this.#r.querySelector(".menu")?.replaceChildren(...t.children)}#u(t){this.#o=!0}#i(t){this.#o=!1,this.querySelector("input")?.focus();let r=t.target?.closest("button"),a=this.querySelector("input");!a||!r||(a.value=r.dataset.handle||"",this.#a=[],this.#t())}};var B={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"};var Pe=([e,t,r])=>{let a=document.createElementNS("http://www.w3.org/2000/svg",e);return Object.keys(t).forEach(o=>{a.setAttribute(o,String(t[o]))}),r?.length&&r.forEach(o=>{let s=Pe(o);a.appendChild(s)}),a},Le=(e,t={})=>{let a={...B,...t};return Pe(["svg",a,e])};var Ie=e=>Array.from(e.attributes).reduce((t,r)=>(t[r.name]=r.value,t),{}),Ue=e=>typeof e=="string"?e:!e||!e.class?"":e.class&&typeof e.class=="string"?e.class.split(" "):e.class&&Array.isArray(e.class)?e.class:"",Ne=e=>e.flatMap(Ue).map(r=>r.trim()).filter(Boolean).filter((r,a,o)=>o.indexOf(r)===a).join(" "),Ve=e=>e.replace(/(\w)(\w*)(_|-|\s*)/g,(t,r,a)=>r.toUpperCase()+a.toLowerCase()),G=(e,{nameAttr:t,icons:r,attrs:a})=>{let o=e.getAttribute(t);if(o==null)return;let s=Ve(o),l=r[s];if(!l)return console.warn(`${e.outerHTML} icon name was not found in the provided icons object.`);let f=Ie(e),n={...B,"data-lucide":o,...a,...f},u=Ne(["lucide",`lucide-${o}`,f,a]);u&&Object.assign(n,{class:u});let m=Le(l,n);return e.parentNode?.replaceChild(m,e)};var _=[["path",{d:"M12 6v16"}],["path",{d:"m19 13 2-1a9 9 0 0 1-18 0l2 1"}],["path",{d:"M9 11h6"}],["circle",{cx:"12",cy:"4",r:"2"}]];var z=[["path",{d:"M12 17V3"}],["path",{d:"m6 11 6 6 6-6"}],["path",{d:"M19 21H5"}]];var j=[["path",{d:"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"}],["path",{d:"m3.3 7 8.7 5 8.7-5"}],["path",{d:"M12 22V12"}]];var $=[["path",{d:"M20 6 9 17l-5-5"}]];var J=[["path",{d:"m6 9 6 6 6-6"}]];var K=[["path",{d:"m15 18-6-6 6-6"}]];var Q=[["path",{d:"m9 18 6-6-6-6"}]];var O=[["circle",{cx:"12",cy:"12",r:"10"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16"}]];var q=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335"}],["path",{d:"m9 11 3 3L22 4"}]];var P=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"m15 9-6 6"}],["path",{d:"m9 9 6 6"}]];var Z=[["path",{d:"m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z"}],["circle",{cx:"12",cy:"12",r:"10"}]];var Y=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]];var ee=[["path",{d:"M12 15V3"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}],["path",{d:"m7 10 5 5 5-5"}]];var te=[["circle",{cx:"12",cy:"12",r:"10"}],["path",{d:"M12 16v-4"}],["path",{d:"M12 8h.01"}]];var I=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56"}]];var re=[["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"}]];var ae=[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"}],["path",{d:"M12 22V12"}],["polyline",{points:"3.29 7 12 12 20.71 7"}],["path",{d:"m7.5 4.27 9 5.15"}]];var oe=[["path",{d:"M5 12h14"}],["path",{d:"M12 5v14"}]];var se=[["path",{d:"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"}],["path",{d:"M3 3v5h5"}],["path",{d:"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"}],["path",{d:"M16 16h5v5"}]];var le=[["path",{d:"m21 21-4.34-4.34"}],["circle",{cx:"11",cy:"11",r:"8"}]];var fe=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"}],["path",{d:"m9 12 2 2 4-4"}]];var ne=[["path",{d:"M12 10.189V14"}],["path",{d:"M12 2v3"}],["path",{d:"M19 13V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v6"}],["path",{d:"M19.38 20A11.6 11.6 0 0 0 21 14l-8.188-3.639a2 2 0 0 0-1.624 0L3 14a11.6 11.6 0 0 0 2.81 7.76"}],["path",{d:"M2 21c.6.5 1.2 1 2.5 1 2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1s1.2 1 2.5 1c2.5 0 2.5-2 5-2 1.3 0 1.9.5 2.5 1"}]];var ue=[["path",{d:"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"}]];var ie=[["path",{d:"M12 2v2"}],["path",{d:"M14.837 16.385a6 6 0 1 1-7.223-7.222c.624-.147.97.66.715 1.248a4 4 0 0 0 5.26 5.259c.589-.255 1.396.09 1.248.715"}],["path",{d:"M16 12a4 4 0 0 0-4-4"}],["path",{d:"m19 5-1.256 1.256"}],["path",{d:"M20 12h2"}]];var de=[["circle",{cx:"12",cy:"12",r:"4"}],["path",{d:"M12 2v2"}],["path",{d:"M12 20v2"}],["path",{d:"m4.93 4.93 1.41 1.41"}],["path",{d:"m17.66 17.66 1.41 1.41"}],["path",{d:"M2 12h2"}],["path",{d:"M20 12h2"}],["path",{d:"m6.34 17.66-1.41 1.41"}],["path",{d:"m19.07 4.93-1.41 1.41"}]];var me=[["path",{d:"M12 19h8"}],["path",{d:"m4 17 6-6-6-6"}]];var ce=[["path",{d:"M10 11v6"}],["path",{d:"M14 11v6"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"}],["path",{d:"M3 6h18"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"}]];var L=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"}],["path",{d:"M12 9v4"}],["path",{d:"M12 17h.01"}]];var pe=({icons:e={},nameAttr:t="data-lucide",attrs:r={},root:a=document,inTemplates:o}={})=>{if(!Object.values(e).length)throw new Error(`Please provide an icons object. +If you want to use all the icons you can import it like: + \`import { createIcons, icons } from 'lucide'; +lucide.createIcons({icons});\``);if(typeof a>"u")throw new Error("`createIcons()` only works in a browser environment.");if(Array.from(a.querySelectorAll(`[${t}]`)).forEach(l=>G(l,{nameAttr:t,icons:e,attrs:r})),o&&Array.from(a.querySelectorAll("template")).forEach(f=>pe({icons:e,nameAttr:t,attrs:r,root:f.content,inTemplates:o})),t==="data-lucide"){let l=a.querySelectorAll("[icon-name]");l.length>0&&(console.warn("[Lucide] Some icons were found with the now deprecated icon-name attribute. These will still be replaced for backwards compatibility, but will no longer be supported in v1.0 and you should switch to data-lucide"),Array.from(l).forEach(f=>G(f,{nameAttr:"icon-name",icons:e,attrs:r})))}};function Re(){return localStorage.getItem("theme")||"system"}function We(e){return e==="dark"?"dark":e==="light"?"light":window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function ge(){let e=Re(),t=We(e);document.documentElement.classList.toggle("dark",t==="dark"),document.documentElement.setAttribute("data-theme",t),Xe(e)}function Me(e){localStorage.setItem("theme",e),ge(),Ge()}function Xe(e){let t={system:"sun-moon",light:"sun",dark:"moon"},r=document.getElementById("theme-icon");r&&(r.setAttribute("data-lucide",t[e]||"sun-moon"),typeof window.lucide<"u"&&window.lucide.createIcons()),document.querySelectorAll(".theme-option").forEach(a=>{let o=a.dataset.value===e,s=a.querySelector(".theme-check");s&&(s.style.visibility=o?"visible":"hidden")})}function Ge(){let t=document.getElementById("theme-toggle-btn")?.closest("details");t&&t.removeAttribute("open")}window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{Re()==="system"&&ge()});function _e(){let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(e.classList.toggle("expanded"),e.classList.contains("expanded")&&t.focus())}function xe(){let e=document.querySelector(".nav-search-wrapper");e&&e.classList.remove("expanded")}document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(document.addEventListener("keydown",r=>{r.key==="Escape"&&e.classList.contains("expanded")&&xe()}),document.addEventListener("click",r=>{e.classList.contains("expanded")&&!e.contains(r.target)&&xe()}))});function ze(e){navigator.clipboard.writeText(e).then(()=>{let t=event.target.closest("button"),r=t.innerHTML;t.innerHTML=' Copied!',typeof window.lucide<"u"&&window.lucide.createIcons(),setTimeout(()=>{t.innerHTML=r,typeof window.lucide<"u"&&window.lucide.createIcons()},2e3)}).catch(t=>{console.error("Failed to copy:",t)})}function je(e){let t=Math.floor((new Date-new Date(e))/1e3),r={year:31536e3,month:2592e3,week:604800,day:86400,hour:3600,minute:60,second:1};for(let[a,o]of Object.entries(r)){let s=Math.floor(t/o);if(s>=1)return s===1?`1 ${a} ago`:`${s} ${a}s ago`}return"just now"}function Ce(){document.querySelectorAll("time[datetime]").forEach(e=>{let t=e.getAttribute("datetime");if(t&&!e.dataset.noUpdate){let r=je(t);e.textContent!==r&&(e.textContent=r)}})}document.addEventListener("DOMContentLoaded",()=>{Ce(),ge();let e=document.getElementById("theme-dropdown-menu");e&&e.querySelectorAll(".theme-option").forEach(t=>{t.addEventListener("click",()=>{Me(t.dataset.value)})})});document.addEventListener("htmx:afterSwap",Ce);setInterval(Ce,6e4);function $e(){let e=document.getElementById("show-offline-toggle"),t=document.querySelector(".manifests-list");!e||!t||(localStorage.setItem("showOfflineManifests",e.checked),e.checked?t.classList.add("show-offline"):t.classList.remove("show-offline"))}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("show-offline-toggle");if(!e)return;let t=localStorage.getItem("showOfflineManifests")==="true";e.checked=t;let r=document.querySelector(".manifests-list");r&&(t?r.classList.add("show-offline"):r.classList.remove("show-offline"))});async function Je(e,t,r){try{let a=await fetch(`/api/images/${e}/manifests/${t}`,{method:"DELETE",credentials:"include"});if(a.status===409){let o=await a.json();Ke(e,t,r,o.tags)}else if(a.ok)Fe(r);else{let o=await a.text();alert(`Failed to delete manifest: ${o}`)}}catch(a){console.error("Error deleting manifest:",a),alert(`Error deleting manifest: ${a.message}`)}}function Ke(e,t,r,a){let o=document.getElementById("manifest-delete-modal"),s=document.getElementById("manifest-delete-tags"),l=document.getElementById("confirm-manifest-delete-btn");s.innerHTML="",a.forEach(f=>{let n=document.createElement("li");n.textContent=f,s.appendChild(n)}),l.onclick=()=>Qe(e,t,r),o.style.display="flex"}function Se(){let e=document.getElementById("manifest-delete-modal");e.style.display="none"}async function Qe(e,t,r){let a=document.getElementById("confirm-manifest-delete-btn"),o=a.textContent;try{a.disabled=!0,a.textContent="Deleting...";let s=await fetch(`/api/images/${e}/manifests/${t}?confirm=true`,{method:"DELETE",credentials:"include"});if(s.ok)Se(),Fe(r),location.reload();else{let l=await s.text();alert(`Failed to delete manifest: ${l}`),a.disabled=!1,a.textContent=o}}catch(s){console.error("Error deleting manifest:",s),alert(`Error deleting manifest: ${s.message}`),a.disabled=!1,a.textContent=o}}function Fe(e){let t=document.getElementById(`manifest-${e}`);t&&t.remove()}async function Ze(e,t){let r=e.files[0];if(!r)return;if(!["image/png","image/jpeg","image/webp"].includes(r.type)){alert("Please select a PNG, JPEG, or WebP image");return}if(r.size>3*1024*1024){alert("Image must be less than 3MB");return}let o=new FormData;o.append("avatar",r);try{let s=await fetch(`/api/images/${t}/avatar`,{method:"POST",credentials:"include",body:o});if(s.status===401){window.location.href="/auth/oauth/login";return}if(!s.ok){let m=await s.text();throw new Error(m)}let l=await s.json(),f=document.querySelector(".repo-hero-icon-wrapper");if(!f)return;let n=f.querySelector(".repo-hero-icon"),u=f.querySelector(".repo-hero-icon-placeholder");if(n)n.src=l.avatarURL;else if(u){let m=document.createElement("img");m.src=l.avatarURL,m.alt=t,m.className="repo-hero-icon",u.replaceWith(m)}}catch(s){console.error("Error uploading avatar:",s),alert("Failed to upload avatar: "+s.message)}e.value=""}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("manifest-delete-modal");e&&e.addEventListener("click",t=>{t.target===e&&Se()})});var he=class{constructor(t){this.input=t,this.typeahead=t.closest("actor-typeahead"),this.dropdown=null,this.currentFocus=-1,this.init()}init(){this.createDropdown(),this.input.addEventListener("focus",()=>this.handleFocus()),this.input.addEventListener("input",()=>this.handleInput()),this.input.addEventListener("keydown",t=>this.handleKeydown(t)),document.addEventListener("click",t=>{!this.input.contains(t.target)&&!this.dropdown.contains(t.target)&&this.hideDropdown()})}createDropdown(){this.dropdown=document.createElement("div"),this.dropdown.className="recent-accounts-dropdown",this.dropdown.style.display="none",this.typeahead?this.typeahead.insertAdjacentElement("afterend",this.dropdown):this.input.insertAdjacentElement("afterend",this.dropdown)}handleFocus(){this.input.value.trim().length<1&&this.showRecentAccounts()}handleInput(){this.input.value.trim().length>=1&&this.hideDropdown()}showRecentAccounts(){let t=this.getRecentAccounts();if(t.length===0){this.hideDropdown();return}this.dropdown.innerHTML="",this.currentFocus=-1;let r=document.createElement("div");r.className="recent-accounts-header",r.textContent="Recent accounts",this.dropdown.appendChild(r),t.forEach((a,o)=>{let s=document.createElement("div");s.className="recent-accounts-item",s.dataset.index=o,s.dataset.handle=a,s.textContent=a,s.addEventListener("click",()=>this.selectItem(a)),this.dropdown.appendChild(s)}),this.dropdown.style.display="block"}selectItem(t){this.input.value=t,this.hideDropdown(),this.input.focus()}hideDropdown(){this.dropdown.style.display="none",this.currentFocus=-1}handleKeydown(t){if(this.dropdown.style.display==="none")return;let r=this.dropdown.querySelectorAll(".recent-accounts-item");t.key==="ArrowDown"?(t.preventDefault(),this.currentFocus++,this.currentFocus>=r.length&&(this.currentFocus=0),this.updateFocus(r)):t.key==="ArrowUp"?(t.preventDefault(),this.currentFocus--,this.currentFocus<0&&(this.currentFocus=r.length-1),this.updateFocus(r)):t.key==="Enter"&&this.currentFocus>-1&&r[this.currentFocus]?(t.preventDefault(),this.selectItem(r[this.currentFocus].dataset.handle)):t.key==="Escape"&&this.hideDropdown()}updateFocus(t){t.forEach((r,a)=>{r.classList.toggle("focused",a===this.currentFocus)})}getRecentAccounts(){try{let t=localStorage.getItem("atcr_recent_handles");return t?JSON.parse(t):[]}catch{return[]}}saveRecentAccount(t){if(t)try{let r=this.getRecentAccounts();r=r.filter(a=>a!==t),r.unshift(t),r=r.slice(0,5),localStorage.setItem("atcr_recent_handles",JSON.stringify(r))}catch(r){console.error("Failed to save recent account:",r)}}};document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("login-form"),t=document.getElementById("handle");e&&t&&new he(t)});document.addEventListener("DOMContentLoaded",()=>{let e=document.cookie.split("; ").find(r=>r.startsWith("atcr_login_handle="));if(!e)return;let t=decodeURIComponent(e.split("=")[1]);if(t){try{let r="atcr_recent_handles",a=JSON.parse(localStorage.getItem(r)||"[]");a=a.filter(o=>o!==t),a.unshift(t),a=a.slice(0,5),localStorage.setItem(r,JSON.stringify(a))}catch(r){console.error("Failed to save recent account:",r)}document.cookie="atcr_login_handle=; path=/; max-age=0"}});document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("featured-carousel"),t=document.getElementById("carousel-prev"),r=document.getElementById("carousel-next");if(!e)return;let a=Array.from(e.querySelectorAll(".carousel-item"));if(a.length===0)return;let o=null,s=5e3;function l(){let x=a[0];if(!x)return 0;let d=getComputedStyle(e),p=parseFloat(d.gap)||24;return x.offsetWidth+p}function f(){let x=e.offsetWidth,d=l();return d===0?1:Math.round(x/d)}function n(){return e.scrollWidth-e.offsetWidth}function u(){let x=l(),d=n(),p=e.scrollLeft;p>=d-10?e.scrollTo({left:0,behavior:"smooth"}):e.scrollTo({left:p+x,behavior:"smooth"})}function m(){let x=l(),d=n(),p=e.scrollLeft;p<=10?e.scrollTo({left:d,behavior:"smooth"}):e.scrollTo({left:p-x,behavior:"smooth"})}t&&t.addEventListener("click",()=>{c(),m(),i()}),r&&r.addEventListener("click",()=>{c(),u(),i()});function i(){o||a.length<=f()||(o=setInterval(u,s))}function c(){o&&(clearInterval(o),o=null)}i(),e.addEventListener("mouseenter",c),e.addEventListener("mouseleave",i)});window.setTheme=Me;window.toggleSearch=_e;window.closeSearch=xe;window.copyToClipboard=ze;window.toggleOfflineManifests=$e;window.deleteManifest=Je;window.closeManifestDeleteModal=Se;window.uploadAvatar=Ze;window.htmx=Ae;var Ye={Anchor:_,AlertCircle:O,AlertTriangle:L,ArrowDownToLine:z,Box:j,Check:$,CheckCircle:q,ChevronDown:J,ChevronLeft:K,ChevronRight:Q,CircleX:P,Compass:Z,Copy:Y,Download:ee,Info:te,Loader2:I,Moon:re,Package:ae,Plus:oe,RefreshCcw:se,Search:le,ShieldCheck:fe,Ship:ne,Star:ue,Sun:de,SunMoon:ie,Terminal:me,Trash2:ce,TriangleAlert:L,XCircle:P};window.lucide={createIcons:(e={})=>pe({icons:Ye,...e})};document.addEventListener("DOMContentLoaded",()=>{window.lucide.createIcons(),document.body.addEventListener("htmx:afterSwap",()=>{window.lucide.createIcons()})}); +/*! Bundled license information: + +lucide/dist/esm/defaultAttributes.js: +lucide/dist/esm/createElement.js: +lucide/dist/esm/replaceElement.js: +lucide/dist/esm/icons/anchor.js: +lucide/dist/esm/icons/arrow-down-to-line.js: +lucide/dist/esm/icons/box.js: +lucide/dist/esm/icons/check.js: +lucide/dist/esm/icons/chevron-down.js: +lucide/dist/esm/icons/chevron-left.js: +lucide/dist/esm/icons/chevron-right.js: +lucide/dist/esm/icons/circle-alert.js: +lucide/dist/esm/icons/circle-check-big.js: +lucide/dist/esm/icons/circle-x.js: +lucide/dist/esm/icons/compass.js: +lucide/dist/esm/icons/copy.js: +lucide/dist/esm/icons/download.js: +lucide/dist/esm/icons/info.js: +lucide/dist/esm/icons/loader-circle.js: +lucide/dist/esm/icons/moon.js: +lucide/dist/esm/icons/package.js: +lucide/dist/esm/icons/plus.js: +lucide/dist/esm/icons/refresh-ccw.js: +lucide/dist/esm/icons/search.js: +lucide/dist/esm/icons/shield-check.js: +lucide/dist/esm/icons/ship.js: +lucide/dist/esm/icons/star.js: +lucide/dist/esm/icons/sun-moon.js: +lucide/dist/esm/icons/sun.js: +lucide/dist/esm/icons/terminal.js: +lucide/dist/esm/icons/trash-2.js: +lucide/dist/esm/icons/triangle-alert.js: +lucide/dist/esm/lucide.js: + (** + * @license lucide v0.562.0 - ISC + * + * This source code is licensed under the ISC license. + * See the LICENSE file in the root directory of this source tree. + *) +*/ diff --git a/pkg/appview/static/static/install.ps1 b/pkg/appview/public/static/install.ps1 similarity index 100% rename from pkg/appview/static/static/install.ps1 rename to pkg/appview/public/static/install.ps1 diff --git a/pkg/appview/static/static/install.sh b/pkg/appview/public/static/install.sh similarity index 100% rename from pkg/appview/static/static/install.sh rename to pkg/appview/public/static/install.sh diff --git a/pkg/appview/public/static/wave-pattern.svg b/pkg/appview/public/static/wave-pattern.svg new file mode 100644 index 0000000..9361bf7 --- /dev/null +++ b/pkg/appview/public/static/wave-pattern.svg @@ -0,0 +1,10 @@ + + + + diff --git a/pkg/appview/routes/routes.go b/pkg/appview/routes/routes.go index 7495c4b..6182063 100644 --- a/pkg/appview/routes/routes.go +++ b/pkg/appview/routes/routes.go @@ -112,11 +112,13 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) { ).ServeHTTP) // API routes for stars (require authentication) + // Returns HTML for HTMX requests, JSON for API clients router.Post("/api/stars/{handle}/{repository}", middleware.RequireAuth(deps.SessionStore, deps.Database)( &uihandlers.StarRepositoryHandler{ DB: deps.Database, // Needs write access Directory: deps.OAuthClientApp.Dir, Refresher: deps.Refresher, + Templates: deps.Templates, }, ).ServeHTTP) @@ -125,6 +127,7 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) { DB: deps.Database, // Needs write access Directory: deps.OAuthClientApp.Dir, Refresher: deps.Refresher, + Templates: deps.Templates, }, ).ServeHTTP) diff --git a/pkg/appview/src/css/main.css b/pkg/appview/src/css/main.css new file mode 100644 index 0000000..2773781 --- /dev/null +++ b/pkg/appview/src/css/main.css @@ -0,0 +1,143 @@ +/* ======================================== + TAILWIND + DAISYUI + ======================================== */ +@import "tailwindcss"; + +/*@layer base { + .container { + max-width: 1920px; + } +}*/ + +@plugin "daisyui" { + themes: + light --default, + dark --prefersdark; +} + +/* ======================================== + BRAND COLOR OVERRIDES + ======================================== */ +@plugin "daisyui/theme" { + name: "light"; + default: true; + --color-primary: oklch(75% 0.12 175); /* #4ECDC4 teal */ + --color-accent: oklch(68% 0.18 25); /* #FF6B6B coral */ +} + +@plugin "daisyui/theme" { + name: "dark"; + --color-primary: oklch(78% 0.12 175); /* #5ED4CB slightly brighter */ + --color-accent: oklch(72% 0.16 25); /* #FF8080 */ +} + +/* ======================================== + ADDITIONAL CSS VARIABLES + ======================================== */ +:root { + --shadow-card-hover: + 0 8px 25px oklch(75% 0.12 175 / 0.15), 0 4px 12px rgba(0, 0, 0, 0.1); +} + +[data-theme="dark"] { + --shadow-card-hover: + 0 8px 25px oklch(78% 0.12 175 / 0.1), 0 4px 12px rgba(0, 0, 0, 0.2); +} + +/* ======================================== + CUSTOM COMPONENTS (Not in DaisyUI) + ======================================== */ +@layer components { + /* ---------------------------------------- + COMMAND / CODE DISPLAY + ---------------------------------------- */ + .cmd { + @apply flex items-center gap-2 relative w-full overflow-hidden; + @apply bg-base-200 border border-base-300 rounded-md; + @apply px-3 py-2; + } + + .cmd code { + @apply font-mono text-sm truncate; + } + + /* ---------------------------------------- + EXPANDABLE SEARCH (nav-specific) + ---------------------------------------- */ + .nav-search-wrapper { + @apply relative flex items-center; + } + + .nav-search-form { + @apply absolute right-full mr-2; + @apply w-0 opacity-0 overflow-hidden; + @apply transition-all duration-300; + } + + .nav-search-wrapper.expanded .nav-search-form { + @apply w-62 opacity-100; + } + + /* ---------------------------------------- + CARD EXTENSIONS + ---------------------------------------- */ + .card-interactive { + @apply cursor-pointer transition-all duration-500; + } + + .card-interactive:hover { + box-shadow: var(--shadow-card-hover); + transform: translateY(-2px); + } + + /* ---------------------------------------- + ACTOR-TYPEAHEAD COMPONENT STYLING + ---------------------------------------- */ + actor-typeahead { + /* Use DaisyUI CSS variables - they auto-switch with theme */ + --color-background: var(--color-base-100); + --color-border: var(--color-base-300); + --color-shadow: var(--color-base-content); + --color-hover: var(--color-base-200); + --color-avatar-fallback: var(--color-base-300); + --radius: 0.5rem; + --padding-menu: 0.25rem; + z-index: 50; + } + + actor-typeahead::part(handle) { + @apply text-base-content; + } + + actor-typeahead::part(menu) { + @apply shadow-lg; + margin-top: 0.25rem; + } + + /* ---------------------------------------- + RECENT ACCOUNTS DROPDOWN + ---------------------------------------- */ + .recent-accounts-dropdown { + @apply absolute top-full left-0 right-0; + @apply bg-base-100 border border-base-300; + @apply rounded-lg shadow-lg; + @apply max-h-60 overflow-y-auto z-50; + margin-top: 0.25rem; + } + + .recent-accounts-header { + @apply px-3 py-2 text-xs font-semibold uppercase; + @apply text-base-content/60 border-b border-base-300; + } + + .recent-accounts-item { + @apply px-3 py-2.5; + @apply cursor-pointer transition-colors duration-150; + @apply text-base-content; + } + + .recent-accounts-item:hover, + .recent-accounts-item.focused { + @apply bg-base-200; + } +} diff --git a/pkg/appview/static/js/app.js b/pkg/appview/src/js/app.js similarity index 51% rename from pkg/appview/static/js/app.js rename to pkg/appview/src/js/app.js index 231b517..eaa1b90 100644 --- a/pkg/appview/static/js/app.js +++ b/pkg/appview/src/js/app.js @@ -1,40 +1,64 @@ -// Theme management -// Load theme immediately to avoid flash -(function() { - const theme = localStorage.getItem('theme') || 'light'; - document.documentElement.setAttribute('data-theme', theme); -})(); - -function toggleTheme() { - const html = document.documentElement; - const currentTheme = html.getAttribute('data-theme') || 'light'; - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - html.setAttribute('data-theme', newTheme); - localStorage.setItem('theme', newTheme); - updateThemeIcon(); +// Theme management (system / light / dark) +function getThemePreference() { + return localStorage.getItem('theme') || 'system'; } -function updateThemeIcon() { - const themeBtn = document.getElementById('theme-toggle'); - if (!themeBtn) return; +function getEffectiveTheme(pref) { + if (pref === 'dark') return 'dark'; + if (pref === 'light') return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} - const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; - const icon = themeBtn.querySelector('.theme-icon'); +function applyTheme() { + const pref = getThemePreference(); + const effective = getEffectiveTheme(pref); + document.documentElement.classList.toggle('dark', effective === 'dark'); + document.documentElement.setAttribute('data-theme', effective); + + updateThemeUI(pref); +} + +function setTheme(theme) { + localStorage.setItem('theme', theme); + applyTheme(); + closeThemeDropdown(); +} + +function updateThemeUI(pref) { + // Update nav button icon to show selected preference + const iconMap = { system: 'sun-moon', light: 'sun', dark: 'moon' }; + const icon = document.getElementById('theme-icon'); if (icon) { - // In dark mode, show sun icon (to switch to light) - // In light mode, show moon icon (to switch to dark) - icon.setAttribute('data-lucide', currentTheme === 'dark' ? 'sun' : 'moon'); - - // Re-initialize Lucide icons - if (typeof lucide !== 'undefined') { - lucide.createIcons(); + icon.setAttribute('data-lucide', iconMap[pref] || 'sun-moon'); + if (typeof window.lucide !== 'undefined') { + window.lucide.createIcons(); } } - themeBtn.setAttribute('aria-label', currentTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'); + // Update checkmarks in dropdown + document.querySelectorAll('.theme-option').forEach(option => { + const isSelected = option.dataset.value === pref; + const check = option.querySelector('.theme-check'); + if (check) { + check.style.visibility = isSelected ? 'visible' : 'hidden'; + } + }); } +function closeThemeDropdown() { + const btn = document.getElementById('theme-toggle-btn'); + const details = btn?.closest('details'); + if (details) details.removeAttribute('open'); +} + +// Listen for system theme changes +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + if (getThemePreference() === 'system') { + applyTheme(); + } +}); + // Expandable search function toggleSearch() { const wrapper = document.querySelector('.nav-search-wrapper'); @@ -87,14 +111,14 @@ function copyToClipboard(text) { const originalHTML = btn.innerHTML; btn.innerHTML = ' Copied!'; // Re-initialize Lucide icons for the new icon - if (typeof lucide !== 'undefined') { - lucide.createIcons(); + if (typeof window.lucide !== 'undefined') { + window.lucide.createIcons(); } setTimeout(() => { btn.innerHTML = originalHTML; // Re-initialize Lucide icons to restore original icon - if (typeof lucide !== 'undefined') { - lucide.createIcons(); + if (typeof window.lucide !== 'undefined') { + window.lucide.createIcons(); } }, 2000); }).catch(err => { @@ -139,10 +163,22 @@ function updateTimestamps() { }); } -// Initial timestamp update +// Initial timestamp update and theme setup document.addEventListener('DOMContentLoaded', () => { updateTimestamps(); - updateThemeIcon(); + applyTheme(); + + // Theme dropdown setup - DaisyUI details handles open/close natively + const themeMenu = document.getElementById('theme-dropdown-menu'); + + if (themeMenu) { + // Handle theme option clicks + themeMenu.querySelectorAll('.theme-option').forEach(option => { + option.addEventListener('click', () => { + setTheme(option.dataset.value); + }); + }); + } }); // Update timestamps after HTMX swaps @@ -151,196 +187,6 @@ document.addEventListener('htmx:afterSwap', updateTimestamps); // Update timestamps periodically setInterval(updateTimestamps, 60000); // Every minute -// Toggle repository details (for images page) -function toggleRepo(name) { - const details = document.getElementById('repo-' + name); - const btn = document.getElementById('btn-' + name); - - if (details.style.display === 'none') { - details.style.display = 'block'; - btn.innerHTML = ''; - } else { - details.style.display = 'none'; - btn.innerHTML = ''; - } - - // Re-initialize Lucide icons - if (typeof lucide !== 'undefined') { - lucide.createIcons(); - } -} - -// User dropdown menu -document.addEventListener('DOMContentLoaded', () => { - const menuBtn = document.getElementById('user-menu-btn'); - const dropdownMenu = document.getElementById('user-dropdown-menu'); - - if (menuBtn && dropdownMenu) { - // Toggle dropdown on button click - menuBtn.addEventListener('click', (e) => { - e.stopPropagation(); - const isExpanded = menuBtn.getAttribute('aria-expanded') === 'true'; - - if (isExpanded) { - closeDropdown(); - } else { - openDropdown(); - } - }); - - // Close dropdown when clicking outside - document.addEventListener('click', (e) => { - if (!menuBtn.contains(e.target) && !dropdownMenu.contains(e.target)) { - closeDropdown(); - } - }); - - // Close dropdown on Escape key - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - closeDropdown(); - } - }); - - function openDropdown() { - menuBtn.setAttribute('aria-expanded', 'true'); - dropdownMenu.removeAttribute('hidden'); - } - - function closeDropdown() { - menuBtn.setAttribute('aria-expanded', 'false'); - dropdownMenu.setAttribute('hidden', ''); - } - } -}); - -// Toggle star on a repository -async function toggleStar(handle, repository) { - const starBtn = document.getElementById('star-btn'); - const starIcon = document.getElementById('star-icon'); - const starCountEl = document.getElementById('star-count'); - - if (!starBtn || !starIcon || !starCountEl) return; - - // Disable button during request - starBtn.disabled = true; - - try { - // Check current state - const isStarred = starIcon.classList.contains('star-filled'); - const method = isStarred ? 'DELETE' : 'POST'; - const url = `/api/stars/${handle}/${repository}`; - - const response = await fetch(url, { - method: method, - credentials: 'include', - }); - - if (response.status === 401) { - console.log('Not authenticated, redirecting to login'); - // Not authenticated, redirect to login - window.location.href = '/auth/oauth/login'; - return; - } - - if (!response.ok) { - const errorText = await response.text(); - console.error(`Toggle star failed: ${response.status} ${response.statusText}`, errorText); - throw new Error(`Failed to toggle star: ${errorText}`); - } - - const data = await response.json(); - - // Update UI optimistically - if (data.starred) { - starIcon.classList.add('star-filled'); - starBtn.classList.add('starred'); - // Optimistically increment count - const currentCount = parseInt(starCountEl.textContent) || 0; - starCountEl.textContent = currentCount + 1; - } else { - starIcon.classList.remove('star-filled'); - starBtn.classList.remove('starred'); - // Optimistically decrement count - const currentCount = parseInt(starCountEl.textContent) || 0; - starCountEl.textContent = Math.max(0, currentCount - 1); - } - - // Don't fetch count immediately - trust the optimistic update - // The actual count will be correct on next page load - - } catch (err) { - console.error('Error toggling star:', err); - alert(`Failed to toggle star: ${err.message}`); - } finally { - starBtn.disabled = false; - } -} - -// Load star status and count for current repository -async function loadStarStatus() { - const starBtn = document.getElementById('star-btn'); - const starIcon = document.getElementById('star-icon'); - - if (!starBtn || !starIcon) return; // Not on repository page - - // Extract handle and repository from button onclick attribute - const onclick = starBtn.getAttribute('onclick'); - const match = onclick.match(/toggleStar\('([^']+)',\s*'([^']+)'\)/); - if (!match) return; - - const handle = match[1]; - const repository = match[2]; - - try { - // Check if user has starred this repo - const starResponse = await fetch(`/api/stars/${handle}/${repository}`, { - credentials: 'include', - }); - - if (starResponse.ok) { - const starData = await starResponse.json(); - console.log('Star status data:', starData); - if (starData.starred) { - starIcon.classList.add('star-filled'); - starBtn.classList.add('starred'); - } - } else { - const errorText = await starResponse.text(); - console.error('Failed to load star status:', errorText); - } - - // Load star count - await loadStarCount(handle, repository); - - } catch (err) { - console.error('Error loading star status:', err); - } -} - -// Load star count for a repository -async function loadStarCount(handle, repository) { - const starCountEl = document.getElementById('star-count'); - if (!starCountEl) return; - - try { - const statsResponse = await fetch(`/api/stats/${handle}/${repository}`, { - credentials: 'include', - }); - - if (statsResponse.ok) { - const stats = await statsResponse.json(); - console.log('Stats data:', stats); - starCountEl.textContent = stats.star_count || 0; - } else { - const errorText = await statsResponse.text(); - console.error('Failed to load stats:', errorText); - } - } catch (err) { - console.error('Error loading star count:', err); - } -} - // Toggle offline manifests visibility function toggleOfflineManifests() { const checkbox = document.getElementById('show-offline-toggle'); @@ -553,28 +399,28 @@ document.addEventListener('DOMContentLoaded', () => { } }); -// Login page typeahead functionality -class LoginTypeahead { +// Login page recent accounts helper (works alongside actor-typeahead web component) +class RecentAccountsHelper { constructor(inputElement) { this.input = inputElement; + this.typeahead = inputElement.closest('actor-typeahead'); this.dropdown = null; - this.debounceTimer = null; this.currentFocus = -1; - this.results = []; - this.isLoading = false; - this.init(); } init() { - // Create dropdown element this.createDropdown(); - // Event listeners - this.input.addEventListener('input', (e) => this.handleInput(e)); - this.input.addEventListener('keydown', (e) => this.handleKeydown(e)); + // Show recent accounts on focus when input is empty this.input.addEventListener('focus', () => this.handleFocus()); + // Hide recent accounts when user starts typing (actor-typeahead takes over) + this.input.addEventListener('input', () => this.handleInput()); + + // Keyboard navigation for recent accounts dropdown + this.input.addEventListener('keydown', (e) => this.handleKeydown(e)); + // Close dropdown when clicking outside document.addEventListener('click', (e) => { if (!this.input.contains(e.target) && !this.dropdown.contains(e.target)) { @@ -585,121 +431,31 @@ class LoginTypeahead { createDropdown() { this.dropdown = document.createElement('div'); - this.dropdown.className = 'typeahead-dropdown'; + this.dropdown.className = 'recent-accounts-dropdown'; this.dropdown.style.display = 'none'; - this.input.parentNode.insertBefore(this.dropdown, this.input.nextSibling); - } - - handleInput(e) { - const value = e.target.value.trim(); - - // Clear debounce timer - clearTimeout(this.debounceTimer); - - if (value.length < 2) { - this.showRecentAccounts(); - return; + // Insert after the actor-typeahead element + if (this.typeahead) { + this.typeahead.insertAdjacentElement('afterend', this.dropdown); + } else { + this.input.insertAdjacentElement('afterend', this.dropdown); } - - // Debounce API call (200ms) - this.debounceTimer = setTimeout(() => { - this.searchActors(value); - }, 200); } handleFocus() { const value = this.input.value.trim(); - if (value.length < 2) { + if (value.length < 1) { this.showRecentAccounts(); } } - async searchActors(query) { - this.isLoading = true; - this.showLoading(); - - try { - const url = `https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead?q=${encodeURIComponent(query)}&limit=3`; - const response = await fetch(url); - - if (!response.ok) { - throw new Error('Failed to fetch suggestions'); - } - - const data = await response.json(); - this.results = data.actors || []; - this.renderResults(); - } catch (err) { - console.error('Typeahead error:', err); + handleInput() { + const value = this.input.value.trim(); + // Hide recent accounts once user starts typing (actor-typeahead shows its menu at 2+ chars) + if (value.length >= 1) { this.hideDropdown(); - } finally { - this.isLoading = false; } } - showLoading() { - this.dropdown.innerHTML = 'Searching...'; - this.dropdown.style.display = 'block'; - } - - renderResults() { - if (this.results.length === 0) { - this.hideDropdown(); - return; - } - - this.dropdown.innerHTML = ''; - this.currentFocus = -1; - - this.results.slice(0, 3).forEach((actor, index) => { - const item = this.createResultItem(actor, index); - this.dropdown.appendChild(item); - }); - - this.dropdown.style.display = 'block'; - } - - createResultItem(actor, index) { - const item = document.createElement('div'); - item.className = 'typeahead-item'; - item.dataset.index = index; - item.dataset.handle = actor.handle; - - // Avatar - const avatar = document.createElement('img'); - avatar.className = 'typeahead-avatar'; - avatar.src = actor.avatar || '/static/images/default-avatar.png'; - avatar.alt = actor.handle; - avatar.onerror = () => { - avatar.src = '/static/images/default-avatar.png'; - }; - - // Text container - const textContainer = document.createElement('div'); - textContainer.className = 'typeahead-text'; - - // Display name - const displayName = document.createElement('div'); - displayName.className = 'typeahead-displayname'; - displayName.textContent = actor.displayName || actor.handle; - - // Handle - const handle = document.createElement('div'); - handle.className = 'typeahead-handle'; - handle.textContent = `@${actor.handle}`; - - textContainer.appendChild(displayName); - textContainer.appendChild(handle); - - item.appendChild(avatar); - item.appendChild(textContainer); - - // Click handler - item.addEventListener('click', () => this.selectItem(actor.handle)); - - return item; - } - showRecentAccounts() { const recent = this.getRecentAccounts(); if (recent.length === 0) { @@ -711,28 +467,17 @@ class LoginTypeahead { this.currentFocus = -1; const header = document.createElement('div'); - header.className = 'typeahead-header'; + header.className = 'recent-accounts-header'; header.textContent = 'Recent accounts'; this.dropdown.appendChild(header); recent.forEach((handle, index) => { const item = document.createElement('div'); - item.className = 'typeahead-item typeahead-recent'; + item.className = 'recent-accounts-item'; item.dataset.index = index; item.dataset.handle = handle; - - const textContainer = document.createElement('div'); - textContainer.className = 'typeahead-text'; - - const handleDiv = document.createElement('div'); - handleDiv.className = 'typeahead-handle'; - handleDiv.textContent = handle; - - textContainer.appendChild(handleDiv); - item.appendChild(textContainer); - + item.textContent = handle; item.addEventListener('click', () => this.selectItem(handle)); - this.dropdown.appendChild(item); }); @@ -742,9 +487,7 @@ class LoginTypeahead { selectItem(handle) { this.input.value = handle; this.hideDropdown(); - this.saveRecentAccount(handle); - // Optionally submit the form automatically - // this.input.form.submit(); + this.input.focus(); } hideDropdown() { @@ -753,21 +496,9 @@ class LoginTypeahead { } handleKeydown(e) { - // If dropdown is hidden, only respond to ArrowDown to show it - if (this.dropdown.style.display === 'none') { - if (e.key === 'ArrowDown') { - e.preventDefault(); - const value = this.input.value.trim(); - if (value.length >= 2) { - this.searchActors(value); - } else { - this.showRecentAccounts(); - } - } - return; - } + if (this.dropdown.style.display === 'none') return; - const items = this.dropdown.querySelectorAll('.typeahead-item'); + const items = this.dropdown.querySelectorAll('.recent-accounts-item'); if (e.key === 'ArrowDown') { e.preventDefault(); @@ -779,12 +510,9 @@ class LoginTypeahead { this.currentFocus--; if (this.currentFocus < 0) this.currentFocus = items.length - 1; this.updateFocus(items); - } else if (e.key === 'Enter') { - if (this.currentFocus > -1 && items[this.currentFocus]) { - e.preventDefault(); - const handle = items[this.currentFocus].dataset.handle; - this.selectItem(handle); - } + } else if (e.key === 'Enter' && this.currentFocus > -1 && items[this.currentFocus]) { + e.preventDefault(); + this.selectItem(items[this.currentFocus].dataset.handle); } else if (e.key === 'Escape') { this.hideDropdown(); } @@ -792,11 +520,7 @@ class LoginTypeahead { updateFocus(items) { items.forEach((item, index) => { - if (index === this.currentFocus) { - item.classList.add('typeahead-focused'); - } else { - item.classList.remove('typeahead-focused'); - } + item.classList.toggle('focused', index === this.currentFocus); }); } @@ -810,13 +534,11 @@ class LoginTypeahead { } saveRecentAccount(handle) { + if (!handle) return; try { let recent = this.getRecentAccounts(); - // Remove if already exists recent = recent.filter(h => h !== handle); - // Add to front recent.unshift(handle); - // Keep only last 5 recent = recent.slice(0, 5); localStorage.setItem('atcr_recent_handles', JSON.stringify(recent)); } catch (err) { @@ -825,10 +547,120 @@ class LoginTypeahead { } } -// Initialize typeahead on login page +// Initialize recent accounts helper on login page document.addEventListener('DOMContentLoaded', () => { + const loginForm = document.getElementById('login-form'); const handleInput = document.getElementById('handle'); - if (handleInput && handleInput.closest('.login-form')) { - new LoginTypeahead(handleInput); + if (loginForm && handleInput) { + new RecentAccountsHelper(handleInput); } }); + +// Save successful login handle from cookie (set by server after OAuth success) +document.addEventListener('DOMContentLoaded', () => { + const cookie = document.cookie.split('; ').find(c => c.startsWith('atcr_login_handle=')); + if (!cookie) return; + + const handle = decodeURIComponent(cookie.split('=')[1]); + if (handle) { + // Save to recent accounts + try { + const key = 'atcr_recent_handles'; + let recent = JSON.parse(localStorage.getItem(key) || '[]'); + recent = recent.filter(h => h !== handle); + recent.unshift(handle); + recent = recent.slice(0, 5); + localStorage.setItem(key, JSON.stringify(recent)); + } catch (err) { + console.error('Failed to save recent account:', err); + } + + // Delete the cookie + document.cookie = 'atcr_login_handle=; path=/; max-age=0'; + } +}); + +// Featured carousel - scroll-based with proper wrap-around +document.addEventListener('DOMContentLoaded', () => { + const carousel = document.getElementById('featured-carousel'); + const prevBtn = document.getElementById('carousel-prev'); + const nextBtn = document.getElementById('carousel-next'); + if (!carousel) return; + + const items = Array.from(carousel.querySelectorAll('.carousel-item')); + if (items.length === 0) return; + + let intervalId = null; + const intervalMs = 5000; + + function getItemWidth() { + const item = items[0]; + if (!item) return 0; + const style = getComputedStyle(carousel); + const gap = parseFloat(style.gap) || 24; + return item.offsetWidth + gap; + } + + function getVisibleCount() { + const containerWidth = carousel.offsetWidth; + const itemWidth = getItemWidth(); + if (itemWidth === 0) return 1; + return Math.round(containerWidth / itemWidth); + } + + function getMaxScroll() { + return carousel.scrollWidth - carousel.offsetWidth; + } + + function advance() { + const itemWidth = getItemWidth(); + const maxScroll = getMaxScroll(); + const currentScroll = carousel.scrollLeft; + + // If we're at or near the end, wrap to start + if (currentScroll >= maxScroll - 10) { + carousel.scrollTo({ left: 0, behavior: 'smooth' }); + } else { + carousel.scrollTo({ left: currentScroll + itemWidth, behavior: 'smooth' }); + } + } + + function retreat() { + const itemWidth = getItemWidth(); + const maxScroll = getMaxScroll(); + const currentScroll = carousel.scrollLeft; + + // If we're at or near the start, wrap to end + if (currentScroll <= 10) { + carousel.scrollTo({ left: maxScroll, behavior: 'smooth' }); + } else { + carousel.scrollTo({ left: currentScroll - itemWidth, behavior: 'smooth' }); + } + } + + if (prevBtn) prevBtn.addEventListener('click', () => { stopInterval(); retreat(); startInterval(); }); + if (nextBtn) nextBtn.addEventListener('click', () => { stopInterval(); advance(); startInterval(); }); + + function startInterval() { + if (intervalId || items.length <= getVisibleCount()) return; + intervalId = setInterval(advance, intervalMs); + } + + function stopInterval() { + if (intervalId) { clearInterval(intervalId); intervalId = null; } + } + + startInterval(); + carousel.addEventListener('mouseenter', stopInterval); + carousel.addEventListener('mouseleave', startInterval); +}); + +// Export functions that are called from templates via onclick handlers +window.setTheme = setTheme; +window.toggleSearch = toggleSearch; +window.closeSearch = closeSearch; +window.copyToClipboard = copyToClipboard; +window.toggleOfflineManifests = toggleOfflineManifests; +window.deleteManifest = deleteManifest; +window.closeManifestDeleteModal = closeManifestDeleteModal; +window.uploadAvatar = uploadAvatar; diff --git a/pkg/appview/src/js/main.js b/pkg/appview/src/js/main.js new file mode 100644 index 0000000..f4311d8 --- /dev/null +++ b/pkg/appview/src/js/main.js @@ -0,0 +1,93 @@ +// HTMX +import htmx from 'htmx.org'; +window.htmx = htmx; + +// Actor Typeahead (web component, auto-registers on import) +import 'actor-typeahead'; + +// Lucide Icons (tree-shaken - only icons actually used in templates) +import { createIcons } from 'lucide'; +import { + Anchor, + AlertCircle, + AlertTriangle, + ArrowDownToLine, + Box, + Check, + CheckCircle, + ChevronDown, + ChevronLeft, + ChevronRight, + CircleX, + Compass, + Copy, + Download, + Info, + Loader2, + Moon, + Package, + Plus, + RefreshCcw, + Search, + ShieldCheck, + Ship, + Star, + Sun, + SunMoon, + Terminal, + Trash2, + TriangleAlert, + XCircle, +} from 'lucide'; + +// Create icons map for createIcons function +const icons = { + Anchor, + AlertCircle, + AlertTriangle, + ArrowDownToLine, + Box, + Check, + CheckCircle, + ChevronDown, + ChevronLeft, + ChevronRight, + CircleX, + Compass, + Copy, + Download, + Info, + Loader2, + Moon, + Package, + Plus, + RefreshCcw, + Search, + ShieldCheck, + Ship, + Star, + Sun, + SunMoon, + Terminal, + Trash2, + TriangleAlert, + XCircle, +}; + +// Export lucide to window for templates that use lucide.createIcons() +window.lucide = { + createIcons: (opts = {}) => createIcons({ icons, ...opts }), +}; + +// Import app functionality +import './app.js'; + +// Initialize icons on DOM load +document.addEventListener('DOMContentLoaded', () => { + window.lucide.createIcons(); + + // Re-initialize icons after HTMX swaps content + document.body.addEventListener('htmx:afterSwap', () => { + window.lucide.createIcons(); + }); +}); diff --git a/pkg/appview/static/css/style.css b/pkg/appview/static/css/style.css deleted file mode 100644 index 69ae194..0000000 --- a/pkg/appview/static/css/style.css +++ /dev/null @@ -1,2653 +0,0 @@ -:root { - --primary: #0066cc; - --button-primary: #0066cc; - --primary-dark: #0052a3; - --secondary: #6c757d; - --success: #28a745; - --success-bg: #d4edda; - --warning: #ffc107; - --warning-bg: #fff3cd; - --danger: #dc3545; - --danger-bg: #f8d7da; - --bg: #ffffff; - --fg: #1a1a1a; - --border-dark: #666; - --border: #e0e0e0; - --code-bg: #f5f5f5; - --hover-bg: #f9f9f9; - --star: #fbbf24; - - /* Navbar colors - stay consistent in dark mode */ - --navbar-bg: #1a1a1a; - --navbar-fg: #ffffff; - - /* Button text color */ - --btn-text: #ffffff; - - /* Shadows */ - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05); - --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1); - --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15); - - /* Metadata badge */ - --metadata-badge-bg: #f0f0f0; - --metadata-badge-text: var(--fg); - - /* Version badge */ - --version-badge-bg: #f3e5f5; - --version-badge-text: #7b1fa2; - --version-badge-border: #ba68c8; - - /* Attestation badge */ - --attestation-badge-bg: #d1fae5; - --attestation-badge-text: #065f46; - - /* Hero section colors */ - --hero-bg-start: #f8f9fa; - --hero-bg-end: #e9ecef; - - /* Terminal colors */ - --terminal-bg: var(--fg); - --terminal-header-bg: #2d2d2d; - --terminal-text: var(--border); - --terminal-prompt: #4ec9b0; - --terminal-comment: #6a9955; -} - -[data-theme="dark"] { - --primary: #60a5fa; - --button-primary: #1d4ed8; - --primary-dark: #1e40af; - --secondary: #9ca3af; - --success: #34d399; - --success-bg: #064e3b; - --warning: #fbbf24; - --warning-bg: #422006; - --danger: #dc3545; - --danger-bg: #7f1d1d; - --bg: #2a2a2a; - --fg: #e0e0e0; - --border-dark: #9ca3af; - --border: #404040; - --code-bg: #1e1e1e; - --hover-bg: #333333; - --star: #fbbf24; - - /* Navbar colors - stay consistent (always black) */ - --navbar-bg: #1a1a1a; - --navbar-fg: #ffffff; - - /* Button text color */ - --btn-text: #ffffff; - - /* Shadows - lighter for dark backgrounds */ - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); - --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.4); - --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.5); - - /* Metadata badge - darker in dark mode */ - --metadata-badge-bg: #1e1e1e; - --metadata-badge-text: #ffffff; - - /* Version badge - swapped colors with softer purple background */ - --version-badge-bg: #9b59b6; - --version-badge-text: #ffffff; - --version-badge-border: #ba68c8; - - /* Attestation badge */ - --attestation-badge-bg: #065f46; - --attestation-badge-text: #6ee7b7; - - /* Hero section colors */ - --hero-bg-start: #2d2d2d; - --hero-bg-end: #1a1a1a; - - /* Terminal colors - keep similar since already dark */ - --terminal-bg: #0d0d0d; - --terminal-header-bg: #1a1a1a; - --terminal-text: #d0d0d0; - --terminal-prompt: #4ec9b0; - --terminal-comment: #6a9955; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", - Arial, sans-serif; - background: var(--bg); - color: var(--fg); - line-height: 1.6; -} - -.container { - max-width: 1920px; - margin: 0 auto; - padding: 20px; -} - -/* Navigation */ -.navbar { - background: var(--navbar-bg); - color: var(--navbar-fg); - padding: 1rem 2rem; - display: flex; - justify-content: space-between; - align-items: center; - box-shadow: var(--shadow-md); -} - -.nav-brand a { - color: var(--navbar-fg); - text-decoration: none; - font-size: 1.5rem; - font-weight: bold; -} - -.nav-brand .at-protocol { - color: var(--primary); -} - -/* Expandable search */ -.nav-search-wrapper { - position: relative; - display: flex; - align-items: center; -} - -.search-toggle-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: transparent; - border: none; - color: var(--navbar-fg); - cursor: pointer; - padding: 0.5rem; - border-radius: 4px; -} - -.search-toggle-btn:hover { - background: var(--secondary); -} - -.search-toggle-btn .search-icon { - width: 1.25rem; - height: 1.25rem; -} - -.nav-search-form { - position: absolute; - right: 100%; - width: 0; - opacity: 0; - overflow: hidden; - transition: width 0.3s ease, opacity 0.3s ease; - margin-right: 0.5rem; -} - -.nav-search-wrapper.expanded .nav-search-form { - width: 250px; - opacity: 1; -} - -.nav-search-form input { - width: 100%; - padding: 0.5rem 1rem; - border: none; - border-radius: 4px; - font-size: 0.95rem; - background: var(--bg); - color: var(--fg); -} - -.nav-search-form input:focus { - outline: 2px solid var(--primary); - outline-offset: -2px; -} - -.nav-links { - display: flex; - gap: 1rem; - align-items: center; -} - -.nav-links a { - color: var(--navbar-fg); - text-decoration: none; - padding: 0.5rem 1rem; -} - -.nav-links a:hover { - background: var(--secondary); - border-radius: 4px; -} - -/* User dropdown */ -.user-dropdown { - position: relative; -} - -.user-menu-btn { - display: flex; - align-items: center; - gap: 0.5rem; - background: transparent; - color: var(--navbar-fg); - border: none; - padding: 0.5rem; - cursor: pointer; - border-radius: 4px; - transition: background 0.2s; -} - -.user-menu-btn:hover { - background: var(--secondary); -} - -.user-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - object-fit: cover; -} - -.user-avatar-placeholder { - width: 32px; - height: 32px; - border-radius: 50%; - background: var(--button-primary); - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - text-transform: uppercase; -} - -/* Profile page avatars */ -.profile-avatar { - width: 80px; - height: 80px; - border-radius: 50%; - object-fit: cover; -} - -.profile-avatar-placeholder { - width: 80px; - height: 80px; - border-radius: 50%; - background: var(--button-primary); - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 2rem; - text-transform: uppercase; - color: var(--btn-text); -} - -.user-profile { - display: flex; - align-items: center; - gap: 1rem; - margin-bottom: 2rem; -} - -.user-profile h1 { - font-size: 1.8rem; - margin: 0; -} - -.user-handle { - color: var(--navbar-fg); - font-size: 0.95rem; -} - -.dropdown-arrow { - transition: transform 0.2s; -} - -.user-menu-btn[aria-expanded="true"] .dropdown-arrow { - transform: rotate(180deg); -} - -.dropdown-menu { - position: absolute; - top: calc(100% + 0.5rem); - right: 0; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: var(--shadow-lg); - min-width: 200px; - overflow: hidden; - z-index: 1000; -} - -.dropdown-menu[hidden] { - display: none; -} - -.dropdown-menu .dropdown-item { - display: block; - width: 100%; - padding: 0.75rem 1rem; - text-align: left; - color: var(--fg); - text-decoration: none; - border: none; - background: var(--bg); - cursor: pointer; - transition: background 0.2s; - font-size: 0.95rem; -} - -.dropdown-menu .dropdown-item:hover { - background: var(--hover-bg); -} - -.dropdown-divider { - margin: 0; - border: none; - border-top: 1px solid var(--border); -} - -.dropdown-menu .logout-btn { - color: var(--danger); - font-weight: 500; -} - -/* Buttons */ -button, -.btn, -.btn-primary, -.btn-secondary { - padding: 0.5rem 1rem; - background: var(--button-primary); - color: var(--btn-text); - border: none; - border-radius: 4px; - cursor: pointer; - text-decoration: none; - display: inline-block; - font-size: 0.95rem; - transition: opacity 0.2s; -} - -button:hover, -.btn:hover, -.btn-primary:hover, -.btn-secondary:hover { - opacity: 0.9; -} - -/* Override nav-links color for primary button */ -.nav-links .btn-primary { - color: var(--btn-text); -} - -.btn-secondary { - background: var(--secondary); -} - -.btn-link { - background: transparent; - color: var(--navbar-fg); - text-decoration: none; -} - -.theme-toggle-btn { - display: inline-flex; - align-items: center; - justify-content: center; -} - -.theme-toggle-btn .theme-icon { - width: 1.25rem; - height: 1.25rem; -} - -.delete-btn { - background: transparent; - border: none; - color: var(--danger); - padding: 0.25rem 0.5rem; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; -} - -.delete-btn:hover { - color: var(--danger); -} - -.delete-btn:hover .lucide { - transform: scale(1.2); -} - -.copy-btn { - padding: 0.25rem 0.5rem; - background: transparent; - color: var(--secondary); - border: none; - font-size: 0.85rem; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; -} - -.copy-btn:hover { - color: var(--primary); -} - -.copy-btn:hover .lucide { - transform: scale(1.2); -} - -/* Cards */ -.push-card, -.repository-card { - border: 1px solid var(--border); - border-radius: 8px; - padding: 1rem; - margin-bottom: 1rem; - background: var(--bg); - box-shadow: var(--shadow-sm); -} - -.push-header { - display: flex; - gap: 1rem; - align-items: flex-start; - margin-bottom: 0.75rem; -} - -.push-user { - color: var(--primary); - text-decoration: none; - font-weight: 500; -} - -.push-user:hover { - text-decoration: underline; -} - -.push-separator { - color: var(--border-dark); - margin: 0 0.25rem; -} - -.push-repo { - font-weight: 500; - color: var(--primary); - text-decoration: none; -} - -.push-repo:hover { - color: var(--primary); - text-decoration: underline; -} - -.push-tag { - color: var(--secondary); -} - -.push-details { - display: flex; - align-items: center; - gap: 1rem; - color: var(--border-dark); - font-size: 0.9rem; - margin-bottom: 0.75rem; -} - -.digest { - font-family: "Monaco", "Courier New", monospace; - font-size: 0.85rem; - background: var(--code-bg); - padding: 0.1rem 0.3rem; - border-radius: 3px; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - display: inline-block; - vertical-align: middle; - position: relative; -} - -/* Digest with copy button container */ -.digest-container { - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -/* Docker command component */ -.docker-command { - display: inline-flex; - position: relative; - align-items: center; - gap: 0.5rem; - background: var(--code-bg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 0.75rem; - margin: 0.5rem 0; - max-width: 100%; -} - -.docker-command-icon { - width: 1.25rem; - height: 1.25rem; - color: var(--secondary); - flex-shrink: 0; -} - -.docker-command-text { - font-family: "Monaco", "Courier New", monospace; - font-size: 0.85rem; - color: var(--fg); - flex: 0 1 auto; - word-break: break-all; -} - -.docker-command .copy-btn { - position: absolute; - right: 0.5rem; - top: 50%; - transform: translateY(-50%); - background: linear-gradient(to right, transparent, var(--code-bg) 30%); - padding: 0.5rem; - padding-left: 1.5rem; - border-radius: 4px; - opacity: 0; - visibility: hidden; - transition: - opacity 0.2s, - visibility 0.2s; -} - -.docker-command:hover .copy-btn { - opacity: 1; - visibility: visible; -} - -/* Digest tooltip on hover - using title attribute for native browser tooltip */ -.digest { - cursor: default; -} - -/* Digest copy button */ -.digest-copy-btn { - background: transparent; - border: none; - color: var(--secondary); - padding: 0.1rem 0.4rem; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; -} - -.digest-copy-btn:hover { - color: var(--primary); -} - -.digest-copy-btn:hover .lucide { - transform: scale(1.2); -} - -.digest-copy-btn .lucide { - width: 0.875rem; - height: 0.875rem; - transition: transform 0.2s ease; -} - -.delete-btn .lucide { - width: 1rem; - height: 1rem; - transition: transform 0.2s ease; -} - -.copy-btn .lucide { - width: 1rem; - height: 1rem; - transition: transform 0.2s ease; -} - -.separator { - color: var(--border); -} - -/* Push card icon and layout */ -.push-icon { - width: 48px; - height: 48px; - border-radius: 8px; - object-fit: cover; - flex-shrink: 0; -} - -.push-icon-placeholder { - width: 48px; - height: 48px; - border-radius: 8px; - background: var(--button-primary); - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 1.5rem; - text-transform: uppercase; - color: var(--btn-text); - flex-shrink: 0; -} - -.push-info { - flex: 1; - min-width: 0; -} - -.push-title-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 1rem; - margin-bottom: 0.25rem; -} - -.push-title { - font-size: 1.1rem; - flex: 1; -} - -.push-description { - color: var(--border-dark); - font-size: 0.9rem; - line-height: 1.4; - margin: 0.25rem 0 0 0; -} - -/* Push stats */ -.push-stats { - display: flex; - gap: 1rem; - align-items: center; - flex-shrink: 0; -} - -.push-stat { - display: flex; - align-items: center; - gap: 0.35rem; - color: var(--border-dark); - font-size: 0.9rem; -} - -.push-stat .star-icon { - color: var(--star); - font-size: 1rem; - width: 1rem; - height: 1rem; - stroke: var(--star); - fill: none; -} - -.push-stat .star-icon.star-filled { - fill: var(--star); -} - -.push-stat .pull-icon { - color: var(--primary); - font-size: 1rem; - width: 1rem; - height: 1rem; - stroke: var(--primary); -} - -.push-stat .stat-count { - font-weight: 600; - color: var(--fg); -} - -/* Repository Cards */ -.repo-header { - padding: 1rem; - cursor: pointer; - display: flex; - gap: 1rem; - align-items: flex-start; - background: var(--hover-bg); - border-radius: 8px 8px 0 0; - margin: -1rem -1rem 0 -1rem; -} - -.repo-header:hover { - background: var(--hover-bg); -} - -.repo-icon { - width: 48px; - height: 48px; - border-radius: 8px; - object-fit: cover; - flex-shrink: 0; -} - -.repo-info { - flex: 1; - min-width: 0; -} - -.repo-title-row { - display: flex; - align-items: center; - gap: 0.75rem; - margin-bottom: 0.25rem; -} - -.repo-header h2 { - font-size: 1.3rem; - margin: 0; -} - -.repo-title-link { - color: var(--fg); - text-decoration: none; -} - -.repo-title-link:hover { - color: var(--primary); - text-decoration: underline; -} - -.repo-badge { - display: inline-flex; - align-items: center; - padding: 0.2rem 0.6rem; - font-size: 0.75rem; - font-weight: 500; - border-radius: 12px; - white-space: nowrap; -} - -.license-badge { - background: var(--code-bg); - color: var(--primary); - border: 1px solid #90caf9; -} - -/* Clickable license badges */ -a.license-badge { - text-decoration: none; - cursor: pointer; - transition: all 0.2s ease; -} - -a.license-badge:hover { - background: var(--button-primary); - color: var(--btn-text); - border-color: var(--button-primary); - transform: translateY(-1px); - box-shadow: var(--shadow-md); -} - -.version-badge { - background: var(--version-badge-bg); - color: var(--version-badge-text); - border: 1px solid var(--version-badge-border); -} - -.repo-description { - color: var(--border-dark); - font-size: 0.95rem; - margin: 0.25rem 0 0.5rem 0; - line-height: 1.4; -} - -.repo-stats { - color: var(--border-dark); - font-size: 0.9rem; - display: flex; - gap: 0.5rem; - align-items: center; - flex-wrap: wrap; -} - -.repo-link { - color: var(--primary); - text-decoration: none; - font-weight: 500; -} - -.repo-link:hover { - text-decoration: underline; -} - -.expand-btn { - background: transparent; - color: var(--fg); - padding: 0.25rem 0.5rem; - font-size: 1.2rem; -} - -.repo-details { - padding-top: 1rem; -} - -.tags-section, -.manifests-section { - margin-bottom: 1.5rem; -} - -.tags-section h3, -.manifests-section h3 { - font-size: 1.1rem; - margin-bottom: 0.5rem; - color: var(--secondary); -} - -.tag-row, -.manifest-row { - display: flex; - gap: 1rem; - align-items: center; - padding: 0.5rem; - border-bottom: 1px solid var(--border); -} - -.tag-row:last-child, -.manifest-row:last-child { - border-bottom: none; -} - -.tag-name { - font-weight: 500; - min-width: 100px; -} - -.tag-arrow { - color: var(--border-dark); -} - -/* Note: .tag-digest and .manifest-digest styling now handled by .digest class above */ - -/* Settings Page */ -.settings-page { - max-width: 800px; - margin: 0 auto; -} - -.settings-section { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 1.5rem; - box-shadow: var(--shadow-sm); -} - -.settings-section h2 { - font-size: 1.3rem; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid var(--border); -} - -.form-group { - margin-bottom: 1rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 500; - color: var(--secondary); -} - -.form-group input, -.form-group select { - width: 100%; - padding: 0.5rem; - border: 1px solid var(--border); - border-radius: 4px; - font-size: 1rem; -} - -.form-group small { - display: block; - margin-top: 0.25rem; - color: var(--border-dark); - font-size: 0.85rem; -} - -.info-row { - margin-bottom: 0.75rem; -} - -.info-row strong { - display: inline-block; - min-width: 150px; - color: var(--secondary); -} - -/* Modal */ -.modal-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.6); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.modal-content { - background: var(--bg); - padding: 2rem; - border-radius: 8px; - max-width: 800px; - max-height: 80vh; - overflow-y: auto; - position: relative; - box-shadow: var(--shadow-lg); -} - -.modal-close { - position: absolute; - top: 1rem; - right: 1rem; - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: var(--secondary); -} - -.modal-close:hover { - color: var(--fg); -} - -.manifest-json { - background: var(--code-bg); - padding: 1rem; - border-radius: 4px; - overflow-x: auto; - font-family: "Monaco", "Courier New", monospace; - font-size: 0.85rem; - border: 1px solid var(--border); -} - -/* Loading and Empty States */ -.loading { - text-align: center; - padding: 2rem; - color: var(--border-dark); -} - -.empty-state { - text-align: center; - padding: 3rem 2rem; - background: var(--hover-bg); - border-radius: 8px; - border: 1px solid var(--border); -} - -.empty-state p { - margin-bottom: 1rem; - font-size: 1.1rem; - color: var(--secondary); -} - -.empty-state pre { - background: var(--code-bg); - padding: 1rem; - border-radius: 4px; - display: inline-block; -} - -.empty-message { - color: var(--border-dark); - font-style: italic; - padding: 1rem; -} - -/* Status Messages / Callouts */ -.note { - background: var(--warning-bg); - border-left: 4px solid var(--warning); - padding: 1rem; - margin: 1rem 0; -} - -.note a { - color: var(--warning); - text-decoration: underline; - font-weight: 500; -} - -.note a:hover { - color: var(--primary); -} - -.note a:visited { - color: var(--warning); -} - -.success { - background: var(--success-bg); - border-left: 4px solid var(--success); - padding: 1rem; - margin: 1rem 0; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.success .lucide { - width: 1.25rem; - height: 1.25rem; - color: var(--success); - stroke: var(--success); - flex-shrink: 0; -} - -.error { - background: var(--danger-bg); - border-left: 4px solid var(--danger); - padding: 1rem; - margin: 1rem 0; -} - -/* Login Page */ -.login-page { - max-width: 450px; - margin: 4rem auto; - padding: 2rem; -} - -.login-page h1 { - font-size: 2rem; - margin-bottom: 0.5rem; - text-align: center; -} - -.login-page > p { - text-align: center; - color: var(--secondary); - margin-bottom: 2rem; -} - -.login-form { - background: var(--bg); - padding: 2rem; - border-radius: 8px; - border: 1px solid var(--border); - box-shadow: var(--shadow-sm); -} - -.login-form .form-group { - margin-bottom: 1.5rem; -} - -.login-form label { - display: block; - margin-bottom: 0.5rem; - font-weight: 500; -} - -.login-form input[type="text"] { - width: 100%; - padding: 0.75rem; - border: 1px solid var(--border); - border-radius: 4px; - font-size: 1rem; -} - -.login-form input[type="text"]:focus { - outline: none; - border-color: var(--primary); -} - -.btn-large { - width: 100%; - padding: 0.75rem 1.5rem; - font-size: 1rem; - font-weight: 500; -} - -.login-help { - text-align: center; - margin-top: 2rem; - color: var(--secondary); -} - -.login-help a { - color: var(--primary); - text-decoration: none; -} - -.login-help a:hover { - text-decoration: underline; -} - -/* Login Typeahead */ -.login-form .form-group { - position: relative; -} - -.typeahead-dropdown { - position: absolute; - top: 100%; - left: 0; - right: 0; - background: var(--bg); - border: 1px solid var(--border); - border-top: none; - border-radius: 0 0 4px 4px; - box-shadow: var(--shadow-md); - max-height: 300px; - overflow-y: auto; - z-index: 1000; - margin-top: -1px; -} - -.typeahead-header { - padding: 0.5rem 0.75rem; - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - color: var(--secondary); - border-bottom: 1px solid var(--border); -} - -.typeahead-item { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem; - cursor: pointer; - transition: background-color 0.15s ease; - border-bottom: 1px solid var(--border); -} - -.typeahead-item:last-child { - border-bottom: none; -} - -.typeahead-item:hover, -.typeahead-item.typeahead-focused { - background: var(--hover-bg); - border-left: 3px solid var(--primary); - padding-left: calc(0.75rem - 3px); -} - -.typeahead-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - object-fit: cover; - flex-shrink: 0; -} - -.typeahead-text { - flex: 1; - min-width: 0; -} - -.typeahead-displayname { - font-weight: 500; - color: var(--text); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.typeahead-handle { - font-size: 0.875rem; - color: var(--secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.typeahead-recent .typeahead-handle { - font-size: 1rem; - color: var(--text); -} - -.typeahead-loading { - padding: 0.75rem; - text-align: center; - color: var(--secondary); - font-size: 0.875rem; -} - -/* Repository Page */ -.repository-page { - /* Let container's max-width (1200px) control page width */ - margin: 0 auto; -} - -.repository-header { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 2rem; - margin-bottom: 2rem; - box-shadow: var(--shadow-sm); -} - -.repo-hero { - display: flex; - gap: 1.5rem; - align-items: flex-start; - margin-bottom: 1.5rem; -} - -.repo-hero-icon { - width: 80px; - height: 80px; - border-radius: 12px; - object-fit: cover; - flex-shrink: 0; -} - -.repo-hero-icon-placeholder { - width: 80px; - height: 80px; - border-radius: 12px; - background: var(--button-primary); - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 2.5rem; - text-transform: uppercase; - color: var(--btn-text); - flex-shrink: 0; -} - -.repo-hero-icon-wrapper { - position: relative; - display: inline-block; - flex-shrink: 0; -} - -.avatar-upload-overlay { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: rgba(0, 0, 0, 0.5); - border-radius: 12px; - opacity: 0; - cursor: pointer; - transition: opacity 0.2s ease; -} - -.avatar-upload-overlay i { - color: white; - width: 24px; - height: 24px; -} - -.repo-hero-icon-wrapper:hover .avatar-upload-overlay { - opacity: 1; -} - -.repo-hero-info { - flex: 1; -} - -.repo-hero-info h1 { - font-size: 2rem; - margin: 0 0 0.5rem 0; -} - -.owner-link { - color: var(--primary); - text-decoration: none; -} - -.owner-link:hover { - text-decoration: underline; -} - -.repo-separator { - color: var(--border-dark); - margin: 0 0.25rem; -} - -.repo-name { - color: var(--fg); -} - -.repo-hero-description { - color: var(--border-dark); - font-size: 1.1rem; - line-height: 1.5; - margin: 0.5rem 0 0 0; -} - -.repo-info-row { - display: flex; - gap: 2rem; - align-items: center; - margin-top: 1.5rem; -} - -.repo-actions { - flex: 0 0 auto; -} - -.star-btn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - background: var(--bg); - border: 2px solid var(--border); - border-radius: 6px; - font-size: 1rem; - cursor: pointer; - transition: all 0.2s ease; - color: var(--fg); -} - -.star-btn:hover:not(:disabled) { - border-color: var(--primary); - background: var(--hover-bg); -} - -.star-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.star-btn.starred { - border-color: var(--star); - background: var(--code-bg); -} - -.star-btn.starred:hover:not(:disabled) { - background: var(--hover-bg); -} - -/* Lucide icon base styles */ -.lucide { - display: inline-block; - width: 1em; - height: 1em; - vertical-align: middle; - stroke-width: 2; - transition: transform 0.2s ease; -} - -/* Star icon styles */ -.star-icon { - font-size: 1.25rem; - line-height: 1; - transition: transform 0.2s ease; - color: var(--star); - width: 1.25rem; - height: 1.25rem; - stroke: var(--star); - fill: none; -} - -.star-icon.star-filled { - fill: var(--star); -} - -.star-btn:hover:not(:disabled) .star-icon { - transform: scale(1.1); -} - -.star-count { - font-weight: 600; - color: var(--fg); -} - -.repo-metadata { - display: flex; - gap: 1rem; - align-items: center; - flex-wrap: wrap; - flex: 1; - justify-content: flex-end; -} - -.metadata-badge { - display: inline-flex; - align-items: center; - padding: 0.3rem 0.75rem; - font-size: 0.85rem; - font-weight: 500; - border-radius: 16px; - white-space: nowrap; -} - -.metadata-link { - color: var(--primary); - text-decoration: none; - font-weight: 500; -} - -.metadata-link:hover { - text-decoration: underline; -} - -.pull-command-section { - padding-top: 1rem; - border-top: 1px solid var(--border); -} - -.pull-command-section h3 { - font-size: 1rem; - margin-bottom: 0.75rem; - color: var(--secondary); -} - -.repo-section { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 1.5rem; - margin-bottom: 2rem; - box-shadow: var(--shadow-sm); -} - -.repo-section h2 { - font-size: 1.5rem; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid var(--border); -} - -.tags-list, -.manifests-list { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.tag-item, -.manifest-item { - border: 1px solid var(--border); - border-radius: 6px; - padding: 1rem; - background: var(--hover-bg); -} - -.tag-item-header, -.manifest-item-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.5rem; -} - -.tag-name-large { - font-size: 1.2rem; - font-weight: 600; - color: var(--fg); -} - -.tag-timestamp { - color: var(--border-dark); - font-size: 0.9rem; -} - -.tag-item-details { - margin-bottom: 0.75rem; -} - -.manifest-item-details { - display: flex; - gap: 0.5rem; - align-items: center; - color: var(--border-dark); - font-size: 0.9rem; - margin-top: 0.5rem; -} - -/* Offline manifest badge */ -.offline-badge { - display: inline-flex; - align-items: center; - gap: 0.35rem; - padding: 0.25rem 0.5rem; - background: var(--warning-bg); - color: var(--warning); - border: 1px solid var(--warning); - border-radius: 4px; - font-size: 0.85rem; - font-weight: 600; - margin-left: 0.5rem; -} - -.offline-badge .lucide { - width: 0.875rem; - height: 0.875rem; -} - -/* Checking manifest badge (health check in progress) */ -.checking-badge { - display: inline-flex; - align-items: center; - gap: 0.35rem; - padding: 0.25rem 0.5rem; - background: #e3f2fd; - color: #1976d2; - border: 1px solid #1976d2; - border-radius: 4px; - font-size: 0.85rem; - font-weight: 600; - margin-left: 0.5rem; -} - -.checking-badge .lucide { - width: 0.875rem; - height: 0.875rem; -} - -/* Hide offline manifests by default */ -.manifest-item[data-reachable="false"] { - display: none; -} - -/* Show offline manifests when toggle is checked */ -.manifests-list.show-offline .manifest-item[data-reachable="false"] { - display: block; - opacity: 0.6; -} - -/* Show offline images toggle styling */ -.show-offline-toggle { - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - user-select: none; -} - -.show-offline-toggle input[type="checkbox"] { - cursor: pointer; -} - -.show-offline-toggle span { - font-size: 0.9rem; - color: var(--border-dark); -} - -.manifest-detail-label { - font-weight: 500; - color: var(--secondary); -} - -/* Multi-architecture badges */ -.badge-multi { - display: inline-flex; - align-items: center; - padding: 0.25rem 0.6rem; - font-size: 0.75rem; - font-weight: 600; - border-radius: 12px; - background: var(--button-primary); - color: var(--btn-text); - white-space: nowrap; - margin-left: 0.5rem; -} - -/* Helm chart badge */ -.badge-helm { - display: inline-flex; - align-items: center; - gap: 0.25rem; - padding: 0.25rem 0.6rem; - font-size: 0.75rem; - font-weight: 600; - border-radius: 12px; - background: #0f1689; - color: #fff; - white-space: nowrap; - margin-left: 0.5rem; -} - -.badge-helm svg { - width: 12px; - height: 12px; -} - -.platform-badge { - display: inline-flex; - align-items: center; - padding: 0.2rem 0.5rem; - font-size: 0.75rem; - font-weight: 500; - border-radius: 4px; - background: var(--code-bg); - color: var(--fg); - border: 1px solid var(--border); - white-space: nowrap; - font-family: "Monaco", "Courier New", monospace; -} - -.platforms-inline { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - align-items: center; -} - -.manifest-type { - display: inline-flex; - align-items: center; - gap: 0.35rem; - font-size: 0.9rem; - font-weight: 500; - color: var(--secondary); -} - -.manifest-type .lucide { - width: 0.95rem; - height: 0.95rem; -} - -.platform-count { - color: var(--border-dark); - font-size: 0.85rem; - font-style: italic; -} - -.text-muted { - color: var(--border-dark); - font-style: italic; -} - -.badge-attestation { - display: inline-flex; - align-items: center; - gap: 0.3rem; - padding: 0.25rem 0.6rem; - background: var(--attestation-badge-bg); - color: var(--attestation-badge-text); - border-radius: 12px; - font-size: 0.75rem; - font-weight: 600; - margin-left: 0.5rem; - vertical-align: middle; - white-space: nowrap; -} - -.badge-attestation .lucide { - width: 0.75rem; - height: 0.75rem; -} - -/* Featured Repositories Section */ -.featured-section { - margin-bottom: 3rem; -} - -.featured-section h1 { - font-size: 1.8rem; - margin-bottom: 1.5rem; -} - -.featured-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1.5rem; - margin-bottom: 2rem; -} - -.featured-card { - border: 1px solid var(--border); - border-radius: 8px; - padding: 1.5rem; - background: var(--bg); - box-shadow: var(--shadow-sm); - transition: all 0.2s ease; - text-decoration: none; - color: var(--fg); - display: flex; - flex-direction: column; - justify-content: space-between; - min-height: 180px; -} - -.featured-card:hover { - box-shadow: var(--shadow-md); - border-color: var(--primary); - transform: translateY(-2px); -} - -.featured-header { - display: flex; - gap: 1rem; - align-items: flex-start; - margin-bottom: 1rem; -} - -.featured-icon { - width: 48px; - height: 48px; - border-radius: 8px; - object-fit: cover; - flex-shrink: 0; -} - -.featured-icon-placeholder { - width: 48px; - height: 48px; - border-radius: 8px; - background: var(--button-primary); - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - font-size: 1.5rem; - text-transform: uppercase; - color: var(--btn-text); - flex-shrink: 0; -} - -.featured-info { - flex: 1; - min-width: 0; -} - -.featured-title { - font-size: 1.1rem; - font-weight: 600; - margin-bottom: 0.5rem; - line-height: 1.3; -} - -.featured-owner { - color: var(--primary); -} - -.featured-separator { - color: var(--border-dark); - margin: 0 0.25rem; -} - -.featured-name { - color: var(--fg); -} - -.featured-description { - color: var(--border-dark); - font-size: 0.9rem; - line-height: 1.4; - margin: 0; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - line-clamp: 2; -} - -.featured-stats { - display: flex; - gap: 1.5rem; - align-items: center; - padding-top: 0.75rem; - border-top: 1px solid var(--border); -} - -.featured-stat { - display: flex; - align-items: center; - gap: 0.5rem; - color: var(--border-dark); - font-size: 0.95rem; -} - -.featured-stat .star-icon { - color: var(--star); - font-size: 1.1rem; - width: 1.1rem; - height: 1.1rem; - stroke: var(--star); - fill: none; -} - -.featured-stat .star-icon.star-filled { - fill: var(--star); -} - -.featured-stat .pull-icon { - color: var(--primary); - font-size: 1.1rem; - width: 1.1rem; - height: 1.1rem; - stroke: var(--primary); -} - -.featured-stat .stat-count { - font-weight: 600; - color: var(--fg); -} - -/* Hero Section */ -.hero-section { - background: linear-gradient( - 135deg, - var(--hero-bg-start) 0%, - var(--hero-bg-end) 100% - ); - padding: 4rem 2rem; - border-bottom: 1px solid var(--border); -} - -.hero-content { - max-width: 900px; - margin: 0 auto; - text-align: center; -} - -.hero-title { - font-size: 3rem; - font-weight: 700; - margin-bottom: 1.5rem; - color: var(--fg); - line-height: 1.2; -} - -.hero-subtitle { - font-size: 1.2rem; - color: var(--border-dark); - margin-bottom: 3rem; - line-height: 1.6; -} - -.hero-terminal { - max-width: 600px; - margin: 0 auto 2.5rem; - background: var(--terminal-bg); - border-radius: 8px; - box-shadow: var(--shadow-lg); - overflow: hidden; -} - -.terminal-header { - background: var(--terminal-header-bg); - padding: 0.75rem 1rem; - display: flex; - gap: 0.5rem; - align-items: center; -} - -.terminal-dot { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--border-dark); -} - -.terminal-dot:nth-child(1) { - background: #ff5f56; -} - -.terminal-dot:nth-child(2) { - background: #ffbd2e; -} - -.terminal-dot:nth-child(3) { - background: #27c93f; -} - -.terminal-content { - padding: 1.5rem; - margin: 0; - font-family: "Monaco", "Courier New", monospace; - font-size: 0.95rem; - line-height: 1.8; - color: var(--terminal-text); - overflow-x: auto; -} - -.terminal-prompt { - color: var(--terminal-prompt); - font-weight: bold; -} - -.terminal-comment { - color: var(--terminal-comment); - font-style: italic; -} - -.hero-actions { - display: flex; - gap: 1rem; - justify-content: center; - margin-bottom: 4rem; -} - -.btn-hero-primary, -.btn-hero-secondary { - padding: 0.9rem 2rem; - font-size: 1.1rem; - font-weight: 600; - border-radius: 6px; - text-decoration: none; - transition: all 0.2s ease; - display: inline-block; -} - -.btn-hero-primary { - background: var(--button-primary); - color: var(--btn-text); - border: 2px solid var(--button-primary); -} - -.btn-hero-primary:hover { - background: var(--primary-dark); - border-color: var(--primary-dark); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 102, 204, 0.3); -} - -.btn-hero-secondary { - background: transparent; - color: var(--primary); - border: 2px solid var(--button-primary); -} - -.btn-hero-secondary:hover { - background: var(--button-primary); - color: var(--btn-text); - transform: translateY(-2px); -} - -.hero-benefits { - max-width: 1000px; - margin: 0 auto; - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 2rem; -} - -.benefit-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 2rem 1.5rem; - text-align: center; - transition: all 0.2s ease; -} - -.benefit-card:hover { - border-color: var(--primary); - box-shadow: var(--shadow-md); - transform: translateY(-4px); -} - -.benefit-icon { - font-size: 3rem; - margin-bottom: 1rem; - line-height: 1; -} - -.benefit-icon .lucide { - width: 3rem; - height: 3rem; - stroke-width: 1.5; - color: var(--primary); - stroke: var(--primary); -} - -.benefit-card h3 { - font-size: 1.2rem; - margin-bottom: 0.75rem; - color: var(--fg); -} - -.benefit-card p { - color: var(--border-dark); - font-size: 0.95rem; - line-height: 1.5; - margin: 0; -} - -/* Install Page */ -.install-page { - max-width: 800px; - margin: 0 auto; - padding: 2rem 1rem; -} - -.install-section { - margin: 2rem 0; -} - -.install-section h2 { - margin-bottom: 1rem; - color: var(--fg); -} - -.install-section h3 { - margin: 1.5rem 0 0.5rem; - color: var(--border-dark); - font-size: 1.1rem; -} - -.install-section a { - color: var(--primary); - text-decoration: underline; - font-weight: 500; -} - -.install-section a:hover { - color: var(--primary-dark); -} - -.install-section a:visited { - color: var(--primary); -} - -.code-block { - background: var(--code-bg); - border: 1px solid var(--border); - border-radius: 4px; - padding: 1rem; - margin: 0.5rem 0 1rem; - overflow-x: auto; -} - -.code-block code { - font-family: "Monaco", "Menlo", monospace; - font-size: 0.9rem; - line-height: 1.5; - white-space: pre-wrap; -} - -.platform-tabs { - display: flex; - gap: 0.5rem; - border-bottom: 2px solid var(--border); - margin-bottom: 1rem; -} - -.platform-tab { - padding: 0.5rem 1rem; - cursor: pointer; - border: none; - background: none; - font-size: 1rem; - color: var(--border-dark); - transition: all 0.2s; -} - -.platform-tab:hover { - color: var(--fg); -} - -.platform-tab.active { - color: var(--primary); - border-bottom: 2px solid var(--primary); - margin-bottom: -2px; -} - -.platform-content { - display: none; -} - -.platform-content.active { - display: block; -} - -/* Responsive */ -@media (max-width: 768px) { - .navbar { - flex-direction: column; - gap: 1rem; - } - - .nav-search-wrapper.expanded .nav-search-form { - width: 200px; - } - - .push-details { - flex-wrap: wrap; - } - - .tag-row, - .manifest-row { - flex-wrap: wrap; - } - - .login-page { - margin: 2rem auto; - padding: 1rem; - } - - .repo-hero { - flex-direction: column; - } - - .repo-hero-info h1 { - font-size: 1.5rem; - } - - .tag-item-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } - - .manifest-item-details { - flex-direction: column; - align-items: flex-start; - } - - .featured-grid { - grid-template-columns: 1fr; - gap: 1rem; - } - - .featured-card { - min-height: auto; - } - - .hero-section { - padding: 3rem 1.5rem; - } - - .hero-title { - font-size: 2rem; - } - - .hero-subtitle { - font-size: 1rem; - margin-bottom: 2rem; - } - - .hero-terminal { - margin-bottom: 2rem; - } - - .terminal-content { - font-size: 0.85rem; - padding: 1rem; - } - - .hero-actions { - flex-direction: column; - margin-bottom: 3rem; - } - - .btn-hero-primary, - .btn-hero-secondary { - width: 100%; - text-align: center; - } - - .hero-benefits { - grid-template-columns: 1fr; - gap: 1.5rem; - } -} - -@media (max-width: 1024px) and (min-width: 769px) { - .featured-grid { - grid-template-columns: repeat(2, 1fr); - } - - .hero-benefits { - grid-template-columns: repeat(3, 1fr); - } -} - -/* README and Repository Layout */ -.repo-content-layout { - display: grid; - grid-template-columns: 6fr 4fr; - gap: 2rem; - margin-top: 2rem; -} - -.readme-section { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 2rem; - min-width: 0; - box-sizing: border-box; -} - -.readme-section h2 { - margin-bottom: 1.5rem; - padding-bottom: 0.5rem; - border-bottom: 2px solid var(--border); -} - -.readme-content { - overflow-wrap: break-word; - max-width: 100%; - box-sizing: border-box; -} - -.repo-sidebar { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -/* Markdown Styling */ -.markdown-body { - font-size: 1rem; - line-height: 1.6; - word-wrap: break-word; -} - -.markdown-body h1, -.markdown-body h2, -.markdown-body h3, -.markdown-body h4, -.markdown-body h5, -.markdown-body h6 { - margin-top: 1.5rem; - margin-bottom: 1rem; - font-weight: 600; - line-height: 1.25; -} - -.markdown-body h1 { - font-size: 2rem; - border-bottom: 1px solid var(--border); - padding-bottom: 0.3rem; -} - -.markdown-body h2 { - font-size: 1.5rem; - border-bottom: 1px solid var(--border); - padding-bottom: 0.3rem; -} - -.markdown-body h3 { - font-size: 1.25rem; -} - -.markdown-body h4 { - font-size: 1rem; -} - -.markdown-body h5 { - font-size: 0.875rem; -} - -.markdown-body h6 { - font-size: 0.85rem; - color: var(--secondary); -} - -.markdown-body p { - margin-bottom: 1rem; -} - -.markdown-body ul, -.markdown-body ol { - margin-bottom: 1rem; - padding-left: 2rem; -} - -.markdown-body li { - margin-bottom: 0.25rem; -} - -.markdown-body li > p { - margin-bottom: 0.5rem; -} - -.markdown-body a { - color: var(--primary); - text-decoration: none; -} - -.markdown-body a:hover { - text-decoration: underline; -} - -.markdown-body code { - background: var(--code-bg); - padding: 0.2rem 0.4rem; - border-radius: 3px; - font-family: - "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; - font-size: 0.9em; -} - -.markdown-body pre { - background: var(--code-bg); - padding: 1rem; - border-radius: 6px; - overflow-x: auto; - margin-bottom: 1rem; - max-width: 100%; - box-sizing: border-box; -} - -.markdown-body pre code { - background: none; - padding: 0; - font-size: 0.875rem; -} - -.markdown-body blockquote { - padding: 0 1rem; - margin-bottom: 1rem; - color: var(--secondary); - border-left: 4px solid var(--border); -} - -.markdown-body table { - border-collapse: collapse; - width: 100%; - margin-bottom: 1rem; -} - -.markdown-body table th, -.markdown-body table td { - padding: 0.5rem 1rem; - border: 1px solid var(--border); - text-align: left; -} - -.markdown-body table th { - background: var(--code-bg); - font-weight: 600; -} - -.markdown-body table tr:nth-child(even) { - background: var(--hover-bg); -} - -.markdown-body img { - max-width: 100%; - height: auto; - margin: 1rem 0; -} - -.markdown-body hr { - height: 0.25rem; - margin: 1.5rem 0; - background: var(--border); - border: 0; -} - -/* Task lists */ -.markdown-body input[type="checkbox"] { - margin-right: 0.5rem; -} - -.markdown-body .task-list-item { - list-style-type: none; -} - -.markdown-body .task-list-item input { - margin: 0 0.2rem 0.25rem -1.6rem; - vertical-align: middle; -} - -/* Responsive Layout */ -@media (max-width: 1024px) { - .repo-content-layout { - grid-template-columns: 1fr; - } - - .repo-sidebar { - order: -1; /* Show sidebar first on mobile */ - } -} - -@media (max-width: 768px) { - .readme-section { - padding: 1rem; - } - - .markdown-body h1 { - font-size: 1.5rem; - } - - .markdown-body h2 { - font-size: 1.25rem; - } - - .markdown-body pre { - padding: 0.75rem; - } -} - -/* 404 Error Page */ -.error-page { - display: flex; - align-items: center; - justify-content: center; - min-height: calc(100vh - 60px); - text-align: center; - padding: 2rem; -} - -.error-content { - max-width: 480px; -} - -.error-icon { - width: 80px; - height: 80px; - color: var(--secondary); - margin-bottom: 1.5rem; -} - -.error-code { - font-size: 8rem; - font-weight: 700; - color: var(--primary); - line-height: 1; - margin-bottom: 0.5rem; -} - -.error-content h1 { - font-size: 2rem; - margin-bottom: 0.75rem; - color: var(--fg); -} - -.error-content p { - font-size: 1.125rem; - color: var(--secondary); - margin-bottom: 2rem; -} - -@media (max-width: 768px) { - .error-code { - font-size: 5rem; - } - - .error-icon { - width: 60px; - height: 60px; - } - - .error-content h1 { - font-size: 1.5rem; - } -} - -/* Artifact type badges */ -.artifact-badge { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.15rem 0.35rem; - border-radius: 4px; - font-size: 0.7rem; - font-weight: 500; - margin-left: 0.5rem; - vertical-align: middle; -} - -.artifact-badge.helm { - background-color: rgba(13, 108, 191, 0.15); - color: #0d6cbf; -} - -.artifact-badge i { - width: 12px; - height: 12px; -} - -.manifest-type.helm { - background-color: rgba(13, 108, 191, 0.15); - color: #0d6cbf; -} - -/* Legal Pages (Privacy Policy, Terms of Service) */ -.legal-page { - max-width: 800px; - margin: 0 auto; - padding: 2rem 1rem; -} - -.legal-page h1 { - font-size: 2rem; - margin-bottom: 0.5rem; - color: var(--fg); -} - -.legal-updated { - color: var(--secondary); - margin-bottom: 2rem; -} - -.legal-section { - margin: 2rem 0; - padding-bottom: 1.5rem; - border-bottom: 1px solid var(--border); -} - -.legal-section:last-child { - border-bottom: none; -} - -.legal-section h2 { - font-size: 1.5rem; - margin-bottom: 1rem; - color: var(--fg); -} - -.legal-section h3 { - font-size: 1.15rem; - margin: 1.5rem 0 0.75rem; - color: var(--fg); -} - -.legal-section p { - margin-bottom: 1rem; - line-height: 1.7; -} - -.legal-section ul, -.legal-section ol { - margin-bottom: 1rem; - padding-left: 2rem; -} - -.legal-section li { - margin-bottom: 0.5rem; - line-height: 1.6; -} - -.legal-section ul ul { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.legal-section code { - background: var(--code-bg); - padding: 0.2rem 0.4rem; - border-radius: 3px; - font-family: "Monaco", "Menlo", monospace; - font-size: 0.9em; -} - -.legal-section a { - color: var(--primary); - text-decoration: underline; -} - -.legal-section a:hover { - color: var(--primary-dark); -} - -.legal-section table { - width: 100%; - border-collapse: collapse; - margin: 1rem 0; -} - -.legal-section table th, -.legal-section table td { - padding: 0.75rem 1rem; - border: 1px solid var(--border); - text-align: left; -} - -.legal-section table th { - background: var(--code-bg); - font-weight: 600; -} - -.legal-section table tr:nth-child(even) { - background: var(--hover-bg); -} - -.legal-disclaimer { - background: var(--code-bg); - padding: 1rem; - border-radius: 4px; - font-size: 0.95rem; - margin: 1rem 0; -} - -@media (max-width: 768px) { - .legal-page { - padding: 1rem 0.5rem; - } - - .legal-page h1 { - font-size: 1.5rem; - } - - .legal-section h2 { - font-size: 1.25rem; - } - - .legal-section table { - font-size: 0.85rem; - } - - .legal-section table th, - .legal-section table td { - padding: 0.5rem; - } -} diff --git a/pkg/appview/templates/components/docker-command.html b/pkg/appview/templates/components/docker-command.html index 38511db..fb0ef6d 100644 --- a/pkg/appview/templates/components/docker-command.html +++ b/pkg/appview/templates/components/docker-command.html @@ -5,11 +5,11 @@ Expects: string - the docker command to display Usage: {{ template "docker-command" "docker pull atcr.io/alice/myapp:latest" }} */}} - - - {{ . }} - - + + + {{ . }} + + {{ end }} diff --git a/pkg/appview/templates/components/head.html b/pkg/appview/templates/components/head.html index 5957327..c16edd0 100644 --- a/pkg/appview/templates/components/head.html +++ b/pkg/appview/templates/components/head.html @@ -9,26 +9,24 @@ - + + + + - - - - - - - - - + + {{ end }} diff --git a/pkg/appview/templates/components/hero.html b/pkg/appview/templates/components/hero.html new file mode 100644 index 0000000..6e1a69f --- /dev/null +++ b/pkg/appview/templates/components/hero.html @@ -0,0 +1,40 @@ +{{ define "hero" }} +{{/* + Hero section component - displays landing page hero for non-authenticated users + Required: .Benefits ([]Benefit with Icon, Title, Description fields) +*/}} + + + ship containers on the open web. + + Push and pull Docker images on the AT Protocol. + Browse public registries or control your data. + + + + docker login atcr.io + docker push atcr.io/you/app + same docker, decentralized + + + + Get Started + Learn More + + + + + {{ range .Benefits }} + + + + + {{ .Title }} + {{ .Description }} + + {{ end }} + + + + +{{ end }} diff --git a/pkg/appview/templates/components/modal.html b/pkg/appview/templates/components/modal.html index d4936d2..9175d88 100644 --- a/pkg/appview/templates/components/modal.html +++ b/pkg/appview/templates/components/modal.html @@ -1,30 +1,33 @@ {{ define "manifest-modal" }} - - - ✕ + + + ✕ - Manifest Details + Manifest Details - - - Digest: - {{ .Digest }} + + + Digest: + {{ .Digest }} - - Media Type: + + Media Type: {{ .MediaType }} - - Hold Endpoint: + + Hold Endpoint: {{ .HoldEndpoint }} - - Created: + + Created: {{ .CreatedAt.Format "2006-01-02 15:04:05 MST" }} - + + close + + {{ end }} diff --git a/pkg/appview/templates/components/nav-brand.html b/pkg/appview/templates/components/nav-brand.html index c337202..1fe3f76 100644 --- a/pkg/appview/templates/components/nav-brand.html +++ b/pkg/appview/templates/components/nav-brand.html @@ -1,5 +1,3 @@ {{ define "nav-brand" }} - - at://Container Registry - +at://Container Registry {{ end }} diff --git a/pkg/appview/templates/components/nav-search.html b/pkg/appview/templates/components/nav-search.html index ca0e9be..95bcee5 100644 --- a/pkg/appview/templates/components/nav-search.html +++ b/pkg/appview/templates/components/nav-search.html @@ -1,10 +1,10 @@ {{ define "nav-search" }} - - + + - + {{ end }} diff --git a/pkg/appview/templates/components/nav-theme-toggle.html b/pkg/appview/templates/components/nav-theme-toggle.html index cdb74c0..ca999c8 100644 --- a/pkg/appview/templates/components/nav-theme-toggle.html +++ b/pkg/appview/templates/components/nav-theme-toggle.html @@ -1,5 +1,30 @@ {{ define "nav-theme-toggle" }} - - - + + + + + + + + + System + + + + + + + Light + + + + + + + Dark + + + + + {{ end }} diff --git a/pkg/appview/templates/components/nav-user.html b/pkg/appview/templates/components/nav-user.html index fda7423..9f4ae1f 100644 --- a/pkg/appview/templates/components/nav-user.html +++ b/pkg/appview/templates/components/nav-user.html @@ -1,27 +1,30 @@ {{ define "nav-user" }} {{ if .User }} - - + + + {{ if .User.Avatar }} - + + + {{ else }} - {{ firstChar .User.Handle }} + + {{ firstChar .User.Handle }} + {{ end }} - @{{ .User.Handle }} - - - - - - Your Repositories - Settings - - - Logout - - + @{{ .User.Handle }} + + + + Your Repositories + Settings + + Logout + + + {{ else }} - Login +Login {{ end }} {{ end }} diff --git a/pkg/appview/templates/components/nav.html b/pkg/appview/templates/components/nav.html index a65819c..f8be47c 100644 --- a/pkg/appview/templates/components/nav.html +++ b/pkg/appview/templates/components/nav.html @@ -1,7 +1,9 @@ {{ define "nav" }} - - {{ template "nav-brand" }} - + + + {{ template "nav-brand" }} + + {{ template "nav-search" . }} {{ template "nav-theme-toggle" }} {{ template "nav-user" . }} @@ -10,9 +12,11 @@ {{ end }} {{ define "nav-simple" }} - - {{ template "nav-brand" }} - + + + {{ template "nav-brand" }} + + {{ template "nav-theme-toggle" }} diff --git a/pkg/appview/templates/components/pull-count.html b/pkg/appview/templates/components/pull-count.html new file mode 100644 index 0000000..f73d7da --- /dev/null +++ b/pkg/appview/templates/components/pull-count.html @@ -0,0 +1,10 @@ +{{ define "pull-count" }} +{{/* + Pull count component - displays download icon with count + Required: .PullCount (int) +*/}} + + + {{ .PullCount }} + +{{ end }} diff --git a/pkg/appview/templates/components/repo-card.html b/pkg/appview/templates/components/repo-card.html index 760d0e2..7d56610 100644 --- a/pkg/appview/templates/components/repo-card.html +++ b/pkg/appview/templates/components/repo-card.html @@ -11,37 +11,61 @@ - StarCount: int - Number of stars - PullCount: int - Number of pulls - ArtifactType: string - container-image, helm-chart, unknown + - Tag: string (optional) - Latest tag name + - Digest: string (optional) - Latest manifest digest + - LastUpdated: time.Time (optional) - Last push time */}} - - + + {{ if .IconURL }} - + {{ else }} - {{ firstChar .Repository }} - {{ end }} - - - {{ .OwnerHandle }} - / - {{ .Repository }} - {{ if eq .ArtifactType "helm-chart" }} - - {{ end }} + + + {{ firstChar .Repository }} + - {{ if .Description }} - {{ .Description }} + {{ end }} + + + {{ .OwnerHandle }} + / + {{ .Repository }} + + {{ if .Tag }} + Tag: {{ .Tag }} {{ end }} - - - - {{ .StarCount }} - - - - {{ .PullCount }} - + {{ if .Description }} + {{ .Description }} + {{ end }} + + {{ if eq .ArtifactType "helm-chart" }} + {{ if .Tag }} + {{ template "docker-command" (printf "helm pull oci://atcr.io/%s/%s --version %s" .OwnerHandle .Repository .Tag) }} + {{ else }} + {{ template "docker-command" (printf "helm pull oci://atcr.io/%s/%s" .OwnerHandle .Repository) }} + {{ end }} + {{ else }} + {{ if .Tag }} + {{ template "docker-command" (printf "docker pull atcr.io/%s/%s:%s" .OwnerHandle .Repository .Tag) }} + {{ else }} + {{ template "docker-command" (printf "docker pull atcr.io/%s/%s" .OwnerHandle .Repository) }} + {{ end }} + {{ end }} - + + + {{ template "star" (dict "IsStarred" .IsStarred "StarCount" .StarCount) }} + {{ template "pull-count" (dict "PullCount" .PullCount) }} + {{ if eq .ArtifactType "helm-chart" }} + + {{ end }} + + {{ if not .LastUpdated.IsZero }} + {{ timeAgo .LastUpdated }} + {{ end }} + + {{ end }} diff --git a/pkg/appview/templates/components/star.html b/pkg/appview/templates/components/star.html new file mode 100644 index 0000000..9f4b3c2 --- /dev/null +++ b/pkg/appview/templates/components/star.html @@ -0,0 +1,30 @@ +{{ define "star" }} +{{/* + Star component - displays star icon with count + Required: .IsStarred (bool), .StarCount (int) + Optional: .Interactive (bool), .Handle (string), .Repository (string) + + Interactive mode: renders as button with HTMX toggle + Display mode: renders as span (default) +*/}} +{{ if .Interactive }} + + + {{ .StarCount }} + +{{ else }} + + + {{ .StarCount }} + +{{ end }} +{{ end }} diff --git a/pkg/appview/templates/pages/404.html b/pkg/appview/templates/pages/404.html index ab2ac47..b1516c8 100644 --- a/pkg/appview/templates/pages/404.html +++ b/pkg/appview/templates/pages/404.html @@ -7,13 +7,15 @@ {{ template "nav-simple" . }} - - - - 404 - Lost at Sea - The page you're looking for has drifted into uncharted waters. - Return to Port + + + + + 404 + Lost at Sea + The page you're looking for has drifted into uncharted waters. + Return to Port + diff --git a/pkg/appview/templates/pages/home.html b/pkg/appview/templates/pages/home.html index c98adcf..d5134c3 100644 --- a/pkg/appview/templates/pages/home.html +++ b/pkg/appview/templates/pages/home.html @@ -23,75 +23,46 @@ {{ template "nav" . }} {{ if not .User }} - - - - ship containers on the open web. - - Push and pull Docker images on the AT Protocol. - Browse public registries or control your data. - - - - - - - - - $ docker login atcr.io -$ docker push atcr.io/you/app - -# same docker, decentralized - - - - Get Started - Learn More - - - - - - - - Works with Docker - Use docker push & pull. No new tools to learn. - - - - Your Data - Join shared holds or captain your own storage. - - - - Discover Images - Browse and star public container registries. - - - + {{ template "hero" . }} {{ end }} - - + + {{ if .FeaturedRepos }} - - Featured - - {{ range .FeaturedRepos }} + + + Featured + + + + + + + + + + + {{ range $i, $repo := .FeaturedRepos }} + + {{ template "repo-card" $repo }} + + {{ end }} + + + {{ end }} + + + {{ if .RecentRepos }} + + What's New + + {{ range .RecentRepos }} {{ template "repo-card" . }} {{ end }} - + {{ end }} - - - What's New - - - - Loading recent pushes... - diff --git a/pkg/appview/templates/pages/login.html b/pkg/appview/templates/pages/login.html index ca2fa53..8bcb8a3 100644 --- a/pkg/appview/templates/pages/login.html +++ b/pkg/appview/templates/pages/login.html @@ -8,44 +8,57 @@ {{ template "nav-simple" . }} - - - Sign in to ATCR - Use your ATProto handle to sign in + + + Sign in to ATCR + Use your ATProto handle to sign in {{ if .Error }} - - {{ if eq .Error "handle_required" }} - Please enter your handle - {{ else if eq .Error "auth_failed" }} - Authentication failed. Please try again. - {{ else }} - An error occurred. Please try again. - {{ end }} + + + + {{ if eq .Error "handle_required" }} + Please enter your handle + {{ else if eq .Error "auth_failed" }} + Authentication failed. Please try again. + {{ else }} + An error occurred. Please try again. + {{ end }} + {{ end }} - + - - Your ATProto Handle - - Enter your Bluesky or ATProto handle - + + + Your ATProto Handle + + + + + + Enter your Bluesky or ATProto handle + + - Continue with ATProto + + Continue with ATProto + - - Don't have an account? Create one at bsky.app - + + Don't have an account? Create one at + bsky.app + diff --git a/pkg/appview/templates/pages/repository.html b/pkg/appview/templates/pages/repository.html index d87de0d..5b931dd 100644 --- a/pkg/appview/templates/pages/repository.html +++ b/pkg/appview/templates/pages/repository.html @@ -22,87 +22,89 @@ {{ template "nav" . }} - - + + - - - + + + {{ if .Repository.IconURL }} - + {{ else }} - {{ firstChar .Repository.Name }} + + + {{ firstChar .Repository.Name }} + + {{ end }} {{ if $.IsOwner }} - - + + {{ end }} - - - {{ .Owner.Handle }} - / - {{ .Repository.Name }} + + + {{ .Owner.Handle }} + / + {{ .Repository.Name }} {{ if .Repository.Description }} - {{ .Repository.Description }} + {{ .Repository.Description }} {{ end }} - - - - - - {{ .StarCount }} - + + + + {{ template "star" (dict "IsStarred" .IsStarred "StarCount" .StarCount "Interactive" true "Handle" .Owner.Handle "Repository" .Repository.Name) }} + {{ template "pull-count" (dict "PullCount" .PullCount) }} {{ if or .Repository.Licenses .Repository.SourceURL .Repository.DocumentationURL .Repository.Version }} - + {{ if .Repository.Version }} - + {{ .Repository.Version }} {{ end }} {{ if .Repository.Licenses }} {{ range parseLicenses .Repository.Licenses }} {{ if .IsValid }} - + {{ .SPDXID }} {{ else }} - + {{ .Name }} {{ end }} {{ end }} {{ end }} {{ if .Repository.SourceURL }} - + Source {{ end }} {{ if .Repository.DocumentationURL }} - + Documentation {{ end }} - {{ else }} - {{ end }} + + - + {{ if eq .ArtifactType "helm-chart" }} - Pull this chart + Pull this chart {{ if .Tags }} {{ $firstTag := index .Tags 0 }} {{ template "docker-command" (print "helm pull oci://" $.RegistryURL "/" $.Owner.Handle "/" $.Repository.Name " --version " $firstTag.Tag.Tag) }} @@ -110,7 +112,7 @@ {{ template "docker-command" (print "helm pull oci://" $.RegistryURL "/" $.Owner.Handle "/" $.Repository.Name) }} {{ end }} {{ else }} - Pull this image + Pull this image {{ if .Tags }} {{ $firstTag := index .Tags 0 }} {{ template "docker-command" (print "docker pull " $.RegistryURL "/" $.Owner.Handle "/" $.Repository.Name ":" $firstTag.Tag.Tag) }} @@ -123,63 +125,63 @@ {{ if .ReadmeHTML }} - + - - Overview - + + Overview + {{ .ReadmeHTML }} - + {{ end }} - - Tags + + Tags {{ if .Tags }} - + {{ range .Tags }} - - - - {{ .Tag.Tag }} + + + + {{ .Tag.Tag }} {{ if eq .ArtifactType "helm-chart" }} - Helm + Helm {{ else if .IsMultiArch }} - Multi-arch + Multi-arch {{ end }} {{ if .HasAttestations }} - Attestations + Attestations {{ end }} - - + + {{ timeAgo .Tag.CreatedAt }} {{ if $.IsOwner }} - - + {{ end }} - - - - {{ .Tag.Digest }} - + + + + {{ .Tag.Digest }} + {{ if .Platforms }} - + {{ range .Platforms }} - {{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }} + {{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }} {{ end }} {{ end }} @@ -194,77 +196,79 @@ {{ end }} {{ else }} - No tags available + No tags available {{ end }} - - - Manifests - - + + + Manifests + + Show offline images {{ if .Manifests }} - + {{ range .Manifests }} - - - - {{ if .IsManifestList }} - Multi-arch - {{ else if eq .ArtifactType "helm-chart" }} - Helm Chart - {{ else }} - Image - {{ end }} - {{ if .HasAttestations }} - Attestations - {{ end }} - {{ if .Pending }} - - Checking... - - {{ else if not .Reachable }} - Offline - {{ end }} - - {{ .Manifest.Digest }} - + + + + + {{ if .IsManifestList }} + Multi-arch + {{ else if eq .ArtifactType "helm-chart" }} + Helm Chart + {{ else }} + Image + {{ end }} + {{ if .HasAttestations }} + Attestations + {{ end }} + {{ if .Pending }} + + Checking... + + {{ else if not .Reachable }} + Offline + {{ end }} + + + {{ .Manifest.Digest }} + - - + + {{ timeAgo .Manifest.CreatedAt }} {{ if $.IsOwner }} - - + {{ end }} - - + + {{ if .Tags }} - Tags: + Tags: {{ range $index, $tag := .Tags }}{{ if $index }}, {{ end }}{{ $tag }}{{ end }} {{ else }} - (untagged) + (untagged) {{ end }} {{ if .IsManifestList }} {{ if .Platforms }} - + {{ range .Platforms }} - {{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }} + {{ .OS }}/{{ .Architecture }}{{ if .Variant }}/{{ .Variant }}{{ end }} {{ end }} {{ end }} @@ -275,13 +279,13 @@ {{ end }} {{ else }} - No manifests available + No manifests available {{ end }} {{ if .ReadmeHTML }} - - + + {{ end }} @@ -290,121 +294,22 @@ - - - - Confirm Deletion - × - - - This manifest has associated tags that will also be deleted: - - This action cannot be undone. - - + + close + + - {{ end }} diff --git a/pkg/appview/templates/pages/settings.html b/pkg/appview/templates/pages/settings.html index fc2ecc0..8df8b64 100644 --- a/pkg/appview/templates/pages/settings.html +++ b/pkg/appview/templates/pages/settings.html @@ -8,223 +8,232 @@ {{ template "nav" . }} - - - Settings + + + Settings - - - Identity - - Handle: - {{ .Profile.Handle }} - - - DID: - {{ .Profile.DID }} - - - PDS: - {{ .Profile.PDSEndpoint }} - - - - - - Stowage - Estimated storage usage on your default hold. - - Loading... - - - - - - Default Hold - Select where your container images will be stored. - - - - - Storage Hold: - - - AppView Default ({{ .AppViewDefaultHoldDisplay }}{{ if .AppViewDefaultRegion }}, {{ .AppViewDefaultRegion }}{{ end }}) - - {{ if .ShowCurrentHold }} - Current ({{ .CurrentHoldDisplay }}) - {{ end }} - - {{ if .OwnedHolds }} - - {{ range .OwnedHolds }} - - {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} - - {{ end }} - - {{ end }} - - {{ if .CrewHolds }} - - {{ range .CrewHolds }} - - {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} - - {{ end }} - - {{ end }} - - {{ if .EligibleHolds }} - - {{ range .EligibleHolds }} - - {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} - - {{ end }} - - {{ end }} - - {{ if .PublicHolds }} - - {{ range .PublicHolds }} - - {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} - - {{ end }} - - {{ end }} - - + + + Identity + + + Handle + {{ .Profile.Handle }} + + + DID + {{ .Profile.DID }} + + + PDS + {{ .Profile.PDSEndpoint }} + - Your images will be stored on the selected hold - + - Save - + + + Stowage + Estimated storage usage on your default hold. + + Loading... + + - + + + Default Hold + Select where your container images will be stored. - - - Hold Details - - DID: - - Region: - - Your Access: - - - + - + + + Storage Hold + + + AppView Default ({{ .AppViewDefaultHoldDisplay }}{{ if .AppViewDefaultRegion }}, {{ .AppViewDefaultRegion }}{{ end }}) - - - Authorized Devices - Devices authorized via docker-credential-atcr credential helper. + {{ if .ShowCurrentHold }} + Current ({{ .CurrentHoldDisplay }}) + {{ end }} - - - First Time Setup - - Install credential helper: - curl -fsSL atcr.io/static/install.sh | bash - - Configure Docker to use the helper. Add to ~/.docker/config.json: - { + {{ if .OwnedHolds }} + + {{ range .OwnedHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + {{ if .CrewHolds }} + + {{ range .CrewHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + {{ if .EligibleHolds }} + + {{ range .EligibleHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + {{ if .PublicHolds }} + + {{ range .PublicHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + Your images will be stored on the selected hold + + + Save + + + + + + + Hold Details + + DID: + + Region: + + Your Access: + + + + + + + + + Authorized Devices + Devices authorized via docker-credential-atcr credential helper. + + + + + First Time Setup + + Install credential helper: + curl -fsSL atcr.io/static/install.sh | bash + + Configure Docker to use the helper. Add to ~/.docker/config.json: + { "credHelpers": { "{{ .RegistryURL }}": "atcr" } } - - Run any Docker command: - {{ template "docker-command" (print "docker pull " .RegistryURL "/" .Profile.Handle "/myimage") }} - - Browser will open for authorization - click Approve - Done! Device is automatically authorized - + + Run any Docker command: + {{ template "docker-command" (print "docker pull " .RegistryURL "/" .Profile.Handle "/myimage") }} + + Browser will open for authorization - click Approve + Done! Device is automatically authorized + - - Fallback: Use app password with docker login {{ .RegistryURL }} for quick start (no device tracking) - + + Fallback: Use app password with docker login {{ .RegistryURL }} for quick start (no device tracking) + + + + + + Your Authorized Devices + + + + + Device Name + IP Address + Created + Last Used + Actions + + + + Loading... + + + + + + + + + Data Privacy + Download a copy of all data we store about you. + + + + + Export All My Data + + + + + This includes your authorized devices, sessions, and hold memberships. + Data stored on your PDS is already under your control. + See our Privacy Policy for details. + + + + + + + + Danger Zone + + + + + Delete ATCR Data + Remove your data from ATCR. This action cannot be undone. + + + + + This does not delete your ATProto (Bluesky, Blacksky, Tangled) account.Only ATCR-specific data (authorized devices, hold memberships, settings) will be removed. + + + + + + Also delete all io.atcr.* records from my ATProto PDS + + + This removes ATCR records (manifests, tags, stars, profile) stored in your PDS. + Other records in your account are not impacted. + + + + + + Delete My ATCR Data + + + - - - - Your Authorized Devices - - - - Device Name - IP Address - Created - Last Used - Actions - - - - Loading... - - - - - - - - Data Privacy - Download a copy of all data we store about you. - - - - - Export All My Data - - - - - - This includes your authorized devices, sessions, and hold memberships. - Data stored on your PDS is already under your control. - See our Privacy Policy for details. - - - - - - - Danger Zone - - - Delete ATCR Data - Remove your data from ATCR. This action cannot be undone. - - - This does not delete your ATProto (Bluesky, Blacksky, Tangled) account.Only ATCR-specific data (authorized devices, hold memberships, settings) will be removed. - - - - - - Also delete all io.atcr.* records from my ATProto PDS - - - This removes ATCR records (manifests, tags, stars, profile) stored in your PDS. - Other records in your account are not impacted. - - - - - - Delete My ATCR Data - - - - - -
{{ . }}
+ Push and pull Docker images on the AT Protocol. + Browse public registries or control your data. +
docker login atcr.io
docker push atcr.io/you/app
same docker, decentralized
{{ .Description }}
{{ .Digest }}
The page you're looking for has drifted into uncharted waters.
- Push and pull Docker images on the AT Protocol. - Browse public registries or control your data. -
$ docker login atcr.io -$ docker push atcr.io/you/app - -# same docker, decentralized
Use docker push & pull. No new tools to learn.
Join shared holds or captain your own storage.
Browse and star public container registries.
Use your ATProto handle to sign in
+ Enter your Bluesky or ATProto handle +
Don't have an account? Create one at bsky.app
+ Don't have an account? Create one at + bsky.app +
{{ .Repository.Description }}
{{ .Tag.Digest }}
No tags available
{{ .Manifest.Digest }}
No manifests available
This manifest has associated tags that will also be deleted:
This action cannot be undone.
{{ .Profile.DID }}
Estimated storage usage on your default hold.
Loading...
Select where your container images will be stored.
Devices authorized via docker-credential-atcr credential helper.
docker-credential-atcr
curl -fsSL atcr.io/static/install.sh | bash
~/.docker/config.json
{ + {{ if .OwnedHolds }} + + {{ range .OwnedHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + {{ if .CrewHolds }} + + {{ range .CrewHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + {{ if .EligibleHolds }} + + {{ range .EligibleHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + {{ if .PublicHolds }} + + {{ range .PublicHolds }} + + {{ .DisplayName }}{{ if .Region }} ({{ .Region }}){{ end }} + + {{ end }} + + {{ end }} + + Your images will be stored on the selected hold + + + Save + + + + + + + Hold Details + + DID: + + Region: + + Your Access: + + + + + + + + + Authorized Devices + Devices authorized via docker-credential-atcr credential helper. + + + + + First Time Setup + + Install credential helper: + curl -fsSL atcr.io/static/install.sh | bash + + Configure Docker to use the helper. Add to ~/.docker/config.json: + { "credHelpers": { "{{ .RegistryURL }}": "atcr" } } - - Run any Docker command: - {{ template "docker-command" (print "docker pull " .RegistryURL "/" .Profile.Handle "/myimage") }} - - Browser will open for authorization - click Approve - Done! Device is automatically authorized - + + Run any Docker command: + {{ template "docker-command" (print "docker pull " .RegistryURL "/" .Profile.Handle "/myimage") }} + + Browser will open for authorization - click Approve + Done! Device is automatically authorized + - - Fallback: Use app password with docker login {{ .RegistryURL }} for quick start (no device tracking) - + + Fallback: Use app password with docker login {{ .RegistryURL }} for quick start (no device tracking) + + + + + + Your Authorized Devices + + + + + Device Name + IP Address + Created + Last Used + Actions + + + + Loading... + + + + + + + + + Data Privacy + Download a copy of all data we store about you. + + + + + Export All My Data + + + + + This includes your authorized devices, sessions, and hold memberships. + Data stored on your PDS is already under your control. + See our Privacy Policy for details. + + + + + + + + Danger Zone + + + + + Delete ATCR Data + Remove your data from ATCR. This action cannot be undone. + + + + + This does not delete your ATProto (Bluesky, Blacksky, Tangled) account.Only ATCR-specific data (authorized devices, hold memberships, settings) will be removed. + + + + + + Also delete all io.atcr.* records from my ATProto PDS + + + This removes ATCR records (manifests, tags, stars, profile) stored in your PDS. + Other records in your account are not impacted. + + + + + + Delete My ATCR Data + + +
Your images will be stored on the selected hold
{ "credHelpers": { "{{ .RegistryURL }}": "atcr" } }
docker login {{ .RegistryURL }}
Download a copy of all data we store about you.
+ This includes your authorized devices, sessions, and hold memberships. + Data stored on your PDS is already under your control. + See our Privacy Policy for details. +
Remove your data from ATCR. This action cannot be undone.
io.atcr.*
+ This removes ATCR records (manifests, tags, stars, profile) stored in your PDS. + Other records in your account are not impacted. +
- - This includes your authorized devices, sessions, and hold memberships. - Data stored on your PDS is already under your control. - See our Privacy Policy for details. - -